diff --git a/src/components/ha-clickable-list-item.ts b/src/components/ha-clickable-list-item.ts
index fee471c8f4..20adab1bae 100644
--- a/src/components/ha-clickable-list-item.ts
+++ b/src/components/ha-clickable-list-item.ts
@@ -1,6 +1,4 @@
-import "./ha-clickable-ripple";
import { ListItem } from "@material/mwc-list/mwc-list-item";
-// import { ListItemBase } from "@material/mwc-list/mwc-list-item-base";
import { css, CSSResult, customElement } from "lit-element";
import { html } from "lit-html";
@@ -26,12 +24,10 @@ export class HaClickableListItem extends ListItem {
padding-left: 0px;
padding-right: 0px;
}
-
:host([graphic="avatar"]:not([twoLine])),
:host([graphic="icon"]:not([twoLine])) {
height: 48px;
}
-
a {
width: 100%;
height: 100%;
diff --git a/src/components/ha-clickable-ripple.ts b/src/components/ha-clickable-ripple.ts
deleted file mode 100644
index e33d6c106a..0000000000
--- a/src/components/ha-clickable-ripple.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { ListItem } from "@material/mwc-list/mwc-list-item";
-import { Ripple } from "@material/mwc-ripple";
-import { RippleBase } from "@material/mwc-ripple/mwc-ripple-base";
-// import { ListItemBase } from "@material/mwc-list/mwc-list-item-base";
-import { css, CSSResult, customElement } from "lit-element";
-import { html } from "lit-html";
-
-@customElement("ha-clickable-ripple")
-export class HaClickableRipple extends RippleBase {
- public href?: string;
-
- // public render() {
- // const r = super.render();
- // // prettier-ignore
- // return html`
- //
- // ${r}
- // `;
- // }
-
- public render() {
- /** @classMap */
- const classes = {
- "mdc-ripple-upgraded--unbounded": this.unbounded,
- "mdc-ripple-upgraded--background-focused": this.bgFocused,
- "mdc-ripple-upgraded--foreground-activation": super.fgActivation,
- "mdc-ripple-upgraded--foreground-deactivation": super.fgDeactivation,
- hover: super.hovering,
- primary: this.primary,
- accent: this.accent,
- disabled: this.disabled,
- activated: this.activated,
- selected: this.selected,
- };
- return html`
`;
- }
-
- static get styles(): CSSResult[] {
- return [
- super.styles,
- css`
- /* a {
- width: 100%;
- height: 100%;
- border: 1px solid black;
- vertical-align: middle;
- } */
- `,
- ];
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- "ha-clickable-ripple": HaClickableRipple;
- }
-}
diff --git a/src/components/ha-sidebar-combined.ts b/src/components/ha-sidebar-combined.ts
deleted file mode 100644
index c55ecdbd7e..0000000000
--- a/src/components/ha-sidebar-combined.ts
+++ /dev/null
@@ -1,1201 +0,0 @@
-import "./ha-sidebar-panel-list";
-import "@material/mwc-list/mwc-list-item";
-import "@material/mwc-button/mwc-button";
-import "@material/mwc-icon-button";
-import {
- mdiBell,
- mdiCellphoneCog,
- mdiClose,
- mdiMenu,
- mdiMenuOpen,
- mdiPlus,
- mdiViewDashboard,
-} from "@mdi/js";
-import {
- css,
- CSSResult,
- customElement,
- eventOptions,
- html,
- internalProperty,
- LitElement,
- property,
- PropertyValues,
- query,
-} from "lit-element";
-import { classMap } from "lit-html/directives/class-map";
-import { fireEvent } from "../common/dom/fire_event";
-import { computeDomain } from "../common/entity/compute_domain";
-import { computeRTL } from "../common/util/compute_rtl";
-import { ActionHandlerDetail } from "../data/lovelace";
-import { LocalStorage } from "../common/decorators/local-storage";
-import {
- PersistentNotification,
- subscribeNotifications,
-} from "../data/persistent_notification";
-import {
- ExternalConfig,
- getExternalConfig,
-} from "../external_app/external_config";
-import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
-import { haStyleScrollbar } from "../resources/styles";
-import type { HomeAssistant, PanelInfo } from "../types";
-import "./ha-icon";
-import "./ha-menu-button";
-import "./ha-svg-icon";
-import "./user/ha-user-badge";
-import memoizeOne from "memoize-one";
-import { compare } from "../common/string/compare";
-import { guard } from "lit-html/directives/guard";
-import { HaClickableListItem } from "./ha-clickable-list-item";
-import { List } from "@material/mwc-list";
-
-const SHOW_AFTER_SPACER = ["config", "developer-tools", "hassio"];
-
-const SUPPORT_SCROLL_IF_NEEDED = "scrollIntoViewIfNeeded" in document.body;
-
-const SORT_VALUE_URL_PATHS = {
- map: 1,
- logbook: 2,
- history: 3,
- "developer-tools": 9,
- hassio: 10,
- config: 11,
-};
-
-let Sortable;
-
-const panelSorter = (
- reverseSort: string[],
- defaultPanel: string,
- a: PanelInfo,
- b: PanelInfo
-) => {
- const indexA = reverseSort.indexOf(a.url_path);
- const indexB = reverseSort.indexOf(b.url_path);
- if (indexA !== indexB) {
- if (indexA < indexB) {
- return 1;
- }
- return -1;
- }
- return defaultPanelSorter(defaultPanel, a, b);
-};
-
-const defaultPanelSorter = (
- defaultPanel: string,
- a: PanelInfo,
- b: PanelInfo
-) => {
- // Put all the Lovelace at the top.
- const aLovelace = a.component_name === "lovelace";
- const bLovelace = b.component_name === "lovelace";
-
- if (a.url_path === defaultPanel) {
- return -1;
- }
- if (b.url_path === defaultPanel) {
- return 1;
- }
-
- if (aLovelace && bLovelace) {
- return compare(a.title!, b.title!);
- }
- if (aLovelace && !bLovelace) {
- return -1;
- }
- if (bLovelace) {
- return 1;
- }
-
- const aBuiltIn = a.url_path in SORT_VALUE_URL_PATHS;
- const bBuiltIn = b.url_path in SORT_VALUE_URL_PATHS;
-
- if (aBuiltIn && bBuiltIn) {
- return SORT_VALUE_URL_PATHS[a.url_path] - SORT_VALUE_URL_PATHS[b.url_path];
- }
- if (aBuiltIn) {
- return -1;
- }
- if (bBuiltIn) {
- return 1;
- }
- // both not built in, sort by title
- return compare(a.title!, b.title!);
-};
-
-const computePanels = memoizeOne(
- (
- panels: HomeAssistant["panels"],
- defaultPanel: HomeAssistant["defaultPanel"],
- panelsOrder: string[],
- hiddenPanels: string[]
- ): [PanelInfo[], PanelInfo[]] => {
- if (!panels) {
- return [[], []];
- }
-
- const beforeSpacer: PanelInfo[] = [];
- const afterSpacer: PanelInfo[] = [];
-
- Object.values(panels).forEach((panel) => {
- if (
- hiddenPanels.includes(panel.url_path) ||
- (!panel.title && panel.url_path !== defaultPanel)
- ) {
- return;
- }
- (SHOW_AFTER_SPACER.includes(panel.url_path)
- ? afterSpacer
- : beforeSpacer
- ).push(panel);
- });
-
- const reverseSort = [...panelsOrder].reverse();
-
- beforeSpacer.sort((a, b) => panelSorter(reverseSort, defaultPanel, a, b));
- afterSpacer.sort((a, b) => panelSorter(reverseSort, defaultPanel, a, b));
-
- return [beforeSpacer, afterSpacer];
- }
-);
-
-const isListItem = (element: Element): element is HaClickableListItem => {
- return element.hasAttribute("mwc-list-item");
-};
-
-const isNodeElement = (node: Node): node is Element => {
- return node.nodeType === Node.ELEMENT_NODE;
-};
-
-@customElement("ha-sidebar-combined")
-class HaSidebarCombined extends LitElement {
- @property({ attribute: false }) public hass!: HomeAssistant;
-
- @property({ type: Boolean, reflect: true }) public narrow!: boolean;
-
- @property({ type: Boolean }) public alwaysExpand = false;
-
- @property({ type: Boolean, reflect: true }) public expanded = false;
-
- @property({ type: Boolean }) public editMode = false;
-
- // property used only in css
- // @ts-ignore
- @property({ type: Boolean, reflect: true }) public rtl = false;
-
- @internalProperty() private _renderEmptySortable = false;
-
- @internalProperty() private _externalConfig?: ExternalConfig;
-
- @internalProperty() private _notifications?: PersistentNotification[];
-
- @query("mwc-list.ha-scrollbar", false)
- private _standardPanelList?: List;
-
- @query("mwc-list.utility-panels", false)
- private _utilityPanelList?: List;
-
- private _mouseLeaveTimeout?: number;
-
- private _tooltipHideTimeout?: number;
-
- private _recentKeydownActiveUntil = 0;
-
- private _sortable?;
-
- // @ts-ignore
- @LocalStorage("sidebarPanelOrder", true, {
- attribute: false,
- })
- private _panelOrder: string[] = [];
-
- // @ts-ignore
- @LocalStorage("sidebarHiddenPanels", true, {
- attribute: false,
- })
- private _hiddenPanels: string[] = [];
-
- protected render() {
- if (!this.hass) {
- return html``;
- }
- const debug = false;
-
- // prettier-ignore
- return debug
- ? html`
- ${this._renderNormalPanels()}
- ${this._renderUtilityPanels()} `
- : this.render2();
- }
-
- protected render2() {
- if (!this.hass) {
- return html``;
- }
-
- // prettier-ignore
- return html`
- ${this._renderHeader()} ${this._renderAllPanels()}
-
- ${this._renderNotifications()}
- ${this._renderUserItem()}
-
-
- `;
- }
-
- protected shouldUpdate(changedProps: PropertyValues): boolean {
- if (
- changedProps.has("expanded") ||
- changedProps.has("narrow") ||
- changedProps.has("alwaysExpand") ||
- changedProps.has("_externalConfig") ||
- changedProps.has("_notifications") ||
- changedProps.has("editMode") ||
- changedProps.has("_renderEmptySortable") ||
- changedProps.has("_hiddenPanels") ||
- (changedProps.has("_panelOrder") && !this.editMode)
- ) {
- return true;
- }
- if (!this.hass || !changedProps.has("hass")) {
- return false;
- }
- const oldHass = changedProps.get("hass") as HomeAssistant;
- if (!oldHass) {
- return true;
- }
- const hass = this.hass;
- return (
- hass.panels !== oldHass.panels ||
- hass.panelUrl !== oldHass.panelUrl ||
- hass.user !== oldHass.user ||
- hass.localize !== oldHass.localize ||
- hass.language !== oldHass.language ||
- hass.states !== oldHass.states ||
- hass.defaultPanel !== oldHass.defaultPanel
- );
- }
-
- protected firstUpdated(changedProps: PropertyValues) {
- super.firstUpdated(changedProps);
-
- if (this.hass && this.hass.auth.external) {
- getExternalConfig(this.hass.auth.external).then(() => {});
- }
- subscribeNotifications(this.hass.connection, (notifications) => {
- this._notifications = notifications;
- });
- }
-
- protected updated(changedProps) {
- super.updated(changedProps);
- if (changedProps.has("alwaysExpand")) {
- this.expanded = this.alwaysExpand;
- }
- if (changedProps.has("editMode")) {
- if (this.editMode) {
- this._activateEditMode();
- } else {
- this._deactivateEditMode();
- }
- }
- if (!changedProps.has("hass")) {
- return;
- }
-
- const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
- if (!oldHass || oldHass.language !== this.hass.language) {
- this.rtl = computeRTL(this.hass);
- }
-
- if (!SUPPORT_SCROLL_IF_NEEDED) {
- return;
- }
- if (!oldHass || oldHass.panelUrl !== this.hass.panelUrl) {
- const selectedEl = this.shadowRoot!.querySelector(".iron-selected");
- if (selectedEl) {
- // @ts-ignore
- selectedEl.scrollIntoViewIfNeeded();
- }
- }
- }
-
- private _renderNormalPanels() {
- const [beforeSpacer] = computePanels(
- this.hass.panels,
- this.hass.defaultPanel,
- this._panelOrder,
- this._hiddenPanels
- );
-
- // prettier-ignore
- return html`
-
- ${this.editMode
- ? this._renderPanelsEdit(beforeSpacer)
- : this._renderPanels(beforeSpacer)}
-
- `;
- }
-
- private _renderPanels(panels: PanelInfo[]) {
- return panels
- .filter((panel) => !this._isUtilityPanel(panel))
- .map((panel) =>
- this._renderPanel(
- panel.url_path,
- panel.url_path === this.hass.defaultPanel
- ? panel.title || this.hass.localize("panel.states")
- : this.hass.localize(`panel.${panel.title}`) || panel.title,
- panel.icon,
- panel.url_path === this.hass.defaultPanel && !panel.icon
- ? mdiViewDashboard
- : undefined
- )
- );
- }
-
- private _renderPanel(
- urlPath: string,
- title: string | null,
- icon?: string | null,
- iconPath?: string | null
- ) {
- return html`
-
- ${iconPath
- ? html``
- : html``}
-
- ${title}
- ${this.editMode
- ? html`
-
- `
- : ""}
-
- `;
- }
-
- private async _hidePanel(ev: Event) {
- ev.preventDefault();
- const panel = (ev.currentTarget as any).panel;
- if (this._hiddenPanels.includes(panel)) {
- return;
- }
- // Make a copy for Memoize
- this._hiddenPanels = [...this._hiddenPanels, panel];
- this._renderEmptySortable = true;
- await this.updateComplete;
- const container = this.shadowRoot!.getElementById("sortable")!;
- while (container.lastElementChild) {
- container.removeChild(container.lastElementChild);
- }
- this._renderEmptySortable = false;
- }
-
- private async _unhidePanel(ev: Event) {
- ev.preventDefault();
- const panel = (ev.currentTarget as any).panel;
- this._hiddenPanels = this._hiddenPanels.filter(
- (hidden) => hidden !== panel
- );
- this._renderEmptySortable = true;
- await this.updateComplete;
- const container = this.shadowRoot!.getElementById("sortable")!;
- while (container.lastElementChild) {
- container.removeChild(container.lastElementChild);
- }
- this._renderEmptySortable = false;
- }
-
- private _setFocusFirstListItem() {
- // @ts-ignore
- // list._getItemAtIndex(0)?.rippleHandlers.startFocus();
- this._utilityPanelList?.focusItemAtIndex(0);
- }
-
- private _setFocusLastListItem() {
- // @ts-ignore
- // list._getItemAtIndex(0)?.rippleHandlers.startFocus();
- const list = this._standardPanelList;
- list?.focusItemAtIndex(list?.items.length - 1);
- }
-
- private _renderUtilityPanels() {
- const [, afterSpacer] = computePanels(
- this.hass.panels,
- this.hass.defaultPanel,
- this._panelOrder,
- this._hiddenPanels
- );
-
- // prettier-ignore
- return html`
-
- ${afterSpacer.map((panel) =>
- this._renderPanel(
- panel.url_path,
- panel.url_path === this.hass.defaultPanel
- ? panel.title || this.hass.localize("panel.states")
- : this.hass.localize(`panel.${panel.title}`) || panel.title,
- panel.icon,
- panel.url_path === this.hass.defaultPanel && !panel.icon
- ? mdiViewDashboard
- : undefined
- )
- )}
- ${this._renderExternalConfiguration()}
-
-
- `;
- }
-
- private _renderExternalConfiguration() {
- return html`${this._externalConfig && this._externalConfig.hasSettingsScreen
- ? html`
-
-
-
- ${this.hass.localize("ui.sidebar.external_app_configuration")}
-
-
- `
- : ""}`;
- }
-
- private _handleExternalAppConfiguration(ev: Event) {
- ev.preventDefault();
- this.hass.auth.external!.fireMessage({
- type: "config_screen/show",
- });
- }
-
- private _isUtilityPanel(panel: PanelInfo) {
- return (
- panel.component_name === "developer-tools" ||
- panel.component_name === "config" ||
- panel.component_name === "external-config"
- );
- }
-
- private _renderPanelsEdit(beforeSpacer: PanelInfo[]) {
- // prettier-ignore
- return html`
- ${guard([this._hiddenPanels, this._renderEmptySortable], () =>
- this._renderEmptySortable ? "" : this._renderPanels(beforeSpacer)
- )}
-
- ${this._renderSpacer()}
- ${this._renderHiddenPanels()} `;
- }
-
- private _renderHiddenPanels() {
- return html` ${this._hiddenPanels.length
- ? html`${this._hiddenPanels.map((url) => {
- const panel = this.hass.panels[url];
- if (!panel) {
- return "";
- }
- return html`
-
- ${panel.url_path === this.hass.defaultPanel
- ? this.hass.localize("panel.states")
- : this.hass.localize(`panel.${panel.title}`) ||
- panel.title}
-
-
-
- `;
- })}
- ${this._renderSpacer()}`
- : ""}`;
- }
-
- private _renderHeader() {
- return html``;
- }
-
- private _renderAllPanels() {
- return html` ${this._renderNormalPanels()} ${this._renderUtilityPanels()} `;
- }
-
- private _renderSpacer(enabled = true) {
- return enabled
- ? html``
- : html``;
- }
-
- private _renderNotifications() {
- let notificationCount = this._notifications
- ? this._notifications.length
- : 0;
- for (const entityId in this.hass.states) {
- if (computeDomain(entityId) === "configurator") {
- notificationCount++;
- }
- }
-
- return html`
-
-
- ${!this.expanded && notificationCount > 0
- ? html`
-
- ${notificationCount}
-
- `
- : ""}
-
- ${this.hass.localize("ui.notification_drawer.title")}
-
- ${this.expanded && notificationCount > 0
- ? html`
- ${notificationCount}
- `
- : ""}
-
- `;
- }
-
- private _renderUserItem() {
- return html`
-
-
-
- ${this.hass.user ? this.hass.user.name : ""}
-
- `;
- }
-
- private get _tooltip() {
- return this.shadowRoot!.querySelector(".tooltip")! as HTMLDivElement;
- }
-
- private _handleAction(ev: CustomEvent) {
- if (ev.detail.action !== "hold") {
- return;
- }
-
- fireEvent(this, "hass-edit-sidebar", { editMode: true });
- }
-
- private async _activateEditMode() {
- if (!Sortable) {
- const [sortableImport, sortStylesImport] = await Promise.all([
- import("sortablejs/modular/sortable.core.esm"),
- import("../resources/ha-sortable-style-ha-clickable"),
- ]);
-
- const style = document.createElement("style");
- style.innerHTML = sortStylesImport.sortableStyles.cssText;
- this.shadowRoot!.appendChild(style);
-
- Sortable = sortableImport.Sortable;
- Sortable.mount(sortableImport.OnSpill);
- Sortable.mount(sortableImport.AutoScroll());
- }
-
- await this.updateComplete;
-
- this._createSortable();
- }
-
- private _createSortable() {
- this._sortable = new Sortable(this.shadowRoot!.getElementById("sortable"), {
- animation: 150,
- fallbackClass: "sortable-fallback",
- dataIdAttr: "data-panel",
- handle: "ha-clickable-list-item",
- onSort: async () => {
- this._panelOrder = this._sortable.toArray();
- },
- });
- }
-
- private _deactivateEditMode() {
- this._sortable?.destroy();
- this._sortable = undefined;
- }
-
- private _closeEditMode() {
- fireEvent(this, "hass-edit-sidebar", { editMode: false });
- }
-
- private _itemMouseEnter(ev: MouseEvent) {
- // On keypresses on the listbox, we're going to ignore mouse enter events
- // for 100ms so that we ignore it when pressing down arrow scrolls the
- // sidebar causing the mouse to hover a new icon
- if (
- this.expanded ||
- new Date().getTime() < this._recentKeydownActiveUntil
- ) {
- return;
- }
- if (this._mouseLeaveTimeout) {
- clearTimeout(this._mouseLeaveTimeout);
- this._mouseLeaveTimeout = undefined;
- }
- this._showTooltip(ev.currentTarget);
- }
-
- private _itemMouseLeave() {
- if (this._mouseLeaveTimeout) {
- clearTimeout(this._mouseLeaveTimeout);
- }
- this._mouseLeaveTimeout = window.setTimeout(() => {
- this._hideTooltip();
- }, 500);
- }
-
- private _listboxFocusIn(ev) {
- // REMINDER to ask what this line is for
- if (this.expanded || ev.target.nodeName !== "A") {
- return;
- }
- this._showTooltip(ev.target.querySelector("ha-clickable-list-item"));
- }
-
- private _listboxFocusOut() {
- this._hideTooltip();
- }
-
- @eventOptions({
- passive: true,
- })
- private _listboxScroll() {
- // On keypresses on the listbox, we're going to ignore scroll events
- // for 100ms so that if pressing down arrow scrolls the sidebar, the tooltip
- // will not be hidden.
- if (new Date().getTime() < this._recentKeydownActiveUntil) {
- return;
- }
- this._hideTooltip();
- }
-
- private _getIndexOfTarget(evt: Event): number {
- const listbox = evt.currentTarget as List;
- const elements = listbox.items;
- const path = evt.composedPath();
-
- for (const pathItem of path as Node[]) {
- let index = -1;
- if (isNodeElement(pathItem) && isListItem(pathItem)) {
- index = elements.indexOf(pathItem);
- }
-
- if (index !== -1) {
- return index;
- }
- }
-
- return -1;
- }
-
- private _listboxKeydown(ev: KeyboardEvent) {
- const [beforeSpacer] = computePanels(
- this.hass.panels,
- this.hass.defaultPanel,
- this._panelOrder,
- this._hiddenPanels
- );
-
- const curIndex = this._getIndexOfTarget(ev);
- const curList = ev.currentTarget as List;
-
- if (ev.code === "ArrowDown" && curList === this._standardPanelList) {
- const isLastItem = curIndex === beforeSpacer.length - 1;
-
- if (isLastItem) {
- this._setFocusFirstListItem();
- }
- } else if (ev.code === "ArrowUp" && curList === this._utilityPanelList) {
- const isFirstItem = curIndex === 0;
-
- if (isFirstItem) {
- this._setFocusLastListItem();
- }
- } else if (ev.code === "Enter") {
- (ev.target as HaClickableListItem)?.shadowRoot
- ?.querySelector("a")
- ?.click();
- }
-
- this._recentKeydownActiveUntil = new Date().getTime() + 100;
- }
-
- private _showTooltip(item) {
- if (this._tooltipHideTimeout) {
- clearTimeout(this._tooltipHideTimeout);
- this._tooltipHideTimeout = undefined;
- }
- const tooltip = this._tooltip;
- const listbox = this.shadowRoot!.querySelector("mwc-list")!;
- let top = item.offsetTop + 11;
- if (listbox.contains(item)) {
- top -= listbox.scrollTop;
- }
- tooltip.innerHTML = item.querySelector(".item-text")!.innerHTML;
- tooltip.style.display = "block";
- tooltip.style.top = `${top}px`;
- tooltip.style.left = `${item.offsetLeft + item.clientWidth + 4}px`;
- }
-
- private _hideTooltip() {
- // Delay it a little in case other events are pending processing.
- if (!this._tooltipHideTimeout) {
- this._tooltipHideTimeout = window.setTimeout(() => {
- this._tooltipHideTimeout = undefined;
- this._tooltip.style.display = "none";
- }, 10);
- }
- }
-
- private _handleShowNotificationDrawer() {
- fireEvent(this, "hass-show-notifications");
- }
-
- private _toggleSidebar(ev: CustomEvent) {
- if (ev.detail.action !== "tap") {
- return;
- }
- fireEvent(this, "hass-toggle-menu");
- }
-
- static get styles(): CSSResult[] {
- return [
- haStyleScrollbar,
- css`
- :host {
- /* height: calc(100% - var(--header-height)); */
- height: 100%;
- display: block;
- overflow: hidden;
- -ms-user-select: none;
- -webkit-user-select: none;
- -moz-user-select: none;
- border-right: 1px solid var(--divider-color);
- background-color: var(--sidebar-background-color);
- width: 64px;
- }
- :host([expanded]) {
- width: 256px;
- width: calc(256px + env(safe-area-inset-left));
- }
- :host([rtl]) {
- border-right: 0;
- border-left: 1px solid var(--divider-color);
- }
- :host([rtl]) .menu {
- padding-left: 8.5px;
- padding-right: calc(8.5px + env(safe-area-inset-right));
- }
- :host([expanded]) .menu {
- width: calc(256px + env(safe-area-inset-left));
- }
- :host([rtl][expanded]) .menu {
- width: calc(256px + env(safe-area-inset-right));
- }
- .menu mwc-icon-button {
- color: var(--sidebar-icon-color);
- }
- :host([expanded]) .menu mwc-icon-button {
- margin-right: 23px;
- }
- :host([expanded][rtl]) .menu mwc-icon-button {
- margin-right: 0px;
- margin-left: 23px;
- }
-
- .title {
- width: 100%;
- display: none;
- }
- :host([narrow]) .title {
- padding: 0 0px;
- }
- :host([expanded]) .title {
- display: initial;
- }
- .title mwc-button {
- width: 100%;
- }
- #sortable,
- .hidden-panel {
- display: none;
- }
-
- div.panels {
- height: 100%;
- position: relative;
- }
-
- mwc-list.ha-scrollbar {
- height: calc(100% - var(--header-height) - 232px);
- --mdc-list-vertical-padding: 4px 0;
- padding: 4px 0;
- display: flex;
- justify-content: space-between;
- flex-direction: column;
- box-sizing: border-box;
- overflow-x: hidden;
- background: none;
- margin-left: env(safe-area-inset-left);
- -ms-user-select: none;
- -webkit-user-select: none;
- -moz-user-select: none;
- border-right: 1px solid var(--divider-color);
- background-color: var(--sidebar-background-color);
- width: 64px;
- }
- :host([rtl]) mwc-list {
- border-right: 0;
- border-left: 1px solid var(--divider-color);
- }
-
- a {
- text-decoration: none;
- color: var(--sidebar-text-color);
- font-weight: 500;
- font-size: 14px;
- position: relative;
- outline: 0;
- }
-
- mwc-list-item {
- box-sizing: border-box;
- margin: 4px 8px;
- border-radius: 4px;
- width: 48px;
- --mdc-list-item-graphic-margin: 16px;
- --mdc-list-item-meta-size: 32px;
- }
- :host([expanded]) mwc-list {
- width: 256px;
- width: calc(256px + env(safe-area-inset-left));
- }
-
- :host([rtl]) mwc-list-item {
- padding-left: auto;
- padding-right: 12px;
- }
-
- ha-icon[slot="graphic"],
- ha-svg-icon[slot="graphic"] {
- color: var(--sidebar-icon-color);
- }
-
- [slot="graphic"] {
- width: 100%;
- }
-
- :host([rtl]) mwc-list {
- margin-left: initial;
- margin-right: env(safe-area-inset-right);
- }
- .menu {
- height: var(--header-height);
- display: flex;
- padding: 0 8.5px;
- border-bottom: 1px solid transparent;
- white-space: nowrap;
- font-weight: 400;
- color: var(--primary-text-color);
- border-bottom: 1px solid var(--divider-color);
- background-color: var(--primary-background-color);
- font-size: 20px;
- align-items: center;
- padding-left: calc(8.5px + env(safe-area-inset-left));
- }
-
- .iron-selected mwc-list-item::before,
- a:not(.iron-selected):focus::before {
- border-radius: 4px;
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- pointer-events: none;
- content: "";
- transition: opacity 15ms linear;
- will-change: opacity;
- }
- .iron-selected mwc-list-item::before {
- background-color: var(--sidebar-selected-icon-color);
- opacity: 0.12;
- }
- a:not(.iron-selected):focus::before {
- background-color: currentColor;
- opacity: var(--dark-divider-opacity);
- margin: 4px 8px;
- }
- .iron-selected mwc-list-item:focus::before,
- .iron-selected:focus mwc-list-item::before {
- opacity: 0.2;
- }
-
- .iron-selected mwc-list-item[pressed]:before {
- opacity: 0.37;
- }
-
- mwc-list-item span {
- color: var(--sidebar-text-color);
- font-weight: 500;
- font-size: 14px;
- width: 100%;
- }
-
- a.iron-selected ha-icon,
- a.iron-selected ha-svg-icon,
- a.iron-selected mwc-list-item ha-icon,
- a.iron-selected mwc-list-item ha-svg-icon {
- color: var(--sidebar-selected-icon-color);
- }
-
- a.iron-selected .item-text {
- color: var(--sidebar-selected-text-color);
- }
-
- mwc-list-item mwc-list-item .item-text,
- mwc-list-item .item-text {
- display: none;
- max-width: calc(100% - 56px);
- }
- :host([expanded]) mwc-list-item .item-text {
- display: block;
- }
-
- .divider {
- padding: 10px 0;
- }
- .divider::before {
- display: block;
- height: 100px;
- background-color: var(--divider-color);
- }
- .notifications-container {
- display: flex;
- margin-left: env(safe-area-inset-left);
- }
- :host([rtl]) .notifications-container {
- margin-left: initial;
- margin-right: env(safe-area-inset-right);
- }
- .notifications {
- cursor: pointer;
- }
- .notifications .item-text {
- flex: 1;
- }
- .profile {
- margin-left: env(safe-area-inset-left);
- }
- :host([rtl]) .profile {
- margin-left: initial;
- margin-right: env(safe-area-inset-right);
- }
- .profile mwc-list-item {
- padding-left: 4px;
- }
- :host([rtl]) .profile mwc-list-item {
- padding-left: auto;
- padding-right: 4px;
- }
- .profile .item-text {
- margin-left: 8px;
- }
- :host([rtl]) .profile .item-text {
- margin-right: 8px;
- }
-
- .notification-badge {
- /* min-width: 20px;
- box-sizing: border-box;
- border-radius: 50%;
- font-weight: 400;
- background-color: var(--accent-color);
- line-height: 30px;
- text-align: center;
- padding: 0px 4px;
- color: var(--text-accent-color, var(--text-primary-color));
- font-size: 14px; */
- }
- ha-svg-icon + .notification-badge {
- position: absolute;
- bottom: 14px;
- left: 26px;
- font-size: 0.65em;
- }
-
- .spacer {
- flex: 1;
- pointer-events: none;
- }
-
- .bottom-spacer {
- flex: 1;
- pointer-events: none;
- height: 100%;
- }
-
- .subheader {
- color: var(--sidebar-text-color);
- font-weight: 500;
- font-size: 14px;
- padding: 16px;
- white-space: nowrap;
- }
-
- .dev-tools {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- padding: 0 8px;
- width: 256px;
- box-sizing: border-box;
- }
-
- .dev-tools a {
- color: var(--sidebar-icon-color);
- }
-
- .tooltip {
- display: none;
- position: absolute;
- opacity: 0.9;
- border-radius: 2px;
- white-space: nowrap;
- color: var(--sidebar-background-color);
- background-color: var(--sidebar-text-color);
- padding: 4px;
- font-weight: 500;
- }
-
- :host([rtl]) .menu mwc-icon-button {
- -webkit-transform: scaleX(-1);
- transform: scaleX(-1);
- }
- `,
- ];
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- "ha-sidebar-combined": HaSidebarCombined;
- }
-}
diff --git a/src/components/ha-sidebar-header.ts b/src/components/ha-sidebar-header.ts
deleted file mode 100644
index 903408d9f3..0000000000
--- a/src/components/ha-sidebar-header.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import "./ha-sidebar-panel-list";
-import "./ha-clickable-list-item";
-import "@material/mwc-list/mwc-list-item";
-import "@material/mwc-list/mwc-list";
-import "@material/mwc-button/mwc-button";
-import "@material/mwc-icon-button";
-import { mdiMenu, mdiMenuOpen } from "@mdi/js";
-import {
- css,
- CSSResult,
- customElement,
- html,
- LitElement,
- property,
- TemplateResult,
-} from "lit-element";
-
-import { haStyleScrollbar } from "../resources/styles";
-import type { HomeAssistant } from "../types";
-import "./ha-icon";
-import "./ha-menu-button";
-import "./ha-svg-icon";
-import "./user/ha-user-badge";
-
-@customElement("ha-sidebar-header")
-class HaSidebarHeader extends LitElement {
- @property({ attribute: false }) public hass!: HomeAssistant;
-
- @property({ type: Boolean, reflect: true }) public narrow!: boolean;
-
- @property({ type: Boolean, reflect: true }) public expanded = false;
-
- @property() public text: TemplateResult | string = "";
-
- @property() public toggleButtonCallback?: (ev: CustomEvent) => void;
-
- // property used only in css
- // @ts-ignore
- @property({ type: Boolean, reflect: true }) public rtl = false;
-
- protected render() {
- if (!this.hass) {
- return html``;
- }
-
- return html`
- ${!this.narrow
- ? html`
-
-
-
- `
- : ""}
-
- ${this.text}
-
- `;
- }
-
- static get styles(): CSSResult[] {
- return [haStyleScrollbar, css``];
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- "ha-sidebar-header": HaSidebarHeader;
- }
-}
diff --git a/src/components/ha-sidebar-overhaul.ts b/src/components/ha-sidebar-overhaul.ts
deleted file mode 100644
index 936314380f..0000000000
--- a/src/components/ha-sidebar-overhaul.ts
+++ /dev/null
@@ -1,331 +0,0 @@
-import "./ha-sidebar-header";
-import "./ha-sidebar-panel-list";
-import "./ha-clickable-list-item";
-import "@material/mwc-list/mwc-list-item";
-import "@material/mwc-list/mwc-list";
-import "@material/mwc-button/mwc-button";
-import "@material/mwc-icon-button";
-import { mdiBell, mdiMenu, mdiMenuOpen } from "@mdi/js";
-import {
- css,
- CSSResult,
- customElement,
- eventOptions,
- html,
- internalProperty,
- LitElement,
- property,
- PropertyValues,
-} from "lit-element";
-import { classMap } from "lit-html/directives/class-map";
-import { fireEvent } from "../common/dom/fire_event";
-import { computeDomain } from "../common/entity/compute_domain";
-import { computeRTL } from "../common/util/compute_rtl";
-import { ActionHandlerDetail } from "../data/lovelace";
-import {
- PersistentNotification,
- subscribeNotifications,
-} from "../data/persistent_notification";
-import { getExternalConfig } from "../external_app/external_config";
-import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
-import { haStyleScrollbar } from "../resources/styles";
-import type { HomeAssistant } from "../types";
-import "./ha-icon";
-import "./ha-menu-button";
-import "./ha-svg-icon";
-import "./user/ha-user-badge";
-import { sidebarStyles } from "./ha-sidebar";
-
-const SUPPORT_SCROLL_IF_NEEDED = "scrollIntoViewIfNeeded" in document.body;
-
-let Sortable;
-
-@customElement("ha-sidebar-overhaul")
-class HaSidebarOverhaul extends LitElement {
- @property({ attribute: false }) public hass!: HomeAssistant;
-
- @property({ type: Boolean, reflect: true }) public narrow!: boolean;
-
- @property({ type: Boolean }) public alwaysExpand = false;
-
- @property({ type: Boolean, reflect: true }) public expanded = false;
-
- @property({ type: Boolean }) public editMode = false;
-
- @internalProperty() private _notifications?: PersistentNotification[];
-
- // property used only in css
- // @ts-ignore
- @property({ type: Boolean, reflect: true }) public rtl = false;
-
- private _sortable?;
-
- protected render() {
- const hass = this.hass;
- if (!this.hass) {
- return html``;
- }
-
- // prettier-ignore
- return html` ${this._renderHeader()}
-
-
-
- Panel 1
- Panel 2
- Panel 3
- Divider
- Utility
-
-
-
- User stuff
-
`;
- }
-
- private _renderHeader() {
- return html``;
- }
-
- private _editDoneButton() {
- return html` this._closeEditMode()}>
- ${this.hass.localize("ui.sidebar.done")}
- `;
- }
-
- protected updated(changedProps) {
- super.updated(changedProps);
- if (changedProps.has("alwaysExpand")) {
- this.expanded = this.alwaysExpand;
- }
-
- if (changedProps.has("editMode")) {
- if (this.editMode) {
- this._activateEditMode();
- } else {
- this._deactivateEditMode();
- }
- }
-
- if (!changedProps.has("hass")) {
- return;
- }
-
- const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
- if (!oldHass || oldHass.language !== this.hass.language) {
- this.rtl = computeRTL(this.hass);
- }
-
- if (!SUPPORT_SCROLL_IF_NEEDED) {
- return;
- }
- // if (!oldHass || oldHass.panelUrl !== this.hass.panelUrl) {
- // const selectedEl = this.shadowRoot!.querySelector(".iron-selected");
- // if (selectedEl) {
- // // @ts-ignore
- // selectedEl.scrollIntoViewIfNeeded();
- // }
- // }
- }
-
- private async _activateEditMode() {
- if (!Sortable) {
- const [sortableImport, sortStylesImport] = await Promise.all([
- import("sortablejs/modular/sortable.core.esm"),
- import("../resources/ha-sortable-style-ha-clickable"),
- ]);
-
- const style = document.createElement("style");
- style.innerHTML = sortStylesImport.sortableStyles.cssText;
- this.shadowRoot!.appendChild(style);
-
- Sortable = sortableImport.Sortable;
- Sortable.mount(sortableImport.OnSpill);
- Sortable.mount(sortableImport.AutoScroll());
- }
-
- await this.updateComplete;
-
- this._createSortable();
- }
-
- private _createSortable() {
- this._sortable = new Sortable(this.shadowRoot!.getElementById("sortable"), {
- animation: 150,
- fallbackClass: "sortable-fallback",
- // fallbackTolerance: 15,
- dataIdAttr: "data-panel",
- handle: "span",
- onSort: async () => {},
- });
- }
-
- private _deactivateEditMode() {
- this._sortable?.destroy();
- this._sortable = undefined;
- }
-
- private _handleAction(ev: CustomEvent) {
- if (ev.detail.action !== "hold") {
- return;
- }
-
- fireEvent(this, "hass-edit-sidebar", { editMode: true });
- }
-
- private _closeEditMode() {
- fireEvent(this, "hass-edit-sidebar", { editMode: false });
- }
-
- private _toggleSidebar(ev: CustomEvent) {
- if (ev.detail.action !== "tap") {
- return;
- }
- fireEvent(this, "hass-toggle-menu");
- }
-
- static get styles(): CSSResult[] {
- return [
- haStyleScrollbar,
- css`
- :host {
- /* height: calc(100% - var(--header-height)); */
- height: 100%;
- display: block;
- overflow: hidden;
- -ms-user-select: none;
- -webkit-user-select: none;
- -moz-user-select: none;
- border-right: 1px solid var(--divider-color);
- background-color: var(--sidebar-background-color);
- width: 64px;
- }
- :host([expanded]) {
- width: 256px;
- width: calc(256px + env(safe-area-inset-left));
- }
- :host([rtl]) {
- border-right: 0;
- border-left: 1px solid var(--divider-color);
- }
- .menu {
- height: var(--header-height);
- display: flex;
- padding: 0 8.5px;
- border-bottom: 1px solid transparent;
- white-space: nowrap;
- font-weight: 400;
- color: var(--primary-text-color);
- border-bottom: 1px solid var(--divider-color);
- background-color: var(--primary-background-color);
- font-size: 20px;
- align-items: center;
- padding-left: calc(8.5px + env(safe-area-inset-left));
- }
-
- :host([rtl]) .menu {
- padding-left: 8.5px;
- padding-right: calc(8.5px + env(safe-area-inset-right));
- }
- :host([expanded]) .menu {
- width: calc(256px + env(safe-area-inset-left));
- }
- :host([rtl][expanded]) .menu {
- width: calc(256px + env(safe-area-inset-right));
- }
- .menu mwc-icon-button {
- color: var(--sidebar-icon-color);
- }
- :host([expanded]) .menu mwc-icon-button {
- margin-right: 23px;
- }
- :host([expanded][rtl]) .menu mwc-icon-button {
- margin-right: 0px;
- margin-left: 23px;
- }
-
- .title {
- width: 100%;
- display: none;
- }
- :host([narrow]) .title {
- padding: 0 0px;
- }
- :host([expanded]) .title {
- display: initial;
- }
- .title mwc-button {
- width: 100%;
- }
-
- #sortable,
- .hidden-panel {
- display: none;
- }
-
- .all-panels {
- border: 2px solid black;
- height: calc(100% - var(--header-height) - 132px);
- position: relative;
- }
- .panels {
- border: 1px solid blue;
- /* height: calc(100% - var(--header-height)); */
- }
-
- .divider {
- border: 1px solid pink;
- /* height: calc(100% - var(--header-height)); */
- }
- .utilities {
- border: 1px solid red;
- position: absolute;
- bottom: 0px;
- width: 100%;
- }
- .user-stuff {
- border: 1px solid green;
- }
- `,
- ];
- }
-
- // static get styles(): CSSResult[] {
- // return [haStyleScrollbar, sidebarStyles];
- // }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- "ha-sidebar-overhaul": HaSidebarOverhaul;
- }
-}
diff --git a/src/components/ha-sidebar-panel-list.ts b/src/components/ha-sidebar-panel-list.ts
deleted file mode 100644
index 3dba47a987..0000000000
--- a/src/components/ha-sidebar-panel-list.ts
+++ /dev/null
@@ -1,873 +0,0 @@
-import "./ha-clickable-list-item";
-import "@material/mwc-list/mwc-list-item";
-import "@material/mwc-list/mwc-list";
-import "@material/mwc-button/mwc-button";
-import "@material/mwc-icon-button";
-import { mdiCellphoneCog, mdiClose, mdiPlus, mdiViewDashboard } from "@mdi/js";
-import {
- css,
- CSSResult,
- customElement,
- eventOptions,
- html,
- internalProperty,
- LitElement,
- property,
- PropertyValues,
-} from "lit-element";
-import { guard } from "lit-html/directives/guard";
-import memoizeOne from "memoize-one";
-import { LocalStorage } from "../common/decorators/local-storage";
-import { compare } from "../common/string/compare";
-import { computeRTL } from "../common/util/compute_rtl";
-import {
- ExternalConfig,
- getExternalConfig,
-} from "../external_app/external_config";
-import { haStyleScrollbar } from "../resources/styles";
-import type { HomeAssistant, PanelInfo } from "../types";
-import "./ha-icon";
-import "./ha-menu-button";
-import "./ha-svg-icon";
-import "./user/ha-user-badge";
-
-const SHOW_AFTER_SPACER = ["config", "developer-tools", "hassio"];
-
-const SUPPORT_SCROLL_IF_NEEDED = "scrollIntoViewIfNeeded" in document.body;
-
-const SORT_VALUE_URL_PATHS = {
- map: 1,
- logbook: 2,
- history: 3,
- "developer-tools": 9,
- hassio: 10,
- config: 11,
-};
-
-const panelSorter = (
- reverseSort: string[],
- defaultPanel: string,
- a: PanelInfo,
- b: PanelInfo
-) => {
- const indexA = reverseSort.indexOf(a.url_path);
- const indexB = reverseSort.indexOf(b.url_path);
- if (indexA !== indexB) {
- if (indexA < indexB) {
- return 1;
- }
- return -1;
- }
- return defaultPanelSorter(defaultPanel, a, b);
-};
-
-const defaultPanelSorter = (
- defaultPanel: string,
- a: PanelInfo,
- b: PanelInfo
-) => {
- // Put all the Lovelace at the top.
- const aLovelace = a.component_name === "lovelace";
- const bLovelace = b.component_name === "lovelace";
-
- if (a.url_path === defaultPanel) {
- return -1;
- }
- if (b.url_path === defaultPanel) {
- return 1;
- }
-
- if (aLovelace && bLovelace) {
- return compare(a.title!, b.title!);
- }
- if (aLovelace && !bLovelace) {
- return -1;
- }
- if (bLovelace) {
- return 1;
- }
-
- const aBuiltIn = a.url_path in SORT_VALUE_URL_PATHS;
- const bBuiltIn = b.url_path in SORT_VALUE_URL_PATHS;
-
- if (aBuiltIn && bBuiltIn) {
- return SORT_VALUE_URL_PATHS[a.url_path] - SORT_VALUE_URL_PATHS[b.url_path];
- }
- if (aBuiltIn) {
- return -1;
- }
- if (bBuiltIn) {
- return 1;
- }
- // both not built in, sort by title
- return compare(a.title!, b.title!);
-};
-
-const computePanels = memoizeOne(
- (
- panels: HomeAssistant["panels"],
- defaultPanel: HomeAssistant["defaultPanel"],
- panelsOrder: string[],
- hiddenPanels: string[]
- ): [PanelInfo[], PanelInfo[]] => {
- if (!panels) {
- return [[], []];
- }
-
- const beforeSpacer: PanelInfo[] = [];
- const afterSpacer: PanelInfo[] = [];
-
- Object.values(panels).forEach((panel) => {
- if (
- hiddenPanels.includes(panel.url_path) ||
- (!panel.title && panel.url_path !== defaultPanel)
- ) {
- return;
- }
- (SHOW_AFTER_SPACER.includes(panel.url_path)
- ? afterSpacer
- : beforeSpacer
- ).push(panel);
- });
-
- const reverseSort = [...panelsOrder].reverse();
-
- beforeSpacer.sort((a, b) => panelSorter(reverseSort, defaultPanel, a, b));
- afterSpacer.sort((a, b) => panelSorter(reverseSort, defaultPanel, a, b));
-
- return [beforeSpacer, afterSpacer];
- }
-);
-
-let Sortable;
-
-@customElement("ha-sidebar-panel-list")
-class HaSidebarPanelList extends LitElement {
- @property({ attribute: false }) public hass!: HomeAssistant;
-
- @property({ type: Boolean, reflect: true }) public narrow!: boolean;
-
- @property({ type: Boolean }) public alwaysExpand = false;
-
- @property({ type: Boolean, reflect: true }) public expanded = false;
-
- @property({ type: Boolean }) public editMode = false;
-
- // property used only in css
- // @ts-ignore
- @property({ type: Boolean, reflect: true }) public rtl = false;
-
- @internalProperty() private _renderEmptySortable = false;
-
- @internalProperty() private _externalConfig?: ExternalConfig;
-
- private _mouseLeaveTimeout?: number;
-
- private _tooltipHideTimeout?: number;
-
- private _recentKeydownActiveUntil = 0;
-
- private _sortable?;
-
- // @ts-ignore
- @LocalStorage("sidebarPanelOrder", true, {
- attribute: false,
- })
- private _panelOrder: string[] = [];
-
- // @ts-ignore
- @LocalStorage("sidebarHiddenPanels", true, {
- attribute: false,
- })
- private _hiddenPanels: string[] = [];
-
- protected render() {
- if (!this.hass) {
- return html``;
- }
-
- // prettier-ignore
- return html`
- ${this._renderNormalPanels()}
- ${this._renderUtilityPanels()}
-
- `;
- }
-
- protected shouldUpdate(changedProps: PropertyValues): boolean {
- if (
- changedProps.has("expanded") ||
- changedProps.has("narrow") ||
- changedProps.has("alwaysExpand") ||
- changedProps.has("_externalConfig") ||
- changedProps.has("_notifications") ||
- changedProps.has("editMode") ||
- changedProps.has("_renderEmptySortable") ||
- changedProps.has("_hiddenPanels") ||
- (changedProps.has("_panelOrder") && !this.editMode)
- ) {
- return true;
- }
- if (!this.hass || !changedProps.has("hass")) {
- return false;
- }
- const oldHass = changedProps.get("hass") as HomeAssistant;
- if (!oldHass) {
- return true;
- }
- const hass = this.hass;
- return (
- hass.panels !== oldHass.panels ||
- hass.panelUrl !== oldHass.panelUrl ||
- hass.user !== oldHass.user ||
- hass.localize !== oldHass.localize ||
- hass.language !== oldHass.language ||
- hass.states !== oldHass.states ||
- hass.defaultPanel !== oldHass.defaultPanel
- );
- }
-
- protected firstUpdated(changedProps: PropertyValues) {
- super.firstUpdated(changedProps);
-
- if (this.hass && this.hass.auth.external) {
- getExternalConfig(this.hass.auth.external).then((conf) => {
- this._externalConfig = conf;
- });
- }
- }
-
- protected updated(changedProps) {
- super.updated(changedProps);
- if (changedProps.has("alwaysExpand")) {
- this.expanded = this.alwaysExpand;
- }
- if (changedProps.has("editMode")) {
- if (this.editMode) {
- this._activateEditMode();
- } else {
- this._deactivateEditMode();
- }
- }
- if (!changedProps.has("hass")) {
- return;
- }
-
- const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
- if (!oldHass || oldHass.language !== this.hass.language) {
- this.rtl = computeRTL(this.hass);
- }
-
- if (!SUPPORT_SCROLL_IF_NEEDED) {
- return;
- }
- if (!oldHass || oldHass.panelUrl !== this.hass.panelUrl) {
- const selectedEl = this.shadowRoot!.querySelector(".iron-selected");
- if (selectedEl) {
- // @ts-ignore
- selectedEl.scrollIntoViewIfNeeded();
- }
- }
- }
-
- private _renderNormalPanels() {
- const [beforeSpacer] = computePanels(
- this.hass.panels,
- this.hass.defaultPanel,
- this._panelOrder,
- this._hiddenPanels
- );
-
- // prettier-ignore
- return html`
-
- ${this.editMode
- ? this._renderPanelsEdit(beforeSpacer)
- : this._renderPanels(beforeSpacer)}
-
-
-
-
-
- `;
- }
-
- private _renderPanelsEdit(beforeSpacer: PanelInfo[]) {
- // prettier-ignore
- return html`
- ${guard([this._hiddenPanels, this._renderEmptySortable], () =>
- this._renderEmptySortable ? "" : this._renderPanels(beforeSpacer)
- )}
-
- ${this._renderSpacer()}
- ${this._renderHiddenPanels()} `;
- }
-
- private _renderHiddenPanels() {
- return html` ${this._hiddenPanels.length
- ? html`${this._hiddenPanels.map((url) => {
- const panel = this.hass.panels[url];
- if (!panel) {
- return "";
- }
- return html`
-
- ${panel.url_path === this.hass.defaultPanel
- ? this.hass.localize("panel.states")
- : this.hass.localize(`panel.${panel.title}`) ||
- panel.title}
-
-
-
- `;
- })}
- ${this._renderSpacer()}`
- : ""}`;
- }
-
- private _renderSpacer(enabled = true) {
- return enabled
- ? html``
- : html``;
- }
-
- private get _tooltip() {
- return this.shadowRoot!.querySelector(".tooltip")! as HTMLDivElement;
- }
-
- private async _activateEditMode() {
- if (!Sortable) {
- const [sortableImport, sortStylesImport] = await Promise.all([
- import("sortablejs/modular/sortable.core.esm"),
- import("../resources/ha-sortable-style-ha-clickable"),
- ]);
-
- const style = document.createElement("style");
- style.innerHTML = sortStylesImport.sortableStyles.cssText;
- this.shadowRoot!.appendChild(style);
-
- Sortable = sortableImport.Sortable;
- Sortable.mount(sortableImport.OnSpill);
- Sortable.mount(sortableImport.AutoScroll());
- }
-
- await this.updateComplete;
-
- this._createSortable();
- }
-
- private _createSortable() {
- this._sortable = new Sortable(this.shadowRoot!.getElementById("sortable"), {
- animation: 150,
- fallbackClass: "sortable-fallback",
- // fallbackTolerance: 15,
- dataIdAttr: "data-panel",
- handle: "ha-clickable-list-item",
- onSort: async () => {
- this._panelOrder = this._sortable.toArray();
- },
- });
- }
-
- private _deactivateEditMode() {
- this._sortable?.destroy();
- this._sortable = undefined;
- }
-
- private async _hidePanel(ev: Event) {
- ev.preventDefault();
- const panel = (ev.currentTarget as any).panel;
- if (this._hiddenPanels.includes(panel)) {
- return;
- }
- // Make a copy for Memoize
- this._hiddenPanels = [...this._hiddenPanels, panel];
- this._renderEmptySortable = true;
- await this.updateComplete;
- const container = this.shadowRoot!.getElementById("sortable")!;
- while (container.lastElementChild) {
- container.removeChild(container.lastElementChild);
- }
- this._renderEmptySortable = false;
- }
-
- private async _unhidePanel(ev: Event) {
- ev.preventDefault();
- const panel = (ev.currentTarget as any).panel;
- this._hiddenPanels = this._hiddenPanels.filter(
- (hidden) => hidden !== panel
- );
- this._renderEmptySortable = true;
- await this.updateComplete;
- const container = this.shadowRoot!.getElementById("sortable")!;
- while (container.lastElementChild) {
- container.removeChild(container.lastElementChild);
- }
- this._renderEmptySortable = false;
- }
-
- private _itemMouseEnter(ev: MouseEvent) {
- // On keypresses on the listbox, we're going to ignore mouse enter events
- // for 100ms so that we ignore it when pressing down arrow scrolls the
- // sidebar causing the mouse to hover a new icon
- if (
- this.expanded ||
- new Date().getTime() < this._recentKeydownActiveUntil
- ) {
- return;
- }
- if (this._mouseLeaveTimeout) {
- clearTimeout(this._mouseLeaveTimeout);
- this._mouseLeaveTimeout = undefined;
- }
- this._showTooltip(ev.currentTarget);
- }
-
- private _itemMouseLeave() {
- if (this._mouseLeaveTimeout) {
- clearTimeout(this._mouseLeaveTimeout);
- }
- this._mouseLeaveTimeout = window.setTimeout(() => {
- this._hideTooltip();
- }, 500);
- }
-
- private _listboxFocusIn(ev) {
- if (this.expanded || ev.target.nodeName !== "A") {
- return;
- }
- this._showTooltip(ev.target.querySelector("ha-clickable-list-item"));
- }
-
- private _listboxFocusOut() {
- this._hideTooltip();
- }
-
- @eventOptions({
- passive: true,
- })
- private _listboxScroll() {
- // On keypresses on the listbox, we're going to ignore scroll events
- // for 100ms so that if pressing down arrow scrolls the sidebar, the tooltip
- // will not be hidden.
- if (new Date().getTime() < this._recentKeydownActiveUntil) {
- return;
- }
- this._hideTooltip();
- }
-
- private _listboxKeydown() {
- this._recentKeydownActiveUntil = new Date().getTime() + 100;
- }
-
- private _showTooltip(item) {
- if (this._tooltipHideTimeout) {
- clearTimeout(this._tooltipHideTimeout);
- this._tooltipHideTimeout = undefined;
- }
- const tooltip = this._tooltip;
- const listbox = this.shadowRoot!.querySelector("mwc-list")!;
- let top = item.offsetTop + 11;
- if (listbox.contains(item)) {
- top -= listbox.scrollTop;
- }
- tooltip.innerHTML = item.querySelector(".item-text")!.innerHTML;
- tooltip.style.display = "block";
- tooltip.style.top = `${top}px`;
- tooltip.style.left = `${item.offsetLeft + item.clientWidth + 4}px`;
- }
-
- private _hideTooltip() {
- // Delay it a little in case other events are pending processing.
- if (!this._tooltipHideTimeout) {
- this._tooltipHideTimeout = window.setTimeout(() => {
- this._tooltipHideTimeout = undefined;
- this._tooltip.style.display = "none";
- }, 10);
- }
- }
-
- private _handleExternalAppConfiguration(ev: Event) {
- ev.preventDefault();
- this.hass.auth.external!.fireMessage({
- type: "config_screen/show",
- });
- }
-
- private _isUtilityPanel(panel: PanelInfo) {
- return (
- panel.component_name === "developer-tools" ||
- panel.component_name === "config" ||
- panel.component_name === "external-config"
- );
- }
-
- private _renderPanels(panels: PanelInfo[]) {
- return panels
- .filter((panel) => !this._isUtilityPanel(panel))
- .map((panel) =>
- this._renderPanel(
- panel.url_path,
- panel.url_path === this.hass.defaultPanel
- ? panel.title || this.hass.localize("panel.states")
- : this.hass.localize(`panel.${panel.title}`) || panel.title,
- panel.icon,
- panel.url_path === this.hass.defaultPanel && !panel.icon
- ? mdiViewDashboard
- : undefined
- )
- );
- }
-
- private _renderUtilityPanels() {
- const [, afterSpacer] = computePanels(
- this.hass.panels,
- this.hass.defaultPanel,
- this._panelOrder,
- this._hiddenPanels
- );
-
- // prettier-ignore
- return html`
-
- ${afterSpacer.map((panel) =>
- this._renderPanel(
- panel.url_path,
- panel.url_path === this.hass.defaultPanel
- ? panel.title || this.hass.localize("panel.states")
- : this.hass.localize(`panel.${panel.title}`) || panel.title,
- panel.icon,
- panel.url_path === this.hass.defaultPanel && !panel.icon
- ? mdiViewDashboard
- : undefined
- )
- )}
- ${this._renderExternalConfiguration()}
-
-
- `;
- }
-
- private _renderExternalConfiguration() {
- return html`${this._externalConfig && this._externalConfig.hasSettingsScreen
- ? html`
-
-
-
- ${this.hass.localize("ui.sidebar.external_app_configuration")}
-
-
- `
- : ""}`;
- }
-
- private _renderPanel(
- urlPath: string,
- title: string | null,
- icon?: string | null,
- iconPath?: string | null
- ) {
- return html`
-
- ${iconPath
- ? html``
- : html``}
-
- ${title}
-
- ${this.editMode
- ? html`
-
- `
- : ""}
-
- `;
- }
-
- static get styles(): CSSResult[] {
- return [
- haStyleScrollbar,
- css`
- :host {
- height: calc(100% - var(--header-height) - 132px);
- justify-content: space-between;
- flex-direction: column;
- display: block;
- overflow: hidden;
- -ms-user-select: none;
- -webkit-user-select: none;
- -moz-user-select: none;
- border-right: 1px solid var(--divider-color);
- background-color: var(--sidebar-background-color);
- width: 64px;
- }
- :host([expanded]) {
- width: 256px;
- width: calc(256px + env(safe-area-inset-left));
- }
- :host([rtl]) {
- border-right: 0;
- border-left: 1px solid var(--divider-color);
- }
-
- :host([rtl]) .menu {
- padding-left: 8.5px;
- padding-right: calc(8.5px + env(safe-area-inset-right));
- }
- :host([expanded]) .menu {
- width: calc(256px + env(safe-area-inset-left));
- }
- :host([rtl][expanded]) .menu {
- width: calc(256px + env(safe-area-inset-right));
- }
- .menu mwc-icon-button {
- color: var(--sidebar-icon-color);
- }
- :host([expanded]) .menu mwc-icon-button {
- margin-right: 23px;
- }
- :host([expanded][rtl]) .menu mwc-icon-button {
- margin-right: 0px;
- margin-left: 23px;
- }
-
- .title {
- width: 100%;
- display: none;
- }
- :host([narrow]) .title {
- padding: 0 0px;
- }
- :host([expanded]) .title {
- display: initial;
- }
- .title mwc-button {
- width: 100%;
- }
- #sortable,
- .hidden-panel {
- display: none;
- }
-
- div.panels {
- height: 100%;
- position: relative;
- }
-
- mwc-list.ha-scrollbar {
- height: calc(100% - var(--header-height) - 42px);
- --mdc-list-vertical-padding: 4px 0;
- padding: 4px 0;
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
- overflow-x: hidden;
- background: none;
- margin-left: env(safe-area-inset-left);
- }
-
- :host([rtl]) mwc-list {
- margin-left: initial;
- margin-right: env(safe-area-inset-right);
- }
-
- a {
- text-decoration: none;
- color: var(--sidebar-text-color);
- font-weight: 500;
- font-size: 14px;
- position: relative;
- outline: 0;
- border: 1px solid black;
- }
-
- mwc-list-item {
- box-sizing: border-box;
- margin: 4px 8px;
- border-radius: 4px;
- width: 48px;
- --mdc-list-item-graphic-margin: 16px;
- --mdc-list-item-meta-size: 32px;
- }
- :host([expanded]) mwc-list-item {
- width: 240px;
- }
- :host([rtl]) mwc-list-item {
- padding-left: auto;
- padding-right: 12px;
- }
-
- ha-icon[slot="graphic"],
- ha-svg-icon[slot="graphic"] {
- color: var(--sidebar-icon-color);
- }
-
- [slot="graphic"] {
- width: 100%;
- }
-
- .iron-selected mwc-list-item::before,
- a:not(.iron-selected):focus::before {
- border-radius: 4px;
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- pointer-events: none;
- content: "";
- transition: opacity 15ms linear;
- will-change: opacity;
- }
- .iron-selected mwc-list-item::before {
- background-color: var(--sidebar-selected-icon-color);
- opacity: 0.12;
- }
- a:not(.iron-selected):focus::before {
- background-color: currentColor;
- opacity: var(--dark-divider-opacity);
- margin: 4px 8px;
- }
- .iron-selected mwc-list-item:focus::before,
- .iron-selected:focus mwc-list-item::before {
- opacity: 0.2;
- }
-
- .iron-selected mwc-list-item[pressed]:before {
- opacity: 0.37;
- }
-
- mwc-list-item span {
- color: var(--sidebar-text-color);
- font-weight: 500;
- font-size: 14px;
- width: 100%;
- }
-
- a.iron-selected mwc-list-item ha-icon,
- a.iron-selected mwc-list-item ha-svg-icon {
- color: var(--sidebar-selected-icon-color);
- }
-
- a.iron-selected .item-text {
- color: var(--sidebar-selected-text-color);
- }
-
- mwc-list-item mwc-list-item .item-text,
- mwc-list-item .item-text {
- display: none;
- max-width: calc(100% - 56px);
- }
- :host([expanded]) mwc-list-item .item-text {
- display: block;
- }
-
- .divider {
- padding: 10px 0;
- }
- .divider::before {
- display: block;
- height: 100px;
- background-color: var(--divider-color);
- }
-
- .spacer {
- flex: 1;
- pointer-events: none;
- }
-
- .bottom-spacer {
- flex: 1;
- pointer-events: none;
- height: 100%;
- }
-
- .subheader {
- color: var(--sidebar-text-color);
- font-weight: 500;
- font-size: 14px;
- padding: 16px;
- white-space: nowrap;
- }
-
- .dev-tools {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- padding: 0 8px;
- width: 256px;
- box-sizing: border-box;
- }
-
- .dev-tools a {
- color: var(--sidebar-icon-color);
- }
-
- .tooltip {
- display: none;
- position: absolute;
- opacity: 0.9;
- border-radius: 2px;
- white-space: nowrap;
- color: var(--sidebar-background-color);
- background-color: var(--sidebar-text-color);
- padding: 4px;
- font-weight: 500;
- }
-
- :host([rtl]) .menu mwc-icon-button {
- -webkit-transform: scaleX(-1);
- transform: scaleX(-1);
- }
- `,
- ];
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- "ha-sidebar-panel-list": HaSidebarPanelList;
- }
-}
diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts
index 4f6d24bec2..77440fab4b 100644
--- a/src/components/ha-sidebar.ts
+++ b/src/components/ha-sidebar.ts
@@ -1,10 +1,20 @@
-import "./ha-sidebar-panel-list";
import "./ha-clickable-list-item";
-import "@material/mwc-list/mwc-list-item";
-import "@material/mwc-list/mwc-list";
+import "./ha-icon";
+import "./ha-menu-button";
+import "./ha-svg-icon";
+import "./user/ha-user-badge";
import "@material/mwc-button/mwc-button";
import "@material/mwc-icon-button";
-import { mdiBell, mdiMenu, mdiMenuOpen } from "@mdi/js";
+import { List } from "@material/mwc-list";
+import {
+ mdiBell,
+ mdiCellphoneCog,
+ mdiClose,
+ mdiMenu,
+ mdiMenuOpen,
+ mdiPlus,
+ mdiViewDashboard,
+} from "@mdi/js";
import {
css,
CSSResult,
@@ -15,27 +25,144 @@ import {
LitElement,
property,
PropertyValues,
+ query,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
+import { guard } from "lit-html/directives/guard";
+import memoizeOne from "memoize-one";
+import { LocalStorage } from "../common/decorators/local-storage";
import { fireEvent } from "../common/dom/fire_event";
import { computeDomain } from "../common/entity/compute_domain";
+import { compare } from "../common/string/compare";
import { computeRTL } from "../common/util/compute_rtl";
import { ActionHandlerDetail } from "../data/lovelace";
import {
PersistentNotification,
subscribeNotifications,
} from "../data/persistent_notification";
-import { getExternalConfig } from "../external_app/external_config";
+import {
+ ExternalConfig,
+ getExternalConfig,
+} from "../external_app/external_config";
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
import { haStyleScrollbar } from "../resources/styles";
-import type { HomeAssistant } from "../types";
-import "./ha-icon";
-import "./ha-menu-button";
-import "./ha-svg-icon";
-import "./user/ha-user-badge";
+import type { HomeAssistant, PanelInfo } from "../types";
+import { ListItem } from "@material/mwc-list/mwc-list-item";
+
+const SHOW_AFTER_SPACER = ["config", "developer-tools", "hassio"];
const SUPPORT_SCROLL_IF_NEEDED = "scrollIntoViewIfNeeded" in document.body;
+const SORT_VALUE_URL_PATHS = {
+ map: 1,
+ logbook: 2,
+ history: 3,
+ "developer-tools": 9,
+ hassio: 10,
+ config: 11,
+};
+
+const panelSorter = (
+ reverseSort: string[],
+ defaultPanel: string,
+ a: PanelInfo,
+ b: PanelInfo
+) => {
+ const indexA = reverseSort.indexOf(a.url_path);
+ const indexB = reverseSort.indexOf(b.url_path);
+ if (indexA !== indexB) {
+ if (indexA < indexB) {
+ return 1;
+ }
+ return -1;
+ }
+ return defaultPanelSorter(defaultPanel, a, b);
+};
+
+const defaultPanelSorter = (
+ defaultPanel: string,
+ a: PanelInfo,
+ b: PanelInfo
+) => {
+ // Put all the Lovelace at the top.
+ const aLovelace = a.component_name === "lovelace";
+ const bLovelace = b.component_name === "lovelace";
+
+ if (a.url_path === defaultPanel) {
+ return -1;
+ }
+ if (b.url_path === defaultPanel) {
+ return 1;
+ }
+
+ if (aLovelace && bLovelace) {
+ return compare(a.title!, b.title!);
+ }
+ if (aLovelace && !bLovelace) {
+ return -1;
+ }
+ if (bLovelace) {
+ return 1;
+ }
+
+ const aBuiltIn = a.url_path in SORT_VALUE_URL_PATHS;
+ const bBuiltIn = b.url_path in SORT_VALUE_URL_PATHS;
+
+ if (aBuiltIn && bBuiltIn) {
+ return SORT_VALUE_URL_PATHS[a.url_path] - SORT_VALUE_URL_PATHS[b.url_path];
+ }
+ if (aBuiltIn) {
+ return -1;
+ }
+ if (bBuiltIn) {
+ return 1;
+ }
+ // both not built in, sort by title
+ return compare(a.title!, b.title!);
+};
+
+const computePanels = memoizeOne(
+ (
+ panels: HomeAssistant["panels"],
+ defaultPanel: HomeAssistant["defaultPanel"],
+ panelsOrder: string[],
+ hiddenPanels: string[]
+ ): [PanelInfo[], PanelInfo[]] => {
+ if (!panels) {
+ return [[], []];
+ }
+
+ const beforeSpacer: PanelInfo[] = [];
+ const afterSpacer: PanelInfo[] = [];
+
+ Object.values(panels).forEach((panel) => {
+ if (
+ hiddenPanels.includes(panel.url_path) ||
+ (!panel.title && panel.url_path !== defaultPanel)
+ ) {
+ return;
+ }
+ (SHOW_AFTER_SPACER.includes(panel.url_path)
+ ? afterSpacer
+ : beforeSpacer
+ ).push(panel);
+ });
+
+ const reverseSort = [...panelsOrder].reverse();
+
+ beforeSpacer.sort((a, b) => panelSorter(reverseSort, defaultPanel, a, b));
+ afterSpacer.sort((a, b) => panelSorter(reverseSort, defaultPanel, a, b));
+
+ return [beforeSpacer, afterSpacer];
+ }
+);
+
+const isListItem = (element: Element): element is ListItem =>
+ element.hasAttribute("mwc-list-item");
+
+const isNodeElement = (node: Node): node is Element =>
+ node.nodeType === Node.ELEMENT_NODE;
+
let Sortable;
@customElement("ha-sidebar")
@@ -50,45 +177,51 @@ class HaSidebar extends LitElement {
@property({ type: Boolean }) public editMode = false;
+ @internalProperty() private _externalConfig?: ExternalConfig;
+
@internalProperty() private _notifications?: PersistentNotification[];
// property used only in css
// @ts-ignore
@property({ type: Boolean, reflect: true }) public rtl = false;
+ @internalProperty() private _renderEmptySortable = false;
+
+ @query("mwc-list.ha-scrollbar", false)
+ private _standardPanelList?: List;
+
+ @query("mwc-list.utility-panels", false)
+ private _utilityPanelList?: List;
+
private _mouseLeaveTimeout?: number;
private _tooltipHideTimeout?: number;
private _recentKeydownActiveUntil = 0;
+ // @ts-ignore
+ @LocalStorage("sidebarPanelOrder", true, {
+ attribute: false,
+ })
+ private _panelOrder: string[] = [];
+
+ // @ts-ignore
+ @LocalStorage("sidebarHiddenPanels", true, {
+ attribute: false,
+ })
+ private _hiddenPanels: string[] = [];
+
private _sortable?;
protected render() {
if (!this.hass) {
return html``;
}
- const debug = false;
-
- return debug
- ? html`
-
- `
- : this.render2();
- }
-
- protected render2() {
- if (!this.hass) {
- return html``;
- }
// prettier-ignore
return html`
- ${this._renderHeader()} ${this._renderAllPanels()}
+ ${this._renderHeader()}
+ ${this._renderAllPanels()}
{});
+ getExternalConfig(this.hass.auth.external).then((conf) => {
+ this._externalConfig = conf;
+ });
}
subscribeNotifications(this.hass.connection, (notifications) => {
this._notifications = notifications;
@@ -213,19 +348,120 @@ class HaSidebar extends LitElement {
}
private _renderAllPanels() {
+ // prettier-ignore
return html`
-
+ ${this._renderNormalPanels()}
+ ${this._renderUtilityPanels()}
+ `;
+ }
+
+ private _renderNormalPanels() {
+ const [beforeSpacer] = computePanels(
+ this.hass.panels,
+ this.hass.defaultPanel,
+ this._panelOrder,
+ this._hiddenPanels
+ );
+
+ // prettier-ignore
+ return html`
+
+ ${this.editMode
+ ? this._renderPanelsEdit(beforeSpacer)
+ : this._renderPanels(beforeSpacer)}
+
`;
}
- private _renderSpacer(enabled = true) {
- return enabled
- ? html``
- : html``;
+ private _renderPanelsEdit(beforeSpacer: PanelInfo[]) {
+ // prettier-ignore
+ return html`
+ ${guard([this._hiddenPanels, this._renderEmptySortable], () =>
+ this._renderEmptySortable ? "" : this._renderPanels(beforeSpacer)
+ )}
+
+ ${this._renderSpacer()}
+ ${this._renderHiddenPanels()} `;
+ }
+
+ private _renderHiddenPanels() {
+ return html` ${this._hiddenPanels.length
+ ? html`${this._hiddenPanels.map((url) => {
+ const panel = this.hass.panels[url];
+ if (!panel) {
+ return "";
+ }
+ return html`
+
+ ${panel.url_path === this.hass.defaultPanel
+ ? this.hass.localize("panel.states")
+ : this.hass.localize(`panel.${panel.title}`) ||
+ panel.title}
+
+
+
+ `;
+ })}
+ ${this._renderSpacer()}`
+ : ""}`;
+ }
+
+ private _renderUtilityPanels() {
+ const [, afterSpacer] = computePanels(
+ this.hass.panels,
+ this.hass.defaultPanel,
+ this._panelOrder,
+ this._hiddenPanels
+ );
+
+ // prettier-ignore
+ return html`
+
+ ${afterSpacer.map((panel) =>
+ this._renderPanel(
+ panel.url_path,
+ panel.url_path === this.hass.defaultPanel
+ ? panel.title || this.hass.localize("panel.states")
+ : this.hass.localize(`panel.${panel.title}`) || panel.title,
+ panel.icon,
+ panel.url_path === this.hass.defaultPanel && !panel.icon
+ ? mdiViewDashboard
+ : undefined
+ )
+ )}
+ ${this._renderExternalConfiguration()}
+
+
+ `;
+ }
+
+ private _renderSpacer() {
+ return html``;
}
private _renderNotifications() {
@@ -273,7 +509,6 @@ class HaSidebar extends LitElement {
return html` `;
}
+ private _renderExternalConfiguration() {
+ return html`${this._externalConfig && this._externalConfig.hasSettingsScreen
+ ? html`
+
+
+
+ ${this.hass.localize("ui.sidebar.external_app_configuration")}
+
+
+ `
+ : ""}`;
+ }
+
private get _tooltip() {
return this.shadowRoot!.querySelector(".tooltip")! as HTMLDivElement;
}
@@ -334,10 +593,11 @@ class HaSidebar extends LitElement {
this._sortable = new Sortable(this.shadowRoot!.getElementById("sortable"), {
animation: 150,
fallbackClass: "sortable-fallback",
- // fallbackTolerance: 15,
dataIdAttr: "data-panel",
handle: "ha-clickable-list-item",
- onSort: async () => {},
+ onSort: async () => {
+ this._panelOrder = this._sortable.toArray();
+ },
});
}
@@ -350,6 +610,38 @@ class HaSidebar extends LitElement {
fireEvent(this, "hass-edit-sidebar", { editMode: false });
}
+ private async _hidePanel(ev: Event) {
+ ev.preventDefault();
+ const panel = (ev.currentTarget as any).panel;
+ if (this._hiddenPanels.includes(panel)) {
+ return;
+ }
+ // Make a copy for Memoize
+ this._hiddenPanels = [...this._hiddenPanels, panel];
+ this._renderEmptySortable = true;
+ await this.updateComplete;
+ const container = this.shadowRoot!.getElementById("sortable")!;
+ while (container.lastElementChild) {
+ container.removeChild(container.lastElementChild);
+ }
+ this._renderEmptySortable = false;
+ }
+
+ private async _unhidePanel(ev: Event) {
+ ev.preventDefault();
+ const panel = (ev.currentTarget as any).panel;
+ this._hiddenPanels = this._hiddenPanels.filter(
+ (hidden) => hidden !== panel
+ );
+ this._renderEmptySortable = true;
+ await this.updateComplete;
+ const container = this.shadowRoot!.getElementById("sortable")!;
+ while (container.lastElementChild) {
+ container.removeChild(container.lastElementChild);
+ }
+ this._renderEmptySortable = false;
+ }
+
private _itemMouseEnter(ev: MouseEvent) {
// On keypresses on the listbox, we're going to ignore mouse enter events
// for 100ms so that we ignore it when pressing down arrow scrolls the
@@ -400,10 +692,64 @@ class HaSidebar extends LitElement {
this._hideTooltip();
}
- private _listboxKeydown() {
+ private _getIndexOfTarget(evt: Event): number {
+ const listbox = evt.currentTarget as List;
+ const elements = listbox.items;
+ const path = evt.composedPath();
+
+ for (const pathItem of path as Node[]) {
+ let index = -1;
+ if (isNodeElement(pathItem) && isListItem(pathItem)) {
+ index = elements.indexOf(pathItem);
+ }
+
+ if (index !== -1) {
+ return index;
+ }
+ }
+
+ return -1;
+ }
+
+ private _listboxKeydown(ev: KeyboardEvent) {
+ const [beforeSpacer] = computePanels(
+ this.hass.panels,
+ this.hass.defaultPanel,
+ this._panelOrder,
+ this._hiddenPanels
+ );
+
+ const curIndex = this._getIndexOfTarget(ev);
+ const curList = ev.currentTarget as List;
+
+ if (ev.code === "ArrowDown" && curList === this._standardPanelList) {
+ const isLastItem = curIndex === beforeSpacer.length - 1;
+
+ if (isLastItem) {
+ this._setFocusFirstListItem();
+ }
+ } else if (ev.code === "ArrowUp" && curList === this._utilityPanelList) {
+ const isFirstItem = curIndex === 0;
+
+ if (isFirstItem) {
+ this._setFocusLastListItem();
+ }
+ } else if (ev.code === "Enter") {
+ (ev.target as ListItem)?.shadowRoot?.querySelector("a")?.click();
+ }
+
this._recentKeydownActiveUntil = new Date().getTime() + 100;
}
+ private _setFocusFirstListItem() {
+ this._utilityPanelList?.focusItemAtIndex(0);
+ }
+
+ private _setFocusLastListItem() {
+ const list = this._standardPanelList;
+ list?.focusItemAtIndex(list?.items.length - 1);
+ }
+
private _showTooltip(item) {
if (this._tooltipHideTimeout) {
clearTimeout(this._tooltipHideTimeout);
@@ -435,6 +781,13 @@ class HaSidebar extends LitElement {
fireEvent(this, "hass-show-notifications");
}
+ private _handleExternalAppConfiguration(ev: Event) {
+ ev.preventDefault();
+ this.hass.auth.external!.fireMessage({
+ type: "config_screen/show",
+ });
+ }
+
private _toggleSidebar(ev: CustomEvent) {
if (ev.detail.action !== "tap") {
return;
@@ -442,12 +795,60 @@ class HaSidebar extends LitElement {
fireEvent(this, "hass-toggle-menu");
}
+ private _renderPanels(panels: PanelInfo[]) {
+ return panels.map((panel) =>
+ this._renderPanel(
+ panel.url_path,
+ panel.url_path === this.hass.defaultPanel
+ ? panel.title || this.hass.localize("panel.states")
+ : this.hass.localize(`panel.${panel.title}`) || panel.title,
+ panel.icon,
+ panel.url_path === this.hass.defaultPanel && !panel.icon
+ ? mdiViewDashboard
+ : undefined
+ )
+ );
+ }
+
+ private _renderPanel(
+ urlPath: string,
+ title: string | null,
+ icon?: string | null,
+ iconPath?: string | null
+ ) {
+ return html`
+
+ ${iconPath
+ ? html``
+ : html``}
+
+ ${title}
+ ${this.editMode
+ ? html`
+
+ `
+ : ""}
+
+ `;
+ }
+
static get styles(): CSSResult[] {
return [
haStyleScrollbar,
css`
:host {
- /* height: calc(100% - var(--header-height)); */
height: 100%;
display: block;
overflow: hidden;
@@ -523,26 +924,30 @@ class HaSidebar extends LitElement {
display: none;
}
- /* mwc-list.ha-scrollbar {
- height: 100%;
+ mwc-list.ha-scrollbar {
--mdc-list-vertical-padding: 4px 0;
padding: 4px 0;
display: flex;
+ justify-content: space-between;
flex-direction: column;
box-sizing: border-box;
- height: calc(100% - var(--header-height) - 350px);
+ height: calc(100% - var(--header-height) - 232px);
height: calc(
- 100% - var(--header-height) - 350px - env(safe-area-inset-bottom)
+ 100% - var(--header-height) - 232px - env(safe-area-inset-bottom)
);
overflow-x: hidden;
background: none;
margin-left: env(safe-area-inset-left);
- background-color: blue;
- } */
-
+ -ms-user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ border-right: 1px solid var(--divider-color);
+ background-color: var(--sidebar-background-color);
+ width: 64px;
+ }
:host([rtl]) mwc-list {
- margin-left: initial;
- margin-right: env(safe-area-inset-right);
+ border-right: 0;
+ border-left: 1px solid var(--divider-color);
}
a {
@@ -552,7 +957,6 @@ class HaSidebar extends LitElement {
font-size: 14px;
position: relative;
outline: 0;
- border: 1px solid black;
}
mwc-list-item {
@@ -566,6 +970,7 @@ class HaSidebar extends LitElement {
:host([expanded]) paper-icon-item {
width: 248px;
}
+
:host([rtl]) mwc-list-item {
padding-left: auto;
padding-right: 12px;
@@ -580,6 +985,11 @@ class HaSidebar extends LitElement {
width: 100%;
}
+ :host([rtl]) mwc-list {
+ margin-left: initial;
+ margin-right: env(safe-area-inset-right);
+ }
+
.iron-selected mwc-list-item::before,
a:not(.iron-selected):focus::before {
border-radius: 4px;
@@ -618,6 +1028,8 @@ class HaSidebar extends LitElement {
width: 100%;
}
+ a.iron-selected ha-icon,
+ a.iron-selected ha-svg-icon,
a.iron-selected mwc-list-item ha-icon,
a.iron-selected mwc-list-item ha-svg-icon {
color: var(--sidebar-selected-icon-color);
@@ -636,14 +1048,6 @@ class HaSidebar extends LitElement {
display: block;
}
- .divider {
- padding: 10px 0;
- }
- .divider::before {
- display: block;
- height: 100px;
- background-color: var(--divider-color);
- }
.notifications-container {
display: flex;
margin-left: env(safe-area-inset-left);
@@ -680,7 +1084,7 @@ class HaSidebar extends LitElement {
}
.notification-badge {
- /* min-width: 20px;
+ min-width: 20px;
box-sizing: border-box;
border-radius: 50%;
font-weight: 400;
@@ -689,7 +1093,7 @@ class HaSidebar extends LitElement {
text-align: center;
padding: 0px 4px;
color: var(--text-accent-color, var(--text-primary-color));
- font-size: 14px; */
+ font-size: 14px;
}
ha-svg-icon + .notification-badge {
position: absolute;
@@ -703,12 +1107,6 @@ class HaSidebar extends LitElement {
pointer-events: none;
}
- .bottom-spacer {
- flex: 1;
- pointer-events: none;
- height: 100%;
- }
-
.subheader {
color: var(--sidebar-text-color);
font-weight: 500;
diff --git a/src/layouts/home-assistant-main.ts b/src/layouts/home-assistant-main.ts
index c85e6eb39d..f90e26b861 100644
--- a/src/layouts/home-assistant-main.ts
+++ b/src/layouts/home-assistant-main.ts
@@ -1,4 +1,3 @@
-import "../components/ha-sidebar-combined";
import "@polymer/app-layout/app-drawer-layout/app-drawer-layout";
import type { AppDrawerLayoutElement } from "@polymer/app-layout/app-drawer-layout/app-drawer-layout";
import "@polymer/app-layout/app-drawer/app-drawer";
@@ -86,13 +85,13 @@ class HomeAssistantMain extends LitElement {
.persistent=${!this.narrow &&
this.hass.dockedSidebar !== "always_hidden"}
>
-
+ >