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"} > - + >