mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-08 10:29:37 +00:00
Setup other layouts
This commit is contained in:
@@ -2,13 +2,54 @@ import { TopAppBarFixedBase } from "@material/mwc-top-app-bar-fixed/mwc-top-app-
|
||||
import { styles } from "@material/mwc-top-app-bar/mwc-top-app-bar.css";
|
||||
import { css } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { ViewTransitionMixin } from "../mixins/view-transition-mixin";
|
||||
import { haStyleViewTransitions } from "../resources/styles";
|
||||
|
||||
@customElement("ha-top-app-bar-fixed")
|
||||
export class HaTopAppBarFixed extends TopAppBarFixedBase {
|
||||
export class HaTopAppBarFixed extends ViewTransitionMixin(TopAppBarFixedBase) {
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: "content-loading" })
|
||||
public contentLoading = true;
|
||||
|
||||
protected onLoadTransition(): void {
|
||||
// Trigger the transition when content is slotted
|
||||
this.startViewTransition(() => {
|
||||
this.contentLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
protected override enableLoadTransition(): boolean {
|
||||
// Disable automatic transition, we'll trigger it manually
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override firstUpdated() {
|
||||
super.firstUpdated();
|
||||
// Wait for slotted content to be ready
|
||||
const slot = this.shadowRoot?.querySelector("slot:not([name])");
|
||||
if (slot) {
|
||||
const checkContent = () => {
|
||||
const nodes = (slot as HTMLSlotElement).assignedNodes({
|
||||
flatten: true,
|
||||
});
|
||||
if (nodes.length > 0) {
|
||||
this.onLoadTransition();
|
||||
}
|
||||
};
|
||||
// Check immediately in case content is already there
|
||||
checkContent();
|
||||
// Also listen for slotchange
|
||||
slot.addEventListener("slotchange", checkContent, { once: true });
|
||||
} else {
|
||||
// No slot, just trigger immediately
|
||||
this.onLoadTransition();
|
||||
}
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
styles,
|
||||
haStyleViewTransitions,
|
||||
css`
|
||||
header {
|
||||
padding-top: var(--safe-area-inset-top);
|
||||
@@ -23,6 +64,11 @@ export class HaTopAppBarFixed extends TopAppBarFixedBase {
|
||||
);
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
padding-right: var(--safe-area-inset-right);
|
||||
view-transition-name: layout-fade-in;
|
||||
transition: opacity var(--ha-animation-layout-duration) ease-out;
|
||||
}
|
||||
:host([content-loading]) .mdc-top-app-bar--fixed-adjust {
|
||||
opacity: 0;
|
||||
}
|
||||
:host([narrow]) .mdc-top-app-bar--fixed-adjust {
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
|
||||
@@ -7,17 +7,18 @@ import type { MDCTopAppBarAdapter } from "@material/top-app-bar/adapter";
|
||||
import { strings } from "@material/top-app-bar/constants";
|
||||
import MDCFixedTopAppBarFoundation from "@material/top-app-bar/fixed/foundation";
|
||||
import { html, css, nothing } from "lit";
|
||||
import { property, query, customElement } from "lit/decorators";
|
||||
import { property, query, customElement, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styles } from "@material/mwc-top-app-bar/mwc-top-app-bar.css";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import { ViewTransitionMixin } from "../mixins/view-transition-mixin";
|
||||
import { haStyleScrollbar, haStyleViewTransitions } from "../resources/styles";
|
||||
|
||||
export const passiveEventOptionsIfSupported = supportsPassiveEventListener
|
||||
? { passive: true }
|
||||
: undefined;
|
||||
|
||||
@customElement("ha-two-pane-top-app-bar-fixed")
|
||||
export class TopAppBarBaseBase extends BaseElement {
|
||||
export class TopAppBarBaseBase extends ViewTransitionMixin(BaseElement) {
|
||||
protected override mdcFoundation!: MDCFixedTopAppBarFoundation;
|
||||
|
||||
protected override mdcFoundationClass = MDCFixedTopAppBarFoundation;
|
||||
@@ -48,6 +49,20 @@ export class TopAppBarBaseBase extends BaseElement {
|
||||
|
||||
@query(".pane .ha-scrollbar") private _paneElement?: HTMLElement;
|
||||
|
||||
@state() private _loaded = false;
|
||||
|
||||
protected onLoadTransition(): void {
|
||||
// Trigger the transition when content is slotted
|
||||
this.startViewTransition(() => {
|
||||
this._loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
protected enableLoadTransition(): boolean {
|
||||
// Disable automatic transition, we'll trigger it manually
|
||||
return false;
|
||||
}
|
||||
|
||||
@property({ attribute: false, type: Object })
|
||||
get scrollTarget() {
|
||||
return this._scrollTarget || window;
|
||||
@@ -144,7 +159,12 @@ export class TopAppBarBaseBase extends BaseElement {
|
||||
: nothing}
|
||||
<div class="main">
|
||||
${this.pane ? html`<div class="shadow-container"></div>` : nothing}
|
||||
<div class="content">
|
||||
<div
|
||||
class=${classMap({
|
||||
content: true,
|
||||
loading: !this._loaded,
|
||||
})}
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
@@ -235,6 +255,26 @@ export class TopAppBarBaseBase extends BaseElement {
|
||||
super.firstUpdated();
|
||||
this.updateRootPosition();
|
||||
this.registerListeners();
|
||||
|
||||
// Wait for slotted content to be ready for view transition
|
||||
const slot = this.shadowRoot?.querySelector("slot:not([name])");
|
||||
if (slot) {
|
||||
const checkContent = () => {
|
||||
const nodes = (slot as HTMLSlotElement).assignedNodes({
|
||||
flatten: true,
|
||||
});
|
||||
if (nodes.length > 0) {
|
||||
this.onLoadTransition();
|
||||
}
|
||||
};
|
||||
// Check immediately in case content is already there
|
||||
checkContent();
|
||||
// Also listen for slotchange
|
||||
slot.addEventListener("slotchange", checkContent, { once: true });
|
||||
} else {
|
||||
// No slot, just trigger immediately
|
||||
this.onLoadTransition();
|
||||
}
|
||||
}
|
||||
|
||||
override disconnectedCallback() {
|
||||
@@ -245,6 +285,7 @@ export class TopAppBarBaseBase extends BaseElement {
|
||||
static override styles = [
|
||||
styles,
|
||||
haStyleScrollbar,
|
||||
haStyleViewTransitions,
|
||||
css`
|
||||
header {
|
||||
padding-top: var(--safe-area-inset-top);
|
||||
@@ -341,6 +382,11 @@ export class TopAppBarBaseBase extends BaseElement {
|
||||
.mdc-top-app-bar--pane .content {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
view-transition-name: layout-fade-in;
|
||||
transition: opacity var(--ha-animation-layout-duration) ease-out;
|
||||
}
|
||||
.content.loading {
|
||||
opacity: 0;
|
||||
}
|
||||
.mdc-top-app-bar__title {
|
||||
font-size: var(--ha-font-size-xl);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ReactiveElement } from "lit";
|
||||
import { property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { ViewTransitionMixin } from "../mixins/view-transition-mixin";
|
||||
import type { Route } from "../types";
|
||||
|
||||
const extractPage = (path: string, defaultPage: string) => {
|
||||
@@ -43,7 +44,7 @@ export interface RouterOptions {
|
||||
// Time to wait for code to load before we show loading screen.
|
||||
const LOADING_SCREEN_THRESHOLD = 400; // ms
|
||||
|
||||
export class HassRouterPage extends ReactiveElement {
|
||||
export class HassRouterPage extends ViewTransitionMixin(ReactiveElement) {
|
||||
@property({ attribute: false }) public route?: Route;
|
||||
|
||||
protected routerOptions!: RouterOptions;
|
||||
@@ -310,16 +311,19 @@ export class HassRouterPage extends ReactiveElement {
|
||||
page: string,
|
||||
routeOptions: RouteOptions
|
||||
) {
|
||||
if (this.lastChild) {
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
this.startViewTransition(() => {
|
||||
if (this.lastChild) {
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
|
||||
const panelEl = this._cache[page] || this.createElement(routeOptions.tag);
|
||||
this.updatePageEl(panelEl);
|
||||
this.appendChild(panelEl);
|
||||
const panelEl = this._cache[page] || this.createElement(routeOptions.tag);
|
||||
(panelEl as HTMLElement).style.viewTransitionName = "layout-fade-in";
|
||||
this.updatePageEl(panelEl);
|
||||
this.appendChild(panelEl);
|
||||
|
||||
if (routerOptions.cacheAll || routeOptions.cache) {
|
||||
this._cache[page] = panelEl;
|
||||
}
|
||||
if (routerOptions.cacheAll || routeOptions.cache) {
|
||||
this._cache[page] = panelEl;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, eventOptions, property } from "lit/decorators";
|
||||
import { customElement, eventOptions, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { restoreScroll } from "../common/decorators/restore-scroll";
|
||||
import { goBack } from "../common/navigate";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
import "../components/ha-menu-button";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import { ViewTransitionMixin } from "../mixins/view-transition-mixin";
|
||||
import { haStyleScrollbar, haStyleViewTransitions } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
@customElement("hass-subpage")
|
||||
class HassSubpage extends LitElement {
|
||||
class HassSubpage extends ViewTransitionMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public header?: string;
|
||||
@@ -24,9 +26,46 @@ class HassSubpage extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public supervisor = false;
|
||||
|
||||
@state() private _loaded = false;
|
||||
|
||||
// @ts-ignore
|
||||
@restoreScroll(".content") private _savedScrollPos?: number;
|
||||
|
||||
protected onLoadTransition(): void {
|
||||
// Trigger the transition when content is slotted
|
||||
this.startViewTransition(() => {
|
||||
this._loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
protected override enableLoadTransition(): boolean {
|
||||
// Disable automatic transition, we'll trigger it manually
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
// Wait for slotted content to be ready
|
||||
const slot = this.shadowRoot?.querySelector("slot:not([name])");
|
||||
if (slot) {
|
||||
const checkContent = () => {
|
||||
const nodes = (slot as HTMLSlotElement).assignedNodes({
|
||||
flatten: true,
|
||||
});
|
||||
if (nodes.length > 0) {
|
||||
this.onLoadTransition();
|
||||
}
|
||||
};
|
||||
// Check immediately in case content is already there
|
||||
checkContent();
|
||||
// Also listen for slotchange
|
||||
slot.addEventListener("slotchange", checkContent, { once: true });
|
||||
} else {
|
||||
// No slot, just trigger immediately
|
||||
this.onLoadTransition();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="toolbar">
|
||||
@@ -60,7 +99,14 @@ class HassSubpage extends LitElement {
|
||||
<slot name="toolbar-icon"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content ha-scrollbar" @scroll=${this._saveScrollPos}>
|
||||
<div
|
||||
class=${classMap({
|
||||
content: true,
|
||||
"ha-scrollbar": true,
|
||||
loading: !this._loaded,
|
||||
})}
|
||||
@scroll=${this._saveScrollPos}
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div id="fab">
|
||||
@@ -85,6 +131,7 @@ class HassSubpage extends LitElement {
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleScrollbar,
|
||||
haStyleViewTransitions,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
@@ -167,6 +214,11 @@ class HassSubpage extends LitElement {
|
||||
overflow-y: auto;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
view-transition-name: layout-fade-in;
|
||||
transition: opacity var(--ha-animation-layout-duration) ease-out;
|
||||
}
|
||||
.content.loading {
|
||||
opacity: 0;
|
||||
}
|
||||
:host([narrow]) .content {
|
||||
width: calc(
|
||||
|
||||
@@ -11,7 +11,8 @@ import "../components/ha-icon-button-arrow-prev";
|
||||
import "../components/ha-menu-button";
|
||||
import "../components/ha-svg-icon";
|
||||
import "../components/ha-tab";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import { ViewTransitionMixin } from "../mixins/view-transition-mixin";
|
||||
import { haStyleScrollbar, haStyleViewTransitions } from "../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../types";
|
||||
|
||||
export interface PageNavigation {
|
||||
@@ -29,7 +30,7 @@ export interface PageNavigation {
|
||||
}
|
||||
|
||||
@customElement("hass-tabs-subpage")
|
||||
class HassTabsSubpage extends LitElement {
|
||||
class HassTabsSubpage extends ViewTransitionMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public supervisor = false;
|
||||
@@ -61,9 +62,46 @@ class HassTabsSubpage extends LitElement {
|
||||
|
||||
@state() private _activeTab?: PageNavigation;
|
||||
|
||||
@state() private _loaded = false;
|
||||
|
||||
// @ts-ignore
|
||||
@restoreScroll(".content") private _savedScrollPos?: number;
|
||||
|
||||
protected onLoadTransition(): void {
|
||||
// Trigger the transition when content is slotted
|
||||
this.startViewTransition(() => {
|
||||
this._loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
protected override enableLoadTransition(): boolean {
|
||||
// Disable automatic transition, we'll trigger it manually
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
// Wait for slotted content to be ready
|
||||
const slot = this.shadowRoot?.querySelector("slot:not([name])");
|
||||
if (slot) {
|
||||
const checkContent = () => {
|
||||
const nodes = (slot as HTMLSlotElement).assignedNodes({
|
||||
flatten: true,
|
||||
});
|
||||
if (nodes.length > 0) {
|
||||
this.onLoadTransition();
|
||||
}
|
||||
};
|
||||
// Check immediately in case content is already there
|
||||
checkContent();
|
||||
// Also listen for slotchange
|
||||
slot.addEventListener("slotchange", checkContent, { once: true });
|
||||
} else {
|
||||
// No slot, just trigger immediately
|
||||
this.onLoadTransition();
|
||||
}
|
||||
}
|
||||
|
||||
private _getTabs = memoizeOne(
|
||||
(
|
||||
tabs: PageNavigation[],
|
||||
@@ -185,7 +223,12 @@ class HassTabsSubpage extends LitElement {
|
||||
</div>`
|
||||
: nothing}
|
||||
<div
|
||||
class="content ha-scrollbar ${classMap({ tabs: showTabs })}"
|
||||
class=${classMap({
|
||||
content: true,
|
||||
"ha-scrollbar": true,
|
||||
tabs: showTabs,
|
||||
loading: !this._loaded,
|
||||
})}
|
||||
@scroll=${this._saveScrollPos}
|
||||
>
|
||||
<slot></slot>
|
||||
@@ -214,6 +257,7 @@ class HassTabsSubpage extends LitElement {
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleScrollbar,
|
||||
haStyleViewTransitions,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
@@ -332,6 +376,11 @@ class HassTabsSubpage extends LitElement {
|
||||
margin-bottom: var(--safe-area-inset-bottom);
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
view-transition-name: layout-fade-in;
|
||||
transition: opacity var(--ha-animation-layout-duration) ease-out;
|
||||
}
|
||||
.content.loading {
|
||||
opacity: 0;
|
||||
}
|
||||
:host([narrow]) .content {
|
||||
margin-left: var(--safe-area-inset-left);
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import type { LitElement, PropertyValues } from "lit";
|
||||
import type { Constructor } from "../types";
|
||||
import type { PropertyValues, ReactiveElement } from "lit";
|
||||
|
||||
export const ViewTransitionMixin = <T extends Constructor<LitElement>>(
|
||||
type AbstractConstructor<T = object> = abstract new (...args: any[]) => T;
|
||||
|
||||
export const ViewTransitionMixin = <
|
||||
T extends AbstractConstructor<ReactiveElement>,
|
||||
>(
|
||||
superClass: T
|
||||
) =>
|
||||
class ViewTransitionClass extends superClass {
|
||||
) => {
|
||||
abstract class ViewTransitionClass extends superClass {
|
||||
/**
|
||||
* Trigger a view transition if supported by the browser
|
||||
* @param updateCallback - Callback function that updates the DOM
|
||||
@@ -71,4 +74,6 @@ export const ViewTransitionMixin = <T extends Constructor<LitElement>>(
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
return ViewTransitionClass;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user