diff --git a/package.json b/package.json index 204340c43269..4be38cc5707c 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "element-internals-polyfill": "1.3.10", "fuse.js": "7.0.0", "google-timezones-json": "1.2.0", - "hls.js": "1.5.2", + "hls.js": "1.5.3", "home-assistant-js-websocket": "9.1.0", "idb-keyval": "6.2.1", "intl-messageformat": "10.5.11", @@ -183,8 +183,8 @@ "@types/tar": "6.1.11", "@types/ua-parser-js": "0.7.39", "@types/webspeechapi": "0.0.29", - "@typescript-eslint/eslint-plugin": "6.19.1", - "@typescript-eslint/parser": "6.19.1", + "@typescript-eslint/eslint-plugin": "6.20.0", + "@typescript-eslint/parser": "6.20.0", "@web/dev-server": "0.1.38", "@web/dev-server-rollup": "0.4.1", "babel-loader": "9.1.3", @@ -199,7 +199,7 @@ "eslint-plugin-disable": "2.0.3", "eslint-plugin-import": "2.29.1", "eslint-plugin-lit": "1.11.0", - "eslint-plugin-lit-a11y": "4.1.1", + "eslint-plugin-lit-a11y": "4.1.2", "eslint-plugin-unused-imports": "3.0.0", "eslint-plugin-wc": "2.0.4", "fancy-log": "2.0.0", @@ -212,7 +212,7 @@ "gulp-rename": "2.0.0", "gulp-zopfli-green": "6.0.1", "html-minifier-terser": "7.2.0", - "husky": "9.0.6", + "husky": "9.0.7", "instant-mocha": "1.5.2", "jszip": "3.10.1", "lint-staged": "15.2.0", diff --git a/pyproject.toml b/pyproject.toml index c1f29c43b5ef..03402285c70b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20240131.0" +version = "20240202.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" diff --git a/src/common/util/compute_rtl.ts b/src/common/util/compute_rtl.ts index 0c53e374e902..b0b8b20cf555 100644 --- a/src/common/util/compute_rtl.ts +++ b/src/common/util/compute_rtl.ts @@ -38,4 +38,8 @@ export function setDirectionStyles(direction: string, element: LitElement) { "--margin-title", direction === "ltr" ? "var(--margin-title-ltr)" : "var(--margin-title-rtl)" ); + element.style.setProperty( + "--scale-direction", + direction === "ltr" ? "1" : "-1" + ); } diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index 36ddc2915c00..aa4a90fef8d6 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -10,7 +10,6 @@ import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { styleMap } from "lit/directives/style-map"; import { clamp } from "../../common/number/clamp"; -import { computeRTL } from "../../common/util/compute_rtl"; import { HomeAssistant } from "../../types"; import { debounce } from "../../common/util/debounce"; @@ -212,12 +211,10 @@ export class HaChartBase extends LitElement { height: `${ this.height ?? this._chartHeight ?? this.clientWidth / 2 }px`, - "padding-left": `${ - computeRTL(this.hass) ? 0 : this._paddingYAxisInternal - }px`, - "padding-right": `${ - computeRTL(this.hass) ? this._paddingYAxisInternal : 0 - }px`, + "padding-left": `${this._paddingYAxisInternal}`, + "padding-right": 0, + "padding-inline-start": `${this._paddingYAxisInternal}`, + "padding-inline-end": 0, })} > @@ -433,14 +430,6 @@ export class HaChartBase extends LitElement { .chartTooltip .bullet { align-self: baseline; } - :host([rtl]) .chartLegend .bullet, - :host([rtl]) .chartTooltip .bullet { - margin-right: inherit; - margin-left: 6px; - margin-inline-end: inherit; - margin-inline-start: 6px; - direction: var(--direction); - } .chartTooltip { padding: 8px; font-size: 90%; @@ -449,12 +438,13 @@ export class HaChartBase extends LitElement { color: white; border-radius: 4px; pointer-events: none; - z-index: 1000; + z-index: 1; + -ms-user-select: none; + -webkit-user-select: none; + -moz-user-select: none; width: 200px; box-sizing: border-box; - } - :host([rtl]) .chartTooltip { - direction: rtl; + direction: var(--direction); } .chartLegend ul, .chartTooltip ul { diff --git a/src/components/chart/state-history-chart-line.ts b/src/components/chart/state-history-chart-line.ts index 78ec773719c3..5f6286ecef61 100644 --- a/src/components/chart/state-history-chart-line.ts +++ b/src/components/chart/state-history-chart-line.ts @@ -220,7 +220,12 @@ export class StateHistoryChartLine extends LitElement { // @ts-expect-error locale: numberFormatToLocale(this.hass.locale), onClick: (e: any) => { - if (!this.clickForMoreInfo) { + if ( + !this.clickForMoreInfo || + !(e.native instanceof MouseEvent) || + (e.native instanceof PointerEvent && + e.native.pointerType !== "mouse") + ) { return; } diff --git a/src/components/chart/state-history-chart-timeline.ts b/src/components/chart/state-history-chart-timeline.ts index 79b51be40ed3..8bfb0fef8b7f 100644 --- a/src/components/chart/state-history-chart-timeline.ts +++ b/src/components/chart/state-history-chart-timeline.ts @@ -224,7 +224,11 @@ export class StateHistoryChartTimeline extends LitElement { // @ts-expect-error locale: numberFormatToLocale(this.hass.locale), onClick: (e: any) => { - if (!this.clickForMoreInfo) { + if ( + !this.clickForMoreInfo || + !(e.native instanceof MouseEvent) || + (e.native instanceof PointerEvent && e.native.pointerType !== "mouse") + ) { return; } diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index 7f3d2f61b89b..c4805144b165 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -688,14 +688,11 @@ export class HaDataTable extends LitElement { padding-left: 16px; /* @noflip */ padding-right: 0; - width: 60px; - } - :host([dir="rtl"]) .mdc-data-table__header-cell--checkbox, - :host([dir="rtl"]) .mdc-data-table__cell--checkbox { /* @noflip */ - padding-left: 0; + padding-inline-start: 16px; /* @noflip */ - padding-right: 16px; + padding-inline-end: initial; + width: 60px; } .mdc-data-table__table { @@ -723,11 +720,7 @@ export class HaDataTable extends LitElement { } .mdc-data-table__cell--numeric { - text-align: right; - } - :host([dir="rtl"]) .mdc-data-table__cell--numeric { - /* @noflip */ - text-align: left; + text-align: var(--float-end); } .mdc-data-table__cell--icon { @@ -753,15 +746,7 @@ export class HaDataTable extends LitElement { .mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:not( .not-sorted ) { - text-align: left; - } - :host([dir="rtl"]) - .mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:hover, - :host([dir="rtl"]) - .mdc-data-table__header-cell.sortable.mdc-data-table__header-cell--icon:not( - .not-sorted - ) { - text-align: right; + text-align: var(--float-start); } .mdc-data-table__cell--icon:first-child img, @@ -771,27 +756,14 @@ export class HaDataTable extends LitElement { .mdc-data-table__cell--icon:first-child ha-domain-icon, .mdc-data-table__cell--icon:first-child ha-service-icon { margin-left: 8px; - } - :host([dir="rtl"]) .mdc-data-table__cell--icon:first-child ha-icon, - :host([dir="rtl"]) - .mdc-data-table__cell--icon:first-child - ha-state-icon, - :host([dir="rtl"]) - .mdc-data-table__cell--icon:first-child - ha-svg-icon - :host([dir="rtl"]) - .mdc-data-table__cell--icon:first-child - img { - margin-left: auto; - margin-right: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; } .mdc-data-table__cell--icon:first-child state-badge { margin-right: -8px; - } - :host([dir="rtl"]) .mdc-data-table__cell--icon:first-child state-badge { - margin-right: auto; - margin-left: -8px; + margin-inline-end: -8px; + margin-inline-start: initial; } .mdc-data-table__cell--overflow-menu, @@ -824,15 +796,8 @@ export class HaDataTable extends LitElement { .mdc-data-table__header-cell--icon-button:first-child, .mdc-data-table__cell--icon-button:first-child { padding-left: 16px; - } - :host([dir="rtl"]) - .mdc-data-table__header-cell--overflow-menu:first-child, - :host([dir="rtl"]) .mdc-data-table__cell--overflow-menu:first-child, - :host([dir="rtl"]) - .mdc-data-table__header-cell--overflow-menu:first-child, - :host([dir="rtl"]) .mdc-data-table__cell--overflow-menu:first-child { - padding-left: 8px; - padding-right: 16px; + padding-inline-start: 16px; + padding-inline-end: initial; // 8px? } .mdc-data-table__cell--overflow-menu:last-child, @@ -840,14 +805,8 @@ export class HaDataTable extends LitElement { .mdc-data-table__header-cell--icon-button:last-child, .mdc-data-table__cell--icon-button:last-child { padding-right: 16px; - } - :host([dir="rtl"]) - .mdc-data-table__header-cell--overflow-menu:last-child, - :host([dir="rtl"]) .mdc-data-table__cell--overflow-menu:last-child, - :host([dir="rtl"]) .mdc-data-table__header-cell--icon-button:last-child, - :host([dir="rtl"]) .mdc-data-table__cell--icon-button:last-child { - padding-right: 8px; - padding-left: 16px; + padding-inline-end: 16px; + padding-inline-start: initial; // 8px? } .mdc-data-table__cell--overflow-menu, .mdc-data-table__header-cell--overflow-menu { @@ -867,28 +826,15 @@ export class HaDataTable extends LitElement { letter-spacing: 0.0071428571em; text-decoration: inherit; text-transform: inherit; - text-align: left; - } - :host([dir="rtl"]) .mdc-data-table__header-cell { - /* @noflip */ - text-align: right; + text-align: var(--float-start); } .mdc-data-table__header-cell--numeric { - text-align: right; + text-align: var(--float-end); } .mdc-data-table__header-cell--numeric.sortable:hover, .mdc-data-table__header-cell--numeric.sortable:not(.not-sorted) { - text-align: left; - } - :host([dir="rtl"]) .mdc-data-table__header-cell--numeric { - /* @noflip */ - text-align: left; - } - :host([dir="rtl"]) .mdc-data-table__header-cell--numeric.sortable:hover, - :host([dir="rtl"]) - .mdc-data-table__header-cell--numeric.sortable:not(.not-sorted) { - text-align: right; + text-align: var(--float-start); } /* custom from here */ @@ -909,20 +855,15 @@ export class HaDataTable extends LitElement { .mdc-data-table__header-cell span { position: relative; left: 0px; - } - :host([dir="rtl"]) .mdc-data-table__header-cell span { - left: auto; - right: 0px; + inset-inline-start: 0px; + inset-inline-end: initial; } .mdc-data-table__header-cell.sortable { cursor: pointer; } .mdc-data-table__header-cell > * { - transition: left 0.2s ease; - } - :host([dir="rtl"]) .mdc-data-table__header-cell > * { - transition: right 0.2s ease; + transition: var(--float-start) 0.2s ease; } .mdc-data-table__header-cell ha-svg-icon { top: -3px; @@ -930,35 +871,20 @@ export class HaDataTable extends LitElement { } .mdc-data-table__header-cell.not-sorted ha-svg-icon { left: -20px; - } - :host([dir="rtl"]) .mdc-data-table__header-cell.not-sorted ha-svg-icon { - right: -20px; + inset-inline-start: -20px; + inset-inline-end: initial; } .mdc-data-table__header-cell.sortable:not(.not-sorted) span, .mdc-data-table__header-cell.sortable.not-sorted:hover span { left: 24px; - } - :host([dir="rtl"]) - .mdc-data-table__header-cell.sortable:not(.not-sorted) - span, - :host([dir="rtl"]) - .mdc-data-table__header-cell.sortable.not-sorted:hover - span { - left: auto; - right: 24px; + inset-inline-start: 24px; + inset-inline-end: initial; } .mdc-data-table__header-cell.sortable:not(.not-sorted) ha-svg-icon, .mdc-data-table__header-cell.sortable:hover.not-sorted ha-svg-icon { left: 12px; - } - :host([dir="rtl"]) - .mdc-data-table__header-cell.sortable:not(.not-sorted) - ha-svg-icon, - :host([dir="rtl"]) - .mdc-data-table__header-cell.sortable:hover.not-sorted - ha-svg-icon { - left: auto; - right: 12px; + inset-inline-start: 12px; + inset-inline-end: initial; } .table-header { border-bottom: 1px solid var(--divider-color); @@ -966,6 +892,8 @@ export class HaDataTable extends LitElement { search-input { display: block; flex: 1; + --mdc-text-field-fill-color: var(--sidebar-background-color); + --mdc-text-field-idle-line-color: transparent; } slot[name="header"] { display: block; diff --git a/src/components/date-range-picker.ts b/src/components/date-range-picker.ts index 237576a89ce3..5ebb0c2a978b 100644 --- a/src/components/date-range-picker.ts +++ b/src/components/date-range-picker.ts @@ -9,6 +9,7 @@ import { localizeWeekdays, localizeMonths, } from "../common/datetime/localize_date"; +import { mainWindow } from "../common/dom/get_main_window"; // Set the current date to the left picker instead of the right picker because the right is hidden const CustomDateRangePicker = Vue.extend({ @@ -157,7 +158,7 @@ class DateRangePickerElement extends WrappedElement { min-width: initial !important; max-height: var(--date-range-picker-max-height); overflow-y: auto; - } + } .daterangepicker:before { display: none; } @@ -267,15 +268,37 @@ class DateRangePickerElement extends WrappedElement { .calendar-table { padding: 0 !important; } - .daterangepicker.ltr { + .calendar-time { direction: ltr; - text-align: left; + } + .daterangepicker.ltr { + direction: var(--direction); + text-align: var(--float-start); } .vue-daterange-picker{ min-width: unset !important; display: block !important; } `; + if (mainWindow.document.dir === "rtl") { + style.innerHTML += ` + .daterangepicker .calendar-table .next span { + transform: rotate(135deg); + -webkit-transform: rotate(135deg); + } + .daterangepicker .calendar-table .prev span { + transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); + } + .daterangepicker td.start-date { + border-radius: 0 50% 50% 0; + } + .daterangepicker td.end-date { + border-radius: 50% 0 0 50%; + } + `; + } + const shadowRoot = this.shadowRoot!; shadowRoot.appendChild(style); // Stop click events from reaching the document, otherwise it will close the picker immediately. diff --git a/src/components/entity/state-info.ts b/src/components/entity/state-info.ts index db631e4496f3..18bc50068c13 100644 --- a/src/components/entity/state-info.ts +++ b/src/components/entity/state-info.ts @@ -3,7 +3,6 @@ import type { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { computeStateName } from "../../common/entity/compute_state_name"; -import { computeRTL } from "../../common/util/compute_rtl"; import type { HomeAssistant } from "../../types"; import "../ha-relative-time"; import "./state-badge"; @@ -16,9 +15,6 @@ class StateInfo extends LitElement { @property({ type: Boolean }) public inDialog = false; - // property used only in CSS - @property({ type: Boolean, reflect: true }) public rtl = false; - @property() public color?: string; protected render() { @@ -79,18 +75,6 @@ class StateInfo extends LitElement { `; } - protected updated(changedProps) { - super.updated(changedProps); - if (!changedProps.has("hass")) { - return; - } - - const oldHass = changedProps.get("hass") as HomeAssistant | undefined; - if (!oldHass || oldHass.locale !== this.hass.locale) { - this.rtl = computeRTL(this.hass); - } - } - static get styles(): CSSResultGroup { return css` :host { @@ -106,17 +90,14 @@ class StateInfo extends LitElement { .info { margin-left: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; display: flex; flex-direction: column; justify-content: center; height: 100%; min-width: 0; - } - - :host([rtl]) .info { - margin-right: 8px; - margin-left: 0; - text-align: right; + text-align: var(--float-start); } .name { diff --git a/src/components/ha-button-toggle-group.ts b/src/components/ha-button-toggle-group.ts index e4dd2e9524dd..9fc426a99176 100644 --- a/src/components/ha-button-toggle-group.ts +++ b/src/components/ha-button-toggle-group.ts @@ -69,6 +69,7 @@ export class HaButtonToggleGroup extends LitElement { display: flex; --mdc-icon-button-size: var(--button-toggle-size, 36px); --mdc-icon-size: var(--button-toggle-icon-size, 20px); + direction: ltr; } mwc-button { --mdc-shape-small: 0; @@ -119,19 +120,6 @@ export class HaButtonToggleGroup extends LitElement { --mdc-shape-small: 4px; border-right-width: 1px; } - - :host([dir="rtl"]) ha-icon-button:first-child, - :host([dir="rtl"]) mwc-button:first-child { - border-radius: 0 4px 4px 0; - border-right-width: 1px; - --mdc-shape-small: 0 4px 4px 0; - --mdc-button-outline-width: 1px; - } - :host([dir="rtl"]) ha-icon-button:last-child, - :host([dir="rtl"]) mwc-button:last-child { - --mdc-shape-small: 4px 0 0 4px; - border-radius: 4px 0 0 4px; - } `; } } diff --git a/src/components/ha-date-range-picker.ts b/src/components/ha-date-range-picker.ts index 6984dc13fc61..1bb96721fc0d 100644 --- a/src/components/ha-date-range-picker.ts +++ b/src/components/ha-date-range-picker.ts @@ -32,7 +32,6 @@ import { firstWeekdayIndex } from "../common/datetime/first_weekday"; import { formatDate } from "../common/datetime/format_date"; import { formatDateTime } from "../common/datetime/format_date_time"; import { useAmPm } from "../common/datetime/use_am_pm"; -import { computeRTLDirection } from "../common/util/compute_rtl"; import { HomeAssistant } from "../types"; import "./date-range-picker"; import "./ha-icon-button"; @@ -65,8 +64,6 @@ export class HaDateRangePicker extends LitElement { @state() private _hour24format = false; - @state() private _rtlDirection = "ltr"; - @property({ type: Boolean }) public extendedPresets = false; @property() public openingDirection?: "right" | "left" | "center" | "inline"; @@ -236,7 +233,6 @@ export class HaDateRangePicker extends LitElement { const oldHass = changedProps.get("hass") as HomeAssistant | undefined; if (!oldHass || oldHass.locale !== this.hass.locale) { this._hour24format = !useAmPm(this.hass.locale); - this._rtlDirection = computeRTLDirection(this.hass); } } } @@ -306,11 +302,7 @@ export class HaDateRangePicker extends LitElement { >`} ${this.ranges !== false && (this.ranges || this._ranges) - ? html`
+ ? html`
${Object.keys(this.ranges || this._ranges!).map( (name) => html`${name}` diff --git a/src/components/ha-drawer.ts b/src/components/ha-drawer.ts index 37461fb1dfaf..45a54623d56c 100644 --- a/src/components/ha-drawer.ts +++ b/src/components/ha-drawer.ts @@ -3,6 +3,7 @@ import { styles } from "@material/mwc-drawer/mwc-drawer.css"; import { css, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; +import { mainWindow } from "../common/dom/get_main_window"; const blockingElements = (document as any).$blockingElements; @@ -12,6 +13,8 @@ export class HaDrawer extends DrawerBase { private _mc?: HammerManager; + private _rtlStyle?: HTMLElement; + protected createAdapter() { return { ...super.createAdapter(), @@ -31,8 +34,26 @@ export class HaDrawer extends DrawerBase { protected updated(changedProps: PropertyValues) { super.updated(changedProps); if (changedProps.has("direction")) { - this.mdcRoot.dir = this.direction; + if (mainWindow.document.dir === "rtl") { + this._rtlStyle = document.createElement("style"); + this._rtlStyle.innerHTML = ` + .mdc-drawer--animate { + transform: translateX(100%); + } + .mdc-drawer--opening { + transform: translateX(0); + } + .mdc-drawer--closing { + transform: translateX(100%); + } + `; + + this.shadowRoot!.appendChild(this._rtlStyle); + } else if (this._rtlStyle) { + this.shadowRoot!.removeChild(this._rtlStyle); + } } + if (changedProps.has("open") && this.open && this.type === "modal") { this._setupSwipe(); } else if (this._mc) { @@ -66,6 +87,8 @@ export class HaDrawer extends DrawerBase { position: fixed; top: 0; border-color: var(--divider-color, rgba(0, 0, 0, 0.12)); + inset-inline-start: 0 !important; + inset-inline-end: initial !important; } .mdc-drawer.mdc-drawer--modal.mdc-drawer--open { z-index: 200; diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index ba199581707a..d36a6a646db0 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -38,7 +38,6 @@ import { storage } from "../common/decorators/storage"; import { fireEvent } from "../common/dom/fire_event"; import { toggleAttribute } from "../common/dom/toggle_attribute"; import { stringCompare } from "../common/string/compare"; -import { computeRTL } from "../common/util/compute_rtl"; import { throttle } from "../common/util/throttle"; import { ActionHandlerDetail } from "../data/lovelace/action_handler"; import { @@ -307,16 +306,12 @@ class HaSidebar extends SubscribeMixin(LitElement) { return; } - const oldHass = changedProps.get("hass") as HomeAssistant | undefined; - if (!oldHass || oldHass.locale !== this.hass.locale) { - toggleAttribute(this, "rtl", computeRTL(this.hass)); - } - this._calculateCounts(); if (!SUPPORT_SCROLL_IF_NEEDED) { return; } + const oldHass = changedProps.get("hass") as HomeAssistant | undefined; if (!oldHass || oldHass.panelUrl !== this.hass.panelUrl) { const selectedEl = this.shadowRoot!.querySelector(".iron-selected"); if (selectedEl) { @@ -851,29 +846,22 @@ class HaSidebar extends SubscribeMixin(LitElement) { font-size: 20px; align-items: center; padding-left: calc(4px + env(safe-area-inset-left)); - } - :host([rtl]) .menu { - padding-left: 4px; - padding-right: calc(4px + env(safe-area-inset-right)); + padding-inline-start: calc(4px + env(safe-area-inset-left)); + padding-inline-end: initial; } :host([expanded]) .menu { width: calc(256px + env(safe-area-inset-left)); } - :host([rtl][expanded]) .menu { - width: calc(256px + env(safe-area-inset-right)); - } .menu ha-icon-button { color: var(--sidebar-icon-color); } .title { margin-left: 19px; + margin-inline-start: 19px; + margin-inline-end: initial; width: 100%; display: none; } - :host([rtl]) .title { - margin-left: 0; - margin-right: 19px; - } :host([narrow]) .title { margin: 0; padding: 0 16px; @@ -904,11 +892,8 @@ class HaSidebar extends SubscribeMixin(LitElement) { overflow-x: hidden; background: none; margin-left: env(safe-area-inset-left); - } - - :host([rtl]) paper-listbox { - margin-left: initial; - margin-right: env(safe-area-inset-right); + margin-inline-start: env(safe-area-inset-left); + margin-inline-end: initial; } a { @@ -925,6 +910,8 @@ class HaSidebar extends SubscribeMixin(LitElement) { box-sizing: border-box; margin: 4px; padding-left: 12px; + padding-inline-start: 12px; + padding-inline-end: initial; border-radius: 4px; --paper-item-min-height: 40px; width: 48px; @@ -932,10 +919,6 @@ class HaSidebar extends SubscribeMixin(LitElement) { :host([expanded]) paper-icon-item { width: 248px; } - :host([rtl]) paper-icon-item { - padding-left: auto; - padding-right: 12px; - } ha-icon[slot="item-icon"], ha-svg-icon[slot="item-icon"] { @@ -1010,11 +993,8 @@ class HaSidebar extends SubscribeMixin(LitElement) { .configuration-container { display: flex; margin-left: env(safe-area-inset-left); - } - :host([rtl]) .notifications-container, - :host([rtl]) .configuration-container { - margin-left: initial; - margin-right: env(safe-area-inset-right); + margin-inline-start: env(safe-area-inset-left); + margin-inline-end: initial; } .notifications { cursor: pointer; @@ -1025,23 +1005,18 @@ class HaSidebar extends SubscribeMixin(LitElement) { } .profile { margin-left: env(safe-area-inset-left); - } - :host([rtl]) .profile { - margin-left: initial; - margin-right: env(safe-area-inset-right); + margin-inline-start: env(safe-area-inset-left); + margin-inline-end: initial; } .profile paper-icon-item { padding-left: 4px; - } - :host([rtl]) .profile paper-icon-item { - padding-left: auto; - padding-right: 4px; + margin-inline-start: 4px; + margin-inline-end: auto; } .profile .item-text { margin-left: 8px; - } - :host([rtl]) .profile .item-text { - margin-right: 8px; + margin-inline-start: 8px; + margin-inline-end: initial; } .notification-badge, @@ -1106,9 +1081,9 @@ class HaSidebar extends SubscribeMixin(LitElement) { font-weight: 500; } - :host([rtl]) .menu ha-icon-button { - -webkit-transform: scaleX(-1); - transform: scaleX(-1); + .menu ha-icon-button { + -webkit-transform: scaleX(var(--scale-direction)); + transform: scaleX(var(--scale-direction)); } `, ]; diff --git a/src/components/ha-slider.ts b/src/components/ha-slider.ts index 5ea8d50b3321..31607a62f568 100644 --- a/src/components/ha-slider.ts +++ b/src/components/ha-slider.ts @@ -2,9 +2,15 @@ import { customElement } from "lit/decorators"; import "element-internals-polyfill"; import { MdSlider } from "@material/web/slider/slider"; import { CSSResult, css } from "lit"; +import { mainWindow } from "../common/dom/get_main_window"; @customElement("ha-slider") export class HaSlider extends MdSlider { + public connectedCallback() { + super.connectedCallback(); + this.dir = mainWindow.document.dir; + } + static override styles: CSSResult[] = [ ...MdSlider.styles, css` diff --git a/src/components/ha-sortable.ts b/src/components/ha-sortable.ts index b188620c87d0..5258c2fc5fb6 100644 --- a/src/components/ha-sortable.ts +++ b/src/components/ha-sortable.ts @@ -39,6 +39,12 @@ export class HaSortable extends LitElement { @property({ type: String, attribute: "group" }) public group?: string; + @property({ type: Number, attribute: "swap-threshold" }) + public swapThreshold?: number; + + @property({ type: Boolean, attribute: "invert-swap" }) + public invertSwap?: boolean; + protected updated(changedProperties: PropertyValues) { if (changedProperties.has("disabled")) { if (this.disabled) { @@ -81,7 +87,7 @@ export class HaSortable extends LitElement { } .sortable-ghost { - border: 2px solid var(--primary-color); + box-shadow: 0 0 0 2px var(--primary-color); background: rgba(var(--rgb-primary-color), 0.25); border-radius: 4px; opacity: 0.4; @@ -108,7 +114,7 @@ export class HaSortable extends LitElement { const options: SortableInstance.Options = { animation: 150, - swapThreshold: 0.75, + swapThreshold: 1, onChoose: this._handleChoose, onEnd: this._handleEnd, }; @@ -116,6 +122,13 @@ export class HaSortable extends LitElement { if (this.draggableSelector) { options.draggable = this.draggableSelector; } + + if (this.swapThreshold !== undefined) { + options.swapThreshold = this.swapThreshold; + } + if (this.invertSwap !== undefined) { + options.invertSwap = this.invertSwap; + } if (this.handleSelector) { options.handle = this.handleSelector; } diff --git a/src/components/ha-textarea.ts b/src/components/ha-textarea.ts index 7cf773b90312..0fef6fe5a0b3 100644 --- a/src/components/ha-textarea.ts +++ b/src/components/ha-textarea.ts @@ -3,18 +3,11 @@ import { styles as textfieldStyles } from "@material/mwc-textfield/mwc-textfield import { styles as textareaStyles } from "@material/mwc-textarea/mwc-textarea.css"; import { css, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; -import { mainWindow } from "../common/dom/get_main_window"; @customElement("ha-textarea") export class HaTextArea extends TextAreaBase { @property({ type: Boolean, reflect: true }) autogrow = false; - firstUpdated() { - super.firstUpdated(); - - this.setAttribute("dir", mainWindow.document.dir); - } - updated(changedProperties: PropertyValues) { super.updated(changedProperties); if (this.autogrow && changedProperties.has("value")) { @@ -54,9 +47,10 @@ export class HaTextArea extends TextAreaBase { margin-top: 16px; margin-bottom: 16px; } - :host([dir="rtl"]) .mdc-floating-label { - right: 16px; - left: initial; + .mdc-floating-label { + inset-inline-start: 16px !important; + inset-inline-end: initial !important; + transform-origin: var(--float-start) top; } `, ]; diff --git a/src/components/map/ha-map.ts b/src/components/map/ha-map.ts index 42834dbe6907..1fcbceace41b 100644 --- a/src/components/map/ha-map.ts +++ b/src/components/map/ha-map.ts @@ -8,12 +8,18 @@ import type { Marker, Polyline, } from "leaflet"; +import { isToday } from "date-fns"; import { css, CSSResultGroup, PropertyValues, ReactiveElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { LeafletModuleType, setupLeafletMap, } from "../../common/dom/setup-leaflet-map"; +import { + formatTimeWithSeconds, + formatTimeWeekday, +} from "../../common/datetime/format_time"; +import { formatDateTime } from "../../common/datetime/format_date_time"; import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { computeStateName } from "../../common/entity/compute_state_name"; import { loadPolyfillIfNeeded } from "../../resources/resize-observer.polyfill"; @@ -27,12 +33,14 @@ const getEntityId = (entity: string | HaMapEntity): string => export interface HaMapPathPoint { point: LatLngTuple; - tooltip: string; + timestamp: Date; } export interface HaMapPaths { points: HaMapPathPoint[]; color?: string; + name?: string; gradualOpacity?: number; + fullDatetime?: boolean; } export interface HaMapEntity { @@ -242,6 +250,30 @@ export class HaMap extends ReactiveElement { }); } + private _computePathTooltip(path: HaMapPaths, point: HaMapPathPoint): string { + let formattedTime: string; + if (path.fullDatetime) { + formattedTime = formatDateTime( + point.timestamp, + this.hass.locale, + this.hass.config + ); + } else if (isToday(point.timestamp)) { + formattedTime = formatTimeWithSeconds( + point.timestamp, + this.hass.locale, + this.hass.config + ); + } else { + formattedTime = formatTimeWeekday( + point.timestamp, + this.hass.locale, + this.hass.config + ); + } + return `${path.name}
${formattedTime}`; + } + private _drawPaths(): void { const hass = this.hass; const map = this.leafletMap; @@ -289,7 +321,10 @@ export class HaMap extends ReactiveElement { fillOpacity: opacity, interactive: true, }) - .bindTooltip(path.points[pointIndex].tooltip, { direction: "top" }) + .bindTooltip( + this._computePathTooltip(path, path.points[pointIndex]), + { direction: "top" } + ) ); // DRAW line between this and next point @@ -319,7 +354,10 @@ export class HaMap extends ReactiveElement { fillOpacity: opacity, interactive: true, }) - .bindTooltip(path.points[pointIndex].tooltip, { direction: "top" }) + .bindTooltip( + this._computePathTooltip(path, path.points[pointIndex]), + { direction: "top" } + ) ); } this._mapPaths.forEach((marker) => map.addLayer(marker)); @@ -556,6 +594,7 @@ export class HaMap extends ReactiveElement { color: white !important; border-radius: 4px; box-shadow: none !important; + text-align: center; } `; } diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index 6737ea613ba2..e46c9e908c43 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -25,7 +25,6 @@ import { classMap } from "lit/directives/class-map"; import { styleMap } from "lit/directives/style-map"; import { until } from "lit/directives/until"; import { fireEvent } from "../../common/dom/fire_event"; -import { computeRTLDirection } from "../../common/util/compute_rtl"; import { debounce } from "../../common/util/debounce"; import { isUnavailableState } from "../../data/entity"; import type { MediaPlayerItem } from "../../data/media-player"; @@ -539,7 +538,6 @@ export class HaMediaPlayerBrowse extends LitElement { .graphic=${mediaClass.show_list_images ? "medium" : "avatar"} - dir=${computeRTLDirection(this.hass)} > ${this.hass.localize( @@ -637,7 +635,6 @@ export class HaMediaPlayerBrowse extends LitElement { @click=${this._childClicked} .item=${child} .graphic=${mediaClass.show_list_images ? "medium" : "avatar"} - dir=${computeRTLDirection(this.hass)} > ${backgroundImage === "none" && !child.can_play ? html` = { +const resources: { + entity: Record>; + entity_component: { + domains?: string[]; + resources?: Promise>; + }; + services: { + all?: Promise>; + domains: { [domain: string]: ServiceIcons | Promise }; + }; +} = { entity: {}, - entity_component: undefined, - services: {}, + entity_component: {}, + services: { domains: {} }, }; -interface IconResources { - resources: Record>; +interface IconResources< + T extends ComponentIcons | PlatformIcons | ServiceIcons, +> { + resources: Record; } interface PlatformIcons { - [domain: string]: { - [translation_key: string]: { - state: Record; - state_attributes: Record< - string, - { - state: Record; - default: string; - } - >; - default: string; - }; + [translation_key: string]: { + state: Record; + state_attributes: Record< + string, + { + state: Record; + default: string; + } + >; + default: string; }; } @@ -55,12 +66,18 @@ interface ServiceIcons { export type IconCategory = "entity" | "entity_component" | "services"; -export const getHassIcons = async ( +type CategoryType = { + entity: PlatformIcons; + entity_component: ComponentIcons; + services: ServiceIcons; +}; + +export const getHassIcons = async ( hass: HomeAssistant, - category: IconCategory, + category: T, integration?: string -): Promise => - hass.callWS<{ resources: Record }>({ +) => + hass.callWS>({ type: "frontend/get_icons", category, integration, @@ -70,14 +87,17 @@ export const getPlatformIcons = async ( hass: HomeAssistant, integration: string, force = false -): Promise => { +): Promise => { if (!force && integration in resources.entity) { return resources.entity[integration]; } - const result = getHassIcons(hass, "entity", integration); - resources.entity[integration] = result.then( + if (!isComponentLoaded(hass, integration)) { + return undefined; + } + const result = getHassIcons(hass, "entity", integration).then( (res) => res?.resources[integration] ); + resources.entity[integration] = result; return resources.entity[integration]; }; @@ -85,45 +105,59 @@ export const getComponentIcons = async ( hass: HomeAssistant, domain: string, force = false -): Promise => { - if (!force && resources.entity_component) { - return resources.entity_component.then((res) => res[domain]); +): Promise => { + if ( + !force && + resources.entity_component.resources && + resources.entity_component.domains?.includes(domain) + ) { + return resources.entity_component.resources.then((res) => res[domain]); } - resources.entity_component = getHassIcons(hass, "entity_component").then( - (result) => result.resources - ); - return resources.entity_component.then((res) => res[domain]); + if (!isComponentLoaded(hass, domain)) { + return undefined; + } + resources.entity_component.domains = [...hass.config.components]; + resources.entity_component.resources = getHassIcons( + hass, + "entity_component" + ).then((result) => result.resources); + return resources.entity_component.resources.then((res) => res[domain]); }; export const getServiceIcons = async ( hass: HomeAssistant, domain?: string, force = false -): Promise => { +): Promise | undefined> => { if (!domain) { if (!force && resources.services.all) { return resources.services.all; } resources.services.all = getHassIcons(hass, "services", domain).then( (res) => { - resources.services = res.resources; + resources.services.domains = res.resources; return res?.resources; } ); return resources.services.all; } - if (!force && domain && domain in resources.services) { - return resources.services[domain]; + if (!force && domain in resources.services.domains) { + return resources.services.domains[domain]; } if (resources.services.all && !force) { await resources.services.all; - if (domain in resources.services) { - return resources.services[domain]; + if (domain in resources.services.domains) { + return resources.services.domains[domain]; } } + if (!isComponentLoaded(hass, domain)) { + return undefined; + } const result = getHassIcons(hass, "services", domain); - resources.services[domain] = result.then((res) => res?.resources[domain]); - return resources.services[domain]; + resources.services.domains[domain] = result.then( + (res) => res?.resources[domain] + ); + return resources.services.domains[domain]; }; export const entityIcon = async ( @@ -238,7 +272,7 @@ export const serviceIcon = async ( const serviceName = computeObjectId(service); const serviceIcons = await getServiceIcons(hass, domain); if (serviceIcons) { - icon = serviceIcons[serviceName]; + icon = serviceIcons[serviceName] as string; } if (!icon) { icon = await domainIcon(hass, domain); diff --git a/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts b/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts index 7ed96ec06197..da3f8276dfa9 100644 --- a/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts +++ b/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts @@ -2,7 +2,6 @@ import "@material/mwc-button/mwc-button"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; -import { computeRTLDirection } from "../../common/util/compute_rtl"; import "../../components/ha-dialog"; import "../../components/ha-formfield"; import "../../components/ha-switch"; @@ -82,7 +81,6 @@ class DialogConfigEntrySystemOptions extends LitElement { } )}

`} - .dir=${computeRTLDirection(this.hass)} > `} - .dir=${computeRTLDirection(this.hass)} > diff --git a/src/layouts/hass-subpage.ts b/src/layouts/hass-subpage.ts index 3a1ed243853a..f297a77b189d 100644 --- a/src/layouts/hass-subpage.ts +++ b/src/layouts/hass-subpage.ts @@ -1,15 +1,6 @@ -import { - css, - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, -} from "lit"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, eventOptions, property } from "lit/decorators"; import { restoreScroll } from "../common/decorators/restore-scroll"; -import { toggleAttribute } from "../common/dom/toggle_attribute"; -import { computeRTL } from "../common/util/compute_rtl"; import "../components/ha-icon-button-arrow-prev"; import "../components/ha-menu-button"; import { HomeAssistant } from "../types"; @@ -34,17 +25,6 @@ class HassSubpage extends LitElement { // @ts-ignore @restoreScroll(".content") private _savedScrollPos?: number; - protected willUpdate(changedProps: PropertyValues): void { - super.willUpdate(changedProps); - if (!changedProps.has("hass")) { - return; - } - const oldHass = changedProps.get("hass") as HomeAssistant | undefined; - if (!oldHass || oldHass.locale !== this.hass.locale) { - toggleAttribute(this, "rtl", computeRTL(this.hass)); - } - } - protected render(): TemplateResult { return html`
@@ -160,6 +140,9 @@ class HassSubpage extends LitElement { #fab { position: absolute; right: calc(16px + env(safe-area-inset-right)); + inset-inline-end: calc(16px + env(safe-area-inset-right)); + inset-inline-start: initial; + bottom: calc(16px + env(safe-area-inset-bottom)); z-index: 1; } @@ -169,15 +152,8 @@ class HassSubpage extends LitElement { #fab[is-wide] { bottom: 24px; right: 24px; - } - :host([rtl]) #fab { - right: auto; - left: calc(16px + env(safe-area-inset-left)); - } - :host([rtl][is-wide]) #fab { - bottom: 24px; - left: 24px; - right: auto; + inset-inline-end: 24px; + inset-inline-start: initial; } `, ]; diff --git a/src/layouts/hass-tabs-subpage-data-table.ts b/src/layouts/hass-tabs-subpage-data-table.ts index 275cfe82f12f..7af35ded290c 100644 --- a/src/layouts/hass-tabs-subpage-data-table.ts +++ b/src/layouts/hass-tabs-subpage-data-table.ts @@ -4,7 +4,6 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, query } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; import { LocalizeFunc } from "../common/translations/localize"; -import { computeRTLDirection } from "../common/util/compute_rtl"; import "../components/data-table/ha-data-table"; import type { DataTableColumnContainer, @@ -244,7 +243,6 @@ export class HaTabsSubpageDataTable extends LitElement { .selectable=${this.selectable} .hasFab=${this.hasFab} .id=${this.id} - .dir=${computeRTLDirection(this.hass)} .clickable=${this.clickable} .appendRow=${this.appendRow} > diff --git a/src/layouts/hass-tabs-subpage.ts b/src/layouts/hass-tabs-subpage.ts index 15d8b2662927..9de45b8a4a21 100644 --- a/src/layouts/hass-tabs-subpage.ts +++ b/src/layouts/hass-tabs-subpage.ts @@ -13,7 +13,6 @@ import memoizeOne from "memoize-one"; import { isComponentLoaded } from "../common/config/is_component_loaded"; import { restoreScroll } from "../common/decorators/restore-scroll"; import { LocalizeFunc } from "../common/translations/localize"; -import { computeRTL } from "../common/util/compute_rtl"; import "../components/ha-icon-button-arrow-prev"; import "../components/ha-menu-button"; import "../components/ha-svg-icon"; @@ -58,8 +57,6 @@ class HassTabsSubpage extends LitElement { @property({ type: Boolean, reflect: true, attribute: "is-wide" }) public isWide = false; - @property({ type: Boolean, reflect: true }) public rtl = false; - @state() private _activeTab?: PageNavigation; // @ts-ignore @@ -123,14 +120,6 @@ class HassTabsSubpage extends LitElement { `${this.route.prefix}${this.route.path}`.includes(tab.path) ); } - if (changedProperties.has("hass")) { - const oldHass = changedProperties.get("hass") as - | HomeAssistant - | undefined; - if (!oldHass || oldHass.language !== this.hass.language) { - this.rtl = computeRTL(this.hass); - } - } super.willUpdate(changedProperties); } @@ -334,6 +323,8 @@ class HassTabsSubpage extends LitElement { #fab { position: fixed; right: calc(16px + env(safe-area-inset-right)); + inset-inline-end: calc(16px + env(safe-area-inset-right)); + inset-inline-start: initial; bottom: calc(16px + env(safe-area-inset-bottom)); z-index: 1; } @@ -343,15 +334,8 @@ class HassTabsSubpage extends LitElement { #fab[is-wide] { bottom: 24px; right: 24px; - } - :host([rtl]) #fab { - right: auto; - left: calc(16px + env(safe-area-inset-left)); - } - :host([rtl][is-wide]) #fab { - bottom: 24px; - left: 24px; - right: auto; + inset-inline-end: 24px; + inset-inline-start: initial; } `, ]; diff --git a/src/layouts/home-assistant-main.ts b/src/layouts/home-assistant-main.ts index d6e7b3c82648..29963ac45d16 100644 --- a/src/layouts/home-assistant-main.ts +++ b/src/layouts/home-assistant-main.ts @@ -11,7 +11,6 @@ import { customElement, property, state } from "lit/decorators"; import { fireEvent, HASSDomEvent } from "../common/dom/fire_event"; import { listenMediaQuery } from "../common/dom/media_query"; import { toggleAttribute } from "../common/dom/toggle_attribute"; -import { computeRTLDirection } from "../common/util/compute_rtl"; import "../components/ha-drawer"; import { showNotificationDrawer } from "../dialogs/notifications/show-notification-drawer"; import type { HomeAssistant, Route } from "../types"; @@ -62,7 +61,6 @@ export class HomeAssistantMain extends LitElement { ` : html` @@ -203,7 +201,6 @@ export class HAFullCalendar extends LitElement { .buttons=${viewToggleButtons} .active=${this._activeView} @value-changed=${this._handleView} - .dir=${computeRTLDirection(this.hass)} >
`} @@ -503,6 +500,8 @@ export class HAFullCalendar extends LitElement { position: absolute; bottom: 32px; right: 32px; + inset-inline-end: 32px; + inset-inline-start: initial; z-index: 1; } diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index 3cf22ec221a2..fde289baccd5 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -81,6 +81,7 @@ export default class HaAutomationAction extends LitElement { @item-moved=${this._actionMoved} group="actions" .path=${this.path} + invert-swap >
${repeat( @@ -266,22 +267,32 @@ export default class HaAutomationAction extends LitElement { static get styles(): CSSResultGroup { return css` + .actions { + padding: 16px; + margin: -16px -16px 0px -16px; + display: flex; + flex-direction: column; + gap: 16px; + } + .actions:not(:has(ha-automation-action-row)) { + margin: -16px; + } + .sortable-ghost { + background: none; + border-radius: var(--ha-card-border-radius, 12px); + } + .sortable-drag { + background: none; + } ha-automation-action-row { display: block; - margin-bottom: 16px; scroll-margin-top: 48px; } ha-svg-icon { height: 20px; } - ha-alert { - display: block; - margin-bottom: 16px; - border-radius: var(--ha-card-border-radius, 12px); - overflow: hidden; - } .handle { - padding: 12px 4px; + padding: 12px; cursor: move; /* fallback if grab cursor is unsupported */ cursor: grab; } diff --git a/src/panels/config/automation/action/types/ha-automation-action-choose.ts b/src/panels/config/automation/action/types/ha-automation-action-choose.ts index 56dfe10da7ca..d57379ab8ed4 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-choose.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-choose.ts @@ -125,6 +125,7 @@ export class HaChooseAction extends LitElement implements ActionElement { .disabled=${!this._showReorder || this.disabled} group="choose-options" .path=${[...(this.path ?? []), "choose"]} + invert-swap >
${repeat( @@ -296,7 +297,7 @@ export class HaChooseAction extends LitElement implements ActionElement { )}:
${repeat( @@ -291,22 +292,32 @@ export default class HaAutomationCondition extends LitElement { static get styles(): CSSResultGroup { return css` + .conditions { + padding: 16px; + margin: -16px -16px 0px -16px; + display: flex; + flex-direction: column; + gap: 16px; + } + .conditions:not(:has(ha-automation-condition-row)) { + margin: -16px; + } + .sortable-ghost { + background: none; + border-radius: var(--ha-card-border-radius, 12px); + } + .sortable-drag { + background: none; + } ha-automation-condition-row { display: block; - margin-bottom: 16px; scroll-margin-top: 48px; } ha-svg-icon { height: 20px; } - ha-alert { - display: block; - margin-bottom: 16px; - border-radius: var(--ha-card-border-radius, 12px); - overflow: hidden; - } .handle { - padding: 12px 4px; + padding: 12px; cursor: move; /* fallback if grab cursor is unsupported */ cursor: grab; } diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index ed180172b3ee..04343c20ec16 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -1,5 +1,4 @@ import "@material/mwc-button"; -import "@material/mwc-list/mwc-list-item"; import { mdiCheck, mdiContentDuplicate, @@ -35,6 +34,7 @@ import "../../../components/ha-fab"; import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon"; import "../../../components/ha-yaml-editor"; +import "../../../components/ha-list-item"; import { AutomationConfig, AutomationEntity, @@ -150,7 +150,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { .path=${mdiDotsVertical} > - - + - ${this.hass.localize("ui.panel.config.automation.editor.run")} - + ${stateObj && this._config && this.narrow ? html` - + ${this.hass.localize( "ui.panel.config.automation.editor.show_trace" )} @@ -181,22 +181,22 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { slot="graphic" .path=${mdiTransitConnection} > - + ` : ""} - ${this.hass.localize("ui.panel.config.automation.editor.rename")} - + ${this._config && !("use_blueprint" in this._config) ? html` - - + ` : ""} - - +
  • - + ${this.hass.localize("ui.panel.config.automation.editor.edit_ui")} ${this._mode === "gui" ? html`` : ``} - - + + ${this.hass.localize("ui.panel.config.automation.editor.edit_yaml")} ${this._mode === "yaml" ? html`` : ``} - +
  • - - + - - + ${this._config diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index 6de4247300ab..69041acc9287 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -78,6 +78,7 @@ export default class HaAutomationTrigger extends LitElement { @item-moved=${this._triggerMoved} group="triggers" .path=${this.path} + invert-swap >
    ${repeat( @@ -240,22 +241,32 @@ export default class HaAutomationTrigger extends LitElement { static get styles(): CSSResultGroup { return css` + .triggers { + padding: 16px; + margin: -16px -16px 0px -16px; + display: flex; + flex-direction: column; + gap: 16px; + } + .triggers:not(:has(ha-automation-trigger-row)) { + margin: -16px; + } + .sortable-ghost { + background: none; + border-radius: var(--ha-card-border-radius, 12px); + } + .sortable-drag { + background: none; + } ha-automation-trigger-row { display: block; - margin-bottom: 16px; scroll-margin-top: 48px; } ha-svg-icon { height: 20px; } - ha-alert { - display: block; - margin-bottom: 16px; - border-radius: var(--ha-card-border-radius, 16px); - overflow: hidden; - } .handle { - padding: 12px 4px; + padding: 12px; cursor: move; /* fallback if grab cursor is unsupported */ cursor: grab; } diff --git a/src/panels/config/cloud/account/cloud-account.ts b/src/panels/config/cloud/account/cloud-account.ts index 293576fa8f4e..8c81013cd24f 100644 --- a/src/panels/config/cloud/account/cloud-account.ts +++ b/src/panels/config/cloud/account/cloud-account.ts @@ -1,9 +1,8 @@ import "@material/mwc-button"; -import { css, html, LitElement, PropertyValues } from "lit"; +import { css, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { formatDateTime } from "../../../../common/datetime/format_date_time"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import { debounce } from "../../../../common/util/debounce"; import "../../../../components/ha-alert"; import "../../../../components/ha-card"; @@ -37,8 +36,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) { @state() private _subscription?: SubscriptionInfo; - @state() private _rtlDirection: "rtl" | "ltr" = "rtl"; - protected render() { return html` @@ -193,7 +188,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) { .hass=${this.hass} .narrow=${this.narrow} .cloudStatus=${this.cloudStatus} - dir=${this._rtlDirection} >
    @@ -205,15 +199,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) { this._fetchSubscriptionInfo(); } - protected updated(changedProps: PropertyValues) { - if (changedProps.has("hass")) { - const oldHass = changedProps.get("hass") as HomeAssistant | undefined; - if (!oldHass || oldHass.locale !== this.hass.locale) { - this._rtlDirection = computeRTLDirection(this.hass); - } - } - } - protected override hassSubscribe() { const googleCheck = debounce( () => { @@ -272,10 +257,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) { fireEvent(this, "ha-refresh-cloud-status"); } - _computeRTLDirection(hass) { - return computeRTLDirection(hass); - } - static get styles() { return [ haStyle, diff --git a/src/panels/config/cloud/account/cloud-remote-pref.ts b/src/panels/config/cloud/account/cloud-remote-pref.ts index e5d0771b3932..bcfb4bdde797 100644 --- a/src/panels/config/cloud/account/cloud-remote-pref.ts +++ b/src/panels/config/cloud/account/cloud-remote-pref.ts @@ -179,14 +179,12 @@ export class CloudRemotePref extends LitElement { .header-actions { position: absolute; right: 24px; + inset-inline-end: 24px; + inset-inline-start: initial; top: 24px; display: flex; flex-direction: row; } - :host([dir="rtl"]) .header-actions { - right: auto; - left: 24px; - } .header-actions .icon-link { margin-top: -16px; margin-right: 8px; diff --git a/src/panels/config/cloud/account/cloud-tts-pref.ts b/src/panels/config/cloud/account/cloud-tts-pref.ts index 052fad179bf5..c1e85fccdc25 100644 --- a/src/panels/config/cloud/account/cloud-tts-pref.ts +++ b/src/panels/config/cloud/account/cloud-tts-pref.ts @@ -177,12 +177,10 @@ export class CloudTTSPref extends LitElement { .example { position: absolute; right: 16px; + inset-inline-end: 16px; + inset-inline-start: initial; top: 16px; } - :host([dir="rtl"]) .example { - right: auto; - left: 24px; - } .row { display: flex; } diff --git a/src/panels/config/devices/device-detail/integration-elements/mqtt/dialog-mqtt-device-debug-info.ts b/src/panels/config/devices/device-detail/integration-elements/mqtt/dialog-mqtt-device-debug-info.ts index 2a629114aa21..3884f65b5d97 100644 --- a/src/panels/config/devices/device-detail/integration-elements/mqtt/dialog-mqtt-device-debug-info.ts +++ b/src/panels/config/devices/device-detail/integration-elements/mqtt/dialog-mqtt-device-debug-info.ts @@ -9,7 +9,6 @@ import { } from "lit"; import { customElement, state } from "lit/decorators"; import { computeStateName } from "../../../../../../common/entity/compute_state_name"; -import { computeRTLDirection } from "../../../../../../common/util/compute_rtl"; import "../../../../../../components/ha-dialog"; import "../../../../../../components/ha-formfield"; import "../../../../../../components/ha-switch"; @@ -51,8 +50,6 @@ class DialogMQTTDeviceDebugInfo extends LitElement { return nothing; } - const dir = computeRTLDirection(this.hass!); - return html` diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 187a8a14efa6..0599d1e75e9d 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -35,7 +35,6 @@ import { } from "../../../common/integrations/protocolIntegrationPicked"; import { navigate } from "../../../common/navigate"; import { LocalizeFunc } from "../../../common/translations/localize"; -import { computeRTL } from "../../../common/util/compute_rtl"; import type { DataTableColumnContainer, RowClickedEvent, @@ -680,7 +679,6 @@ export class HaConfigEntities extends LitElement { extended @click=${this._addDevice} slot="fab" - ?rtl=${computeRTL(this.hass)} > ` diff --git a/src/panels/config/integrations/integration-panels/matter/dialog-matter-manage-fabrics.ts b/src/panels/config/integrations/integration-panels/matter/dialog-matter-manage-fabrics.ts index 5f1a3ef13f0e..f10c634be9ba 100644 --- a/src/panels/config/integrations/integration-panels/matter/dialog-matter-manage-fabrics.ts +++ b/src/panels/config/integrations/integration-panels/matter/dialog-matter-manage-fabrics.ts @@ -1,5 +1,5 @@ import "@material/mwc-button/mwc-button"; -import { mdiClose } from "@mdi/js"; +import { mdiDelete } from "@mdi/js"; import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; @@ -70,7 +70,7 @@ class DialogMatterManageFabrics extends LitElement { @click=${this._removeFabric} slot="meta" .fabric=${fabric} - .path=${mdiClose} + .path=${mdiDelete} > ` )} diff --git a/src/panels/config/integrations/integration-panels/matter/dialog-matter-open-commissioning-window.ts b/src/panels/config/integrations/integration-panels/matter/dialog-matter-open-commissioning-window.ts index 54d74a039e31..6125b1ef97b4 100644 --- a/src/panels/config/integrations/integration-panels/matter/dialog-matter-open-commissioning-window.ts +++ b/src/panels/config/integrations/integration-panels/matter/dialog-matter-open-commissioning-window.ts @@ -48,6 +48,11 @@ class DialogMatterOpenCommissioningWindow extends LitElement { > ${this._commissionParams ? html` +

    + ${this.hass.localize( + "ui.panel.config.matter.open_commissioning_window.success" + )} +

    diff --git a/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts b/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts index dbf0ae07a0ea..a8a7ed8a8c07 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts @@ -19,7 +19,6 @@ import { ConfigEntry, getConfigEntries, } from "../../../../../data/config_entries"; -import { computeRTL } from "../../../../../common/util/compute_rtl"; import "../../../../../components/ha-card"; import "../../../../../components/ha-fab"; import "../../../../../components/ha-icon-button"; @@ -259,7 +258,6 @@ class ZHAConfigDashboard extends LitElement { diff --git a/src/panels/config/integrations/integration-panels/zha/zha-device-endpoint-data-table.ts b/src/panels/config/integrations/integration-panels/zha/zha-device-endpoint-data-table.ts index 78a4871dd5ef..d68fdb0b1eb5 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-device-endpoint-data-table.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-device-endpoint-data-table.ts @@ -1,7 +1,6 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, query } from "lit/decorators"; import memoizeOne from "memoize-one"; -import { computeRTLDirection } from "../../../../../common/util/compute_rtl"; import "../../../../../components/data-table/ha-data-table"; import type { DataTableColumnContainer, @@ -142,7 +141,6 @@ export class ZHADeviceEndpointDataTable extends LitElement { .data=${this._deviceEndpoints(this.deviceEndpoints)} .selectable=${this.selectable} auto-height - .dir=${computeRTLDirection(this.hass)} .searchLabel=${this.hass.localize("ui.components.data-table.search")} .noDataText=${this.hass.localize("ui.components.data-table.no-data")} > diff --git a/src/panels/config/integrations/integration-panels/zha/zha-device-neighbors.ts b/src/panels/config/integrations/integration-panels/zha/zha-device-neighbors.ts index 64bec7d1bcd2..0774f8b34385 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-device-neighbors.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-device-neighbors.ts @@ -1,7 +1,6 @@ import { html, LitElement, PropertyValues, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; -import { computeRTLDirection } from "../../../../../common/util/compute_rtl"; import "../../../../../components/data-table/ha-data-table"; import type { DataTableColumnContainer, @@ -128,7 +127,6 @@ class ZHADeviceNeighbors extends LitElement { .columns=${this._columns(this.narrow)} .data=${this._deviceNeighbors(this.device, this._devices)} auto-height - .dir=${computeRTLDirection(this.hass)} .searchLabel=${this.hass.localize( "ui.components.data-table.search" )} diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts index e7e8a21e0ad3..16f00a7178ac 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts @@ -18,7 +18,6 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; -import { computeRTL } from "../../../../../common/util/compute_rtl"; import "../../../../../components/ha-card"; import "../../../../../components/ha-expansion-panel"; import "../../../../../components/ha-fab"; @@ -472,7 +471,6 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) { "ui.panel.config.zwave_js.common.add_node" )} extended - ?rtl=${computeRTL(this.hass)} @click=${this._addNodeClicked} .disabled=${this._status !== "connected" || (this._network?.controller.inclusion_state !== InclusionState.Idle && diff --git a/src/panels/config/script/ha-script-picker.ts b/src/panels/config/script/ha-script-picker.ts index aa46a532a872..aca24a71acdc 100644 --- a/src/panels/config/script/ha-script-picker.ts +++ b/src/panels/config/script/ha-script-picker.ts @@ -26,7 +26,6 @@ import { relativeTime } from "../../../common/datetime/relative_time"; import { HASSDomEvent, fireEvent } from "../../../common/dom/fire_event"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { navigate } from "../../../common/navigate"; -import { computeRTL } from "../../../common/util/compute_rtl"; import { DataTableColumnContainer, RowClickedEvent, @@ -308,7 +307,6 @@ class HaScriptPicker extends LitElement { "ui.panel.config.script.picker.add_script" )} extended - ?rtl=${computeRTL(this.hass)} @click=${this._createNew} > diff --git a/src/panels/config/users/dialog-add-user.ts b/src/panels/config/users/dialog-add-user.ts index 3e63f216ee4f..e802ecdd514a 100644 --- a/src/panels/config/users/dialog-add-user.ts +++ b/src/panels/config/users/dialog-add-user.ts @@ -8,7 +8,6 @@ import { nothing, } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { computeRTLDirection } from "../../../common/util/compute_rtl"; import "../../../components/ha-circular-progress"; import { createCloseHeading } from "../../../components/ha-dialog"; import "../../../components/ha-formfield"; @@ -161,7 +160,6 @@ export class DialogAddUser extends LitElement { .label=${this.hass.localize( "ui.panel.config.users.editor.local_only" )} - .dir=${computeRTLDirection(this.hass)} > ` : html``} diff --git a/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts b/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts index 68204c96addc..46f4f6c372c4 100644 --- a/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts +++ b/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts @@ -27,7 +27,6 @@ import { isEmptyFilter, } from "../../../common/entity/entity_filter"; import { navigate } from "../../../common/navigate"; -import { computeRTL } from "../../../common/util/compute_rtl"; import { DataTableColumnContainer, DataTableRowData, @@ -618,7 +617,6 @@ export class VoiceAssistantsExpose extends LitElement { "ui.panel.config.voice_assistants.expose.add" )} extended - ?rtl=${computeRTL(this.hass)} @click=${this._addEntry} > diff --git a/src/panels/developer-tools/service/developer-tools-service.ts b/src/panels/developer-tools/service/developer-tools-service.ts index d920065d5a7b..fb5f374f21d2 100644 --- a/src/panels/developer-tools/service/developer-tools-service.ts +++ b/src/panels/developer-tools/service/developer-tools-service.ts @@ -588,13 +588,10 @@ class HaPanelDevService extends LitElement { } .attributes th { - text-align: left; + text-align: var(--float-start); background-color: var(--card-background-color); border-bottom: 1px solid var(--primary-text-color); - } - - :host([rtl]) .attributes th { - text-align: right; + direction: var(--direction); } .attributes tr { diff --git a/src/panels/developer-tools/state/developer-tools-state.ts b/src/panels/developer-tools/state/developer-tools-state.ts index d82d2bc2bb87..dc7f04ed6abc 100644 --- a/src/panels/developer-tools/state/developer-tools-state.ts +++ b/src/panels/developer-tools/state/developer-tools-state.ts @@ -16,9 +16,7 @@ import memoizeOne from "memoize-one"; import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time"; import { storage } from "../../../common/decorators/storage"; import { fireEvent } from "../../../common/dom/fire_event"; -import { toggleAttribute } from "../../../common/dom/toggle_attribute"; import { escapeRegExp } from "../../../common/string/escape_regexp"; -import { computeRTL } from "../../../common/util/compute_rtl"; import { copyToClipboard } from "../../../common/util/copy-clipboard"; import "../../../components/entity/ha-entity-picker"; import "../../../components/ha-alert"; @@ -69,8 +67,6 @@ class HaPanelDevState extends LitElement { @property({ type: Boolean, reflect: true }) public narrow = false; - @property({ type: Boolean, reflect: true }) public rtl = false; - @query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor; private _filteredEntities = memoizeOne( @@ -325,14 +321,6 @@ class HaPanelDevState extends LitElement { `; } - protected updated(changedProps) { - super.updated(changedProps); - const oldHass = changedProps.get("hass") as HomeAssistant | undefined; - if (!oldHass || oldHass.locale !== this.hass.locale) { - toggleAttribute(this, "rtl", computeRTL(this.hass)); - } - } - private _copyEntity(ev) { ev.preventDefault(); const entity = (ev.currentTarget! as any).entity; @@ -626,7 +614,8 @@ class HaPanelDevState extends LitElement { .entities th { padding: 0 8px; - text-align: left; + text-align: var(--float-start); + direction: var(--direction); } .filters th { @@ -652,15 +641,6 @@ class HaPanelDevState extends LitElement { bottom: -8px; } - :host([rtl]) .entities th { - text-align: right; - direction: rtl; - } - - :host([rtl]) .filters { - direction: rtl; - } - .entities tr { vertical-align: top; direction: ltr; diff --git a/src/panels/developer-tools/statistics/developer-tools-statistics.ts b/src/panels/developer-tools/statistics/developer-tools-statistics.ts index f85744ec45e5..0cb139971695 100644 --- a/src/panels/developer-tools/statistics/developer-tools-statistics.ts +++ b/src/panels/developer-tools/statistics/developer-tools-statistics.ts @@ -1,7 +1,7 @@ import "@material/mwc-button/mwc-button"; import { mdiSlopeUphill } from "@mdi/js"; import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; -import { css, CSSResultGroup, html, LitElement } from "lit"; +import { CSSResultGroup, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../common/dom/fire_event"; @@ -26,7 +26,6 @@ import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { showStatisticsAdjustSumDialog } from "./show-dialog-statistics-adjust-sum"; import { showFixStatisticsUnitsChangedDialog } from "./show-dialog-statistics-fix-units-changed"; -import { computeRTLDirection } from "../../../common/util/compute_rtl"; import { documentationUrl } from "../../../util/documentation-url"; const FIX_ISSUES_ORDER = { @@ -183,7 +182,6 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) { id="statistic_id" clickable @row-click=${this._rowClicked} - .dir=${computeRTLDirection(this.hass)} > `; } @@ -411,45 +409,7 @@ class HaPanelDevStatistics extends SubscribeMixin(LitElement) { }; static get styles(): CSSResultGroup { - return [ - haStyle, - css` - .content { - padding: 16px; - padding: max(16px, env(safe-area-inset-top)) - max(16px, env(safe-area-inset-right)) - max(16px, env(safe-area-inset-bottom)) - max(16px, env(safe-area-inset-left)); - } - - th { - padding: 0 8px; - text-align: left; - } - - :host([rtl]) th { - text-align: right; - } - - tr { - vertical-align: top; - direction: ltr; - } - - tr:nth-child(odd) { - background-color: var(--table-row-background-color, #fff); - } - - tr:nth-child(even) { - background-color: var(--table-row-alternative-background-color, #eee); - } - td { - padding: 4px; - min-width: 200px; - word-break: break-word; - } - `, - ]; + return haStyle; } } diff --git a/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts b/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts index abc264adce6d..6ffa9790da95 100644 --- a/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts +++ b/src/panels/developer-tools/statistics/dialog-statistics-adjust-sum.ts @@ -1,5 +1,4 @@ import "@material/mwc-button/mwc-button"; -import "@material/mwc-list/mwc-list-item"; import formatISO9075 from "date-fns/formatISO9075"; import { css, @@ -21,6 +20,7 @@ import "../../../components/ha-selector/ha-selector-datetime"; import "../../../components/ha-selector/ha-selector-number"; import "../../../components/ha-svg-icon"; import "../../../components/ha-icon-next"; +import "../../../components/ha-list-item"; import { adjustStatisticsSum, fetchStatistics, @@ -151,7 +151,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement { const stat = data[i]; const growth = Math.round(stat.change! * 100) / 100; rows.push(html` - - + `); } stats = html`${rows}`; @@ -413,7 +413,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement { ha-selector-number { margin-bottom: 20px; } - mwc-list-item { + ha-list-item { margin: 0 -24px; --mdc-list-side-padding: 24px; } diff --git a/src/panels/history/ha-panel-history.ts b/src/panels/history/ha-panel-history.ts index fbed2990a971..52f640e2c4a2 100644 --- a/src/panels/history/ha-panel-history.ts +++ b/src/panels/history/ha-panel-history.ts @@ -1,4 +1,4 @@ -import { mdiDownload, mdiFilterRemove, mdiRefresh } from "@mdi/js"; +import { mdiDownload, mdiFilterRemove } from "@mdi/js"; import { differenceInHours } from "date-fns/esm"; import { HassServiceTarget, @@ -6,6 +6,7 @@ import { } from "home-assistant-js-websocket/dist/types"; import { LitElement, PropertyValues, css, html } from "lit"; import { property, query, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { ensureArray } from "../../common/array/ensure-array"; import { storage } from "../../common/decorators/storage"; import { navigate } from "../../common/navigate"; @@ -15,7 +16,6 @@ import { extractSearchParamsObject, removeSearchParam, } from "../../common/url/search-params"; -import { computeRTL } from "../../common/util/compute_rtl"; import { MIN_TIME_BETWEEN_UPDATES } from "../../components/chart/ha-chart-base"; import "../../components/chart/state-history-charts"; import type { StateHistoryCharts } from "../../components/chart/state-history-charts"; @@ -45,8 +45,8 @@ import { HistoryStates, EntityHistoryState, LineChartUnit, - LineChartEntity, computeGroupKey, + LineChartState, } from "../../data/history"; import { fetchStatistics, Statistics } from "../../data/recorder"; import { getSensorNumericDeviceClasses } from "../../data/sensor"; @@ -54,6 +54,7 @@ import { SubscribeMixin } from "../../mixins/subscribe-mixin"; import { haStyle } from "../../resources/styles"; import { HomeAssistant } from "../../types"; import { fileDownload } from "../../util/file_download"; +import { showAlertDialog } from "../../dialogs/generic/show-dialog-box"; class HaPanelHistory extends SubscribeMixin(LitElement) { @property({ attribute: false }) hass!: HomeAssistant; @@ -71,7 +72,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { state: true, subscribe: false, }) - private _targetPickerValue?: HassServiceTarget; + private _targetPickerValue: HassServiceTarget = {}; @state() private _isLoading = false; @@ -138,6 +139,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { } protected render() { + const entitiesSelected = this._getEntityIds().length > 0; return html` ${this._showBack @@ -155,7 +157,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { > `}
    ${this.hass.localize("panel.history")}
    - ${this._targetPickerValue + ${entitiesSelected ? html` ` : ""} - @@ -203,7 +198,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { ? html`
    ` - : !this._targetPickerValue + : !entitiesSelected ? html`` @@ -227,50 +222,83 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { ): HistoryResult { const result: HistoryResult = { ...historyResult, line: [] }; - const keys = new Set( - historyResult.line - .map((i) => computeGroupKey(i.unit, i.device_class, true)) - .concat( - ltsResult.line.map((i) => - computeGroupKey(i.unit, i.device_class, true) - ) - ) - ); - keys.forEach((key) => { - const historyItem = historyResult.line.find( - (i) => computeGroupKey(i.unit, i.device_class, true) === key - ); - const ltsItem = ltsResult.line.find( - (i) => computeGroupKey(i.unit, i.device_class, true) === key - ); - if (historyItem && ltsItem) { - const newLineItem: LineChartUnit = { ...historyItem, data: [] }; - const entities = new Set( - historyItem.data - .map((d) => d.entity_id) - .concat(ltsItem.data.map((d) => d.entity_id)) - ); - entities.forEach((entity) => { - const historyDataItem = historyItem.data.find( - (d) => d.entity_id === entity - ); - const ltsDataItem = ltsItem.data.find((d) => d.entity_id === entity); - if (historyDataItem && ltsDataItem) { - const newDataItem: LineChartEntity = { - ...historyDataItem, - statistics: ltsDataItem.statistics, - }; - newLineItem.data.push(newDataItem); - } else { - newLineItem.data.push(historyDataItem || ltsDataItem!); - } - }); - result.line.push(newLineItem); + const lookup: Record< + string, + { historyItem?: LineChartUnit; ltsItem?: LineChartUnit } + > = {}; + + for (const item of historyResult.line) { + const key = computeGroupKey(item.unit, item.device_class, true); + if (key) { + lookup[key] = { + historyItem: item, + }; + } + } + + for (const item of ltsResult.line) { + const key = computeGroupKey(item.unit, item.device_class, true); + if (!key) { + continue; + } + if (key in lookup) { + lookup[key].ltsItem = item; } else { + lookup[key] = { ltsItem: item }; + } + } + + for (const { historyItem, ltsItem } of Object.values(lookup)) { + if (!historyItem || !ltsItem) { // Only one result has data for this item, so just push it directly instead of merging. result.line.push(historyItem || ltsItem!); + continue; } - }); + + const newLineItem: LineChartUnit = { ...historyItem, data: [] }; + const entities = new Set([ + ...historyItem.data.map((d) => d.entity_id), + ...ltsItem.data.map((d) => d.entity_id), + ]); + + for (const entity of entities) { + const historyDataItem = historyItem.data.find( + (d) => d.entity_id === entity + ); + const ltsDataItem = ltsItem.data.find((d) => d.entity_id === entity); + + if (!historyDataItem || !ltsDataItem) { + newLineItem.data.push(historyDataItem || ltsDataItem!); + continue; + } + + // Remove statistics that overlap with states + const oldestState = + historyDataItem.states[0]?.last_changed || + // If no state, fall back to the max last changed of the last statistics (so approve all) + ltsDataItem.statistics![ltsDataItem.statistics!.length - 1] + .last_changed + 1; + + const statistics: LineChartState[] = []; + for (const s of ltsDataItem.statistics!) { + if (s.last_changed >= oldestState) { + break; + } + statistics.push(s); + } + + newLineItem.data.push( + statistics.length === 0 + ? // All statistics overlapped with states, so just push the states + historyDataItem + : { + ...historyDataItem, + statistics, + } + ); + } + result.line.push(newLineItem); + } return result; } @@ -342,114 +370,90 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { protected updated(changedProps: PropertyValues) { if ( - this._targetPickerValue && - (changedProps.has("_startDate") || - changedProps.has("_endDate") || - changedProps.has("_targetPickerValue") || - (!this._stateHistory && - (changedProps.has("_deviceEntityLookup") || - changedProps.has("_areaEntityLookup") || - changedProps.has("_areaDeviceLookup")))) + changedProps.has("_startDate") || + changedProps.has("_endDate") || + changedProps.has("_targetPickerValue") || + (!this._stateHistory && + (changedProps.has("_deviceEntityLookup") || + changedProps.has("_areaEntityLookup") || + changedProps.has("_areaDeviceLookup"))) ) { this._getHistory(); this._getStats(); } - - if (!changedProps.has("hass") && !changedProps.has("_entities")) { - return; - } - - const oldHass = changedProps.get("hass") as HomeAssistant | undefined; - if (!oldHass || oldHass.language !== this.hass.language) { - this.rtl = computeRTL(this.hass); - } } private _removeAll() { - this._targetPickerValue = undefined; + this._targetPickerValue = {}; this._updatePath(); } private async _getStats() { - const entityIds = this._getEntityIds(); - if (!entityIds) { + const statisticIds = this._getEntityIds(); + + if (statisticIds.length === 0) { + this._statisticsHistory = undefined; return; } - this._getStatistics(entityIds); - } - private async _getStatistics(statisticIds: string[]): Promise { - try { - const statistics = await fetchStatistics( - this.hass!, - this._startDate, - this._endDate, - statisticIds, - "hour", - undefined, - ["mean", "state"] - ); + const statistics = await fetchStatistics( + this.hass!, + this._startDate, + this._endDate, + statisticIds, + "hour", + undefined, + ["mean", "state"] + ); - // Maintain the statistic id ordering - const orderedStatistics: Statistics = {}; - statisticIds.forEach((id) => { - if (id in statistics) { - orderedStatistics[id] = statistics[id]; - } - }); + // Maintain the statistic id ordering + const orderedStatistics: Statistics = {}; + statisticIds.forEach((id) => { + if (id in statistics) { + orderedStatistics[id] = statistics[id]; + } + }); - // Convert statistics to HistoryResult format - const statsHistoryStates: HistoryStates = {}; - Object.entries(orderedStatistics).forEach(([key, value]) => { - const entityHistoryStates: EntityHistoryState[] = value.map((e) => ({ - s: e.mean != null ? e.mean.toString() : e.state!.toString(), - lc: e.start / 1000, - a: {}, - lu: e.start / 1000, - })); - statsHistoryStates[key] = entityHistoryStates; - }); + // Convert statistics to HistoryResult format + const statsHistoryStates: HistoryStates = {}; + Object.entries(orderedStatistics).forEach(([key, value]) => { + const entityHistoryStates: EntityHistoryState[] = value.map((e) => ({ + s: e.mean != null ? e.mean.toString() : e.state!.toString(), + lc: e.start / 1000, + a: {}, + lu: e.start / 1000, + })); + statsHistoryStates[key] = entityHistoryStates; + }); - const { numeric_device_classes: sensorNumericDeviceClasses } = - await getSensorNumericDeviceClasses(this.hass); + const { numeric_device_classes: sensorNumericDeviceClasses } = + await getSensorNumericDeviceClasses(this.hass); - this._statisticsHistory = computeHistory( - this.hass, - statsHistoryStates, - this.hass.localize, - sensorNumericDeviceClasses, - true - ); - // remap states array to statistics array - (this._statisticsHistory?.line || []).forEach((item) => { - item.data.forEach((data) => { - data.statistics = data.states; - data.states = []; - }); + this._statisticsHistory = computeHistory( + this.hass, + statsHistoryStates, + this.hass.localize, + sensorNumericDeviceClasses, + true + ); + // remap states array to statistics array + (this._statisticsHistory?.line || []).forEach((item) => { + item.data.forEach((data) => { + data.statistics = data.states; + data.states = []; }); - } catch (err) { - this._statisticsHistory = undefined; - } + }); } private async _getHistory() { - if (!this._targetPickerValue) { - return; - } - this._isLoading = true; const entityIds = this._getEntityIds(); - if (entityIds === undefined) { - this._isLoading = false; + if (entityIds.length === 0) { this._stateHistory = undefined; return; } - if (entityIds.length === 0) { - this._isLoading = false; - this._stateHistory = { line: [], timeline: [] }; - return; - } + this._isLoading = true; if (this._subscribed) { this._unsubscribeHistory(); @@ -512,84 +516,100 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { } } - private _getEntityIds(): string[] | undefined { - if ( - !this._targetPickerValue || - this._deviceEntityLookup === undefined || - this._areaEntityLookup === undefined || - this._areaDeviceLookup === undefined - ) { - return undefined; - } + private _getEntityIds(): string[] { + return this.__getEntityIds( + this._targetPickerValue, + this._deviceEntityLookup, + this._areaEntityLookup, + this._areaDeviceLookup + ); + } - const entityIds = new Set(); - let { - area_id: searchingAreaId, - device_id: searchingDeviceId, - entity_id: searchingEntityId, - } = this._targetPickerValue; - - if (searchingAreaId) { - searchingAreaId = ensureArray(searchingAreaId); - for (const singleSearchingAreaId of searchingAreaId) { - const foundEntities = this._areaEntityLookup[singleSearchingAreaId]; - if (foundEntities?.length) { - for (const foundEntity of foundEntities) { - if (foundEntity.entity_category === null) { - entityIds.add(foundEntity.entity_id); + private __getEntityIds = memoizeOne( + ( + targetPickerValue: HassServiceTarget, + deviceEntityLookup: DeviceEntityLookup | undefined, + areaEntityLookup: AreaEntityLookup | undefined, + areaDeviceLookup: AreaDeviceLookup | undefined + ): string[] => { + if ( + !targetPickerValue || + deviceEntityLookup === undefined || + areaEntityLookup === undefined || + areaDeviceLookup === undefined + ) { + return []; + } + + const entityIds = new Set(); + let { + area_id: searchingAreaId, + device_id: searchingDeviceId, + entity_id: searchingEntityId, + } = targetPickerValue; + + if (searchingAreaId) { + searchingAreaId = ensureArray(searchingAreaId); + for (const singleSearchingAreaId of searchingAreaId) { + const foundEntities = areaEntityLookup[singleSearchingAreaId]; + if (foundEntities?.length) { + for (const foundEntity of foundEntities) { + if (foundEntity.entity_category === null) { + entityIds.add(foundEntity.entity_id); + } } } - } - - const foundDevices = this._areaDeviceLookup[singleSearchingAreaId]; - if (!foundDevices?.length) { - continue; - } - for (const foundDevice of foundDevices) { - const foundDeviceEntities = this._deviceEntityLookup[foundDevice.id]; - if (!foundDeviceEntities?.length) { + const foundDevices = areaDeviceLookup[singleSearchingAreaId]; + if (!foundDevices?.length) { continue; } - for (const foundDeviceEntity of foundDeviceEntities) { - if ( - (!foundDeviceEntity.area_id || - foundDeviceEntity.area_id === singleSearchingAreaId) && - foundDeviceEntity.entity_category === null - ) { - entityIds.add(foundDeviceEntity.entity_id); + for (const foundDevice of foundDevices) { + const foundDeviceEntities = deviceEntityLookup[foundDevice.id]; + if (!foundDeviceEntities?.length) { + continue; + } + + for (const foundDeviceEntity of foundDeviceEntities) { + if ( + (!foundDeviceEntity.area_id || + foundDeviceEntity.area_id === singleSearchingAreaId) && + foundDeviceEntity.entity_category === null + ) { + entityIds.add(foundDeviceEntity.entity_id); + } } } } } - } - if (searchingDeviceId) { - searchingDeviceId = ensureArray(searchingDeviceId); - for (const singleSearchingDeviceId of searchingDeviceId) { - const foundEntities = this._deviceEntityLookup[singleSearchingDeviceId]; - if (!foundEntities?.length) { - continue; - } + if (searchingDeviceId) { + searchingDeviceId = ensureArray(searchingDeviceId); + for (const singleSearchingDeviceId of searchingDeviceId) { + const foundEntities = deviceEntityLookup[singleSearchingDeviceId]; + if (!foundEntities?.length) { + continue; + } - for (const foundEntity of foundEntities) { - if (foundEntity.entity_category === null) { - entityIds.add(foundEntity.entity_id); + for (const foundEntity of foundEntities) { + if (foundEntity.entity_category === null) { + entityIds.add(foundEntity.entity_id); + } } } } - } - if (searchingEntityId) { - searchingEntityId = ensureArray(searchingEntityId); - for (const singleSearchingEntityId of searchingEntityId) { - entityIds.add(singleSearchingEntityId); + if (searchingEntityId) { + searchingEntityId = ensureArray(searchingEntityId); + for (const singleSearchingEntityId of searchingEntityId) { + entityIds.add(singleSearchingEntityId); + } } - } - return [...entityIds]; - } + return [...entityIds]; + } + ); private _dateRangeChanged(ev) { this._startDate = ev.detail.startDate; @@ -611,20 +631,18 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { private _updatePath() { const params: Record = {}; - if (this._targetPickerValue) { - if (this._targetPickerValue.entity_id) { - params.entity_id = ensureArray(this._targetPickerValue.entity_id).join( - "," - ); - } - if (this._targetPickerValue.area_id) { - params.area_id = ensureArray(this._targetPickerValue.area_id).join(","); - } - if (this._targetPickerValue.device_id) { - params.device_id = ensureArray(this._targetPickerValue.device_id).join( - "," - ); - } + if (this._targetPickerValue.entity_id) { + params.entity_id = ensureArray(this._targetPickerValue.entity_id).join( + "," + ); + } + if (this._targetPickerValue.area_id) { + params.area_id = ensureArray(this._targetPickerValue.area_id).join(","); + } + if (this._targetPickerValue.device_id) { + params.device_id = ensureArray(this._targetPickerValue.device_id).join( + "," + ); } if (this._startDate) { @@ -639,23 +657,35 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { } private _downloadHistory() { + const entities = this._getEntityIds(); + if (entities.length === 0 || !this._mungedStateHistory) { + showAlertDialog(this, { + title: this.hass.localize("ui.panel.history.download_data_error"), + text: this.hass.localize("ui.panel.history.error_no_data"), + warning: true, + }); + return; + } + const csv: string[] = ["entity_id,state,last_changed\n"]; const formatDate = (number) => new Date(number).toISOString(); - for (const line of this._mungedStateHistory!.line) { + for (const line of this._mungedStateHistory.line) { for (const entity of line.data) { const entityId = entity.entity_id; - for (const data of [entity.states, entity.statistics]) { - if (!data) { - continue; - } - for (const s of data) { + + if (entity.statistics) { + for (const s of entity.statistics) { csv.push(`${entityId},${s.state},${formatDate(s.last_changed)}\n`); } } + + for (const s of entity.states) { + csv.push(`${entityId},${s.state},${formatDate(s.last_changed)}\n`); + } } } - for (const timeline of this._mungedStateHistory!.timeline) { + for (const timeline of this._mungedStateHistory.timeline) { const entityId = timeline.entity_id; for (const s of timeline.data) { csv.push(`${entityId},${s.state},${formatDate(s.last_changed)}\n`); @@ -710,6 +740,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) { ha-date-range-picker { margin-right: 0; margin-inline-end: 0; + margin-inline-start: initial; width: 100%; } } diff --git a/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts index b24df99a3264..f7512fc981ef 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts @@ -215,6 +215,8 @@ class HuiEnergyCarbonGaugeCard ha-svg-icon { position: absolute; right: 4px; + inset-inline-end: 4px; + inset-inline-start: initial; top: 4px; color: var(--secondary-text-color); } diff --git a/src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts index 1acb16fef7bc..558762ca15b8 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts @@ -192,6 +192,8 @@ class HuiEnergyGridGaugeCard ha-svg-icon { position: absolute; right: 4px; + inset-inline-end: 4px; + inset-inline-start: initial; top: 4px; color: var(--secondary-text-color); } diff --git a/src/panels/lovelace/cards/energy/hui-energy-self-sufficiency-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-self-sufficiency-gauge-card.ts index 1f87f7167ff8..e39450c4ff3c 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-self-sufficiency-gauge-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-self-sufficiency-gauge-card.ts @@ -255,6 +255,8 @@ class HuiEnergySelfSufficiencyGaugeCard ha-svg-icon { position: absolute; right: 4px; + inset-inline-end: 4px; + inset-inline-start: initial; top: 4px; color: var(--secondary-text-color); } diff --git a/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts index fb70fbcca4b2..78ffa6c75078 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts @@ -194,6 +194,8 @@ class HuiEnergySolarGaugeCard ha-svg-icon { position: absolute; right: 4px; + inset-inline-end: 4px; + inset-inline-start: initial; top: 4px; color: var(--secondary-text-color); } diff --git a/src/panels/lovelace/cards/hui-button-card.ts b/src/panels/lovelace/cards/hui-button-card.ts index 703ad164cfff..b7eddb962c15 100644 --- a/src/panels/lovelace/cards/hui-button-card.ts +++ b/src/panels/lovelace/cards/hui-button-card.ts @@ -187,8 +187,6 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { return html` 144) { - // if showing > 6 days in the history trail, show the full - // date and time - p.tooltip = formatDateTime(t, this.hass.locale, this.hass.config); - } else if (isToday(t)) { - p.tooltip = formatTimeWithSeconds( - t, - this.hass.locale, - this.hass.config - ); - } else { - p.tooltip = formatTimeWeekday( - t, - this.hass.locale, - this.hass.config - ); - } + p.timestamp = new Date(entityState.lu * 1000); points.push(p); } + + const entityConfig = this._configEntities?.find( + (e) => e.entity === entityId + ); + const name = + entityConfig?.name ?? + (entityId in this.hass.states + ? computeStateName(this.hass.states[entityId]) + : entityId); + paths.push({ points, + name, + fullDatetime: (config.hours_to_show ?? DEFAULT_HOURS_TO_SHOW) > 144, color: this._getColor(entityId), gradualOpacity: 0.8, }); diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index 98faa89eaf1a..b1c279091a53 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -524,6 +524,8 @@ export class HuiTileCard extends LitElement implements LovelaceCard { position: absolute; top: -3px; right: -3px; + inset-inline-end: -3px; + inset-inline-start: initial; } .icon-container:not([role="button"]) { pointer-events: none; diff --git a/src/panels/lovelace/cards/hui-weather-forecast-card.ts b/src/panels/lovelace/cards/hui-weather-forecast-card.ts index 781f02bd42fd..3d0d2ac047f2 100644 --- a/src/panels/lovelace/cards/hui-weather-forecast-card.ts +++ b/src/panels/lovelace/cards/hui-weather-forecast-card.ts @@ -612,11 +612,6 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { text-align: center; } - /* ============= RTL ============= */ - :host([rtl]) .temp-attribute { - text-align: left; - } - /* ============= NARROW ============= */ :host([narrow]) .icon-image { diff --git a/src/panels/lovelace/components/hui-generic-entity-row.ts b/src/panels/lovelace/components/hui-generic-entity-row.ts index f58677657edd..262459b2bee8 100644 --- a/src/panels/lovelace/components/hui-generic-entity-row.ts +++ b/src/panels/lovelace/components/hui-generic-entity-row.ts @@ -13,7 +13,6 @@ import { DOMAINS_INPUT_ROW } from "../../../common/const"; import { toggleAttribute } from "../../../common/dom/toggle_attribute"; import { computeDomain } from "../../../common/entity/compute_domain"; import { computeStateName } from "../../../common/entity/compute_state_name"; -import { computeRTL } from "../../../common/util/compute_rtl"; import "../../../components/entity/state-badge"; import "../../../components/ha-relative-time"; import { ActionHandlerEvent } from "../../../data/lovelace/action_handler"; @@ -186,9 +185,6 @@ export class HuiGenericEntityRow extends LitElement { "no-secondary", !this.secondaryText && !this.config?.secondary_info ); - if (changedProps.has("hass")) { - toggleAttribute(this, "rtl", computeRTL(this.hass!)); - } } private _handleAction(ev: ActionHandlerEvent) { @@ -233,22 +229,11 @@ export class HuiGenericEntityRow extends LitElement { state-badge { flex: 0 0 40px; } - :host([rtl]) .flex { - margin-left: 0; - margin-right: 16px; - } - :host([rtl]) .flex ::slotted(*) { - margin-left: 0; - margin-right: 8px; - } .pointer { cursor: pointer; } .state { - text-align: right; - } - .state.rtl { - text-align: left; + text-align: var(--float-end); } .value { direction: ltr; diff --git a/src/panels/lovelace/editor/card-editor/hui-card-preview.ts b/src/panels/lovelace/editor/card-editor/hui-card-preview.ts index a1c95829009f..72a652834afc 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-preview.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-preview.ts @@ -1,6 +1,5 @@ import { PropertyValues, ReactiveElement } from "lit"; import { property } from "lit/decorators"; -import { computeRTL } from "../../../../common/util/compute_rtl"; import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; import { HomeAssistant } from "../../../../types"; import { createCardElement } from "../../create-element/create-card-element"; @@ -70,13 +69,6 @@ export class HuiCardPreview extends ReactiveElement { } if (changedProperties.has("hass")) { - const oldHass = changedProperties.get("hass") as - | HomeAssistant - | undefined; - if (!oldHass || oldHass.language !== this.hass!.language) { - this.style.direction = computeRTL(this.hass!) ? "rtl" : "ltr"; - } - if (this._element) { this._element.hass = this.hass; } diff --git a/src/panels/lovelace/editor/card-editor/hui-entity-picker-table.ts b/src/panels/lovelace/editor/card-editor/hui-entity-picker-table.ts index 997a4ec22dac..51f42252c1f0 100644 --- a/src/panels/lovelace/editor/card-editor/hui-entity-picker-table.ts +++ b/src/panels/lovelace/editor/card-editor/hui-entity-picker-table.ts @@ -3,7 +3,6 @@ import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; import type { HASSDomEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import "../../../../components/data-table/ha-data-table"; import type { DataTableColumnContainer, @@ -33,7 +32,6 @@ export class HuiEntityPickerTable extends LitElement { .id=${"entity_id"} .columns=${this._columns(this.narrow!)} .data=${this.entities} - .dir=${computeRTLDirection(this.hass)} .searchLabel=${this.hass.localize( "ui.panel.lovelace.unused_entities.search" )} diff --git a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts index 8e6eb4bbecc2..106b0b97ec40 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts @@ -18,7 +18,6 @@ import { } from "superstruct"; import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event"; import { customType } from "../../../../common/structs/is-custom-type"; -import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import "../../../../components/ha-card"; import "../../../../components/ha-formfield"; import "../../../../components/ha-icon"; @@ -265,7 +264,6 @@ export class HuiEntitiesCardEditor .label=${this.hass.localize( "ui.panel.lovelace.editor.card.entities.show_header_toggle" )} - .dir=${computeRTLDirection(this.hass)} >
    @@ -171,20 +169,13 @@ export class HuiUnusedEntities extends LitElement { } .fab { position: sticky; - float: right; + float: var(--float-end); right: calc(16px + env(safe-area-inset-right)); bottom: calc(16px + env(safe-area-inset-bottom)); + inset-inline-end: calc(16px + env(safe-area-inset-right)); + inset-inline-start: initial; z-index: 1; } - .fab.rtl { - right: initial; - left: 0; - bottom: 0; - padding-right: 16px; - padding-left: calc(16px + env(safe-area-inset-left)); - padding-inline-end: 16px; - padding-inline-start: calc(16px + env(safe-area-inset-left)); - } ha-fab { position: relative; bottom: calc(-80px - env(safe-area-inset-bottom)); diff --git a/src/panels/lovelace/entity-rows/hui-input-number-entity-row.ts b/src/panels/lovelace/entity-rows/hui-input-number-entity-row.ts index 65c0ef1e2774..f6888a5d02af 100644 --- a/src/panels/lovelace/entity-rows/hui-input-number-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-input-number-entity-row.ts @@ -7,7 +7,6 @@ import { nothing, } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { computeRTLDirection } from "../../../common/util/compute_rtl"; import { debounce } from "../../../common/util/debounce"; import "../../../components/ha-slider"; import "../../../components/ha-textfield"; @@ -86,7 +85,6 @@ class HuiInputNumberEntityRow extends LitElement implements LovelaceRow {
    ; + private _loading = false; + public connectedCallback(): void { super.connectedCallback(); if ( @@ -162,7 +164,7 @@ export class LovelacePanel extends LitElement { protected willUpdate(changedProps: PropertyValues) { super.willUpdate(changedProps); - if (!this.lovelace && this._panelState !== "error") { + if (!this.lovelace && this._panelState !== "error" && !this._loading) { this._fetchConfig(false); } } @@ -233,6 +235,8 @@ export class LovelacePanel extends LitElement { } private async _fetchConfig(forceDiskRefresh: boolean) { + this._loading = true; + let conf: LovelaceConfig; let rawConf: LovelaceRawConfig | undefined; let confMode: Lovelace["mode"] = this.panel!.config.mode; @@ -301,6 +305,7 @@ export class LovelacePanel extends LitElement { rawConf = DEFAULT_CONFIG; confMode = "generated"; } finally { + this._loading = false; // Ignore updates for another 2 seconds. if (this.lovelace && this.lovelace.mode === "yaml") { setTimeout(() => { diff --git a/src/panels/lovelace/views/hui-masonry-view.ts b/src/panels/lovelace/views/hui-masonry-view.ts index 16f3d60882f6..bcc87214e3c6 100644 --- a/src/panels/lovelace/views/hui-masonry-view.ts +++ b/src/panels/lovelace/views/hui-masonry-view.ts @@ -8,9 +8,7 @@ import { TemplateResult, } from "lit"; import { property, state } from "lit/decorators"; -import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../../../common/dom/fire_event"; -import { computeRTL } from "../../../common/util/compute_rtl"; import { nextRender } from "../../../common/util/render-status"; import "../../../components/entity/ha-state-label-badge"; import "../../../components/ha-svg-icon"; @@ -97,9 +95,6 @@ export class MasonryView extends LitElement implements LovelaceViewElement { )} extended @click=${this._addCard} - class=${classMap({ - rtl: computeRTL(this.hass!), - })} > @@ -338,14 +333,11 @@ export class MasonryView extends LitElement implements LovelaceViewElement { position: fixed; right: calc(16px + env(safe-area-inset-right)); bottom: calc(16px + env(safe-area-inset-bottom)); + inset-inline-end: calc(16px + env(safe-area-inset-right)); + inset-inline-start: initial; z-index: 1; } - ha-fab.rtl { - right: auto; - left: calc(16px + env(safe-area-inset-left)); - } - @media (max-width: 500px) { .column > * { margin-left: 0; diff --git a/src/panels/lovelace/views/hui-panel-view.ts b/src/panels/lovelace/views/hui-panel-view.ts index 2d36ba5178a6..aae451deaa5e 100644 --- a/src/panels/lovelace/views/hui-panel-view.ts +++ b/src/panels/lovelace/views/hui-panel-view.ts @@ -137,12 +137,9 @@ export class PanelView extends LitElement implements LovelaceViewElement { right: calc(16px + env(safe-area-inset-right)); bottom: calc(16px + env(safe-area-inset-bottom)); z-index: 1; - } - - ha-fab.rtl { - float: left; - right: auto; - left: calc(16px + env(safe-area-inset-left)); + float: var(--float-end); + inset-inline-end: calc(16px + env(safe-area-inset-right)); + inset-inline-start: initial; } `; } diff --git a/src/panels/lovelace/views/hui-sidebar-view.ts b/src/panels/lovelace/views/hui-sidebar-view.ts index f6085637da04..1e8442880b28 100644 --- a/src/panels/lovelace/views/hui-sidebar-view.ts +++ b/src/panels/lovelace/views/hui-sidebar-view.ts @@ -8,9 +8,7 @@ import { html, } from "lit"; import { property, state } from "lit/decorators"; -import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../../../common/dom/fire_event"; -import { computeRTL } from "../../../common/util/compute_rtl"; import type { LovelaceViewElement } from "../../../data/lovelace"; import type { LovelaceViewConfig } from "../../../data/lovelace/config/view"; import type { HomeAssistant } from "../../../types"; @@ -100,9 +98,6 @@ export class SideBarView extends LitElement implements LovelaceViewElement { )} extended @click=${this._addCard} - class=${classMap({ - rtl: computeRTL(this.hass!), - })} > @@ -241,13 +236,10 @@ export class SideBarView extends LitElement implements LovelaceViewElement { position: fixed; right: calc(16px + env(safe-area-inset-right)); bottom: calc(16px + env(safe-area-inset-bottom)); + inset-inline-end: calc(16px + env(safe-area-inset-right)); + inset-inline-start: initial; z-index: 1; } - - ha-fab.rtl { - right: auto; - left: calc(16px + env(safe-area-inset-left)); - } `; } } diff --git a/src/panels/todo/ha-panel-todo.ts b/src/panels/todo/ha-panel-todo.ts index 03a3e2b1fdb6..b1dcee7fb4de 100644 --- a/src/panels/todo/ha-panel-todo.ts +++ b/src/panels/todo/ha-panel-todo.ts @@ -430,6 +430,8 @@ class PanelTodo extends LitElement { position: fixed; right: 16px; bottom: 16px; + inset-inline-end: 16px; + inset-inline-start: initial; } `, ]; diff --git a/src/resources/ha-sidebar-edit-style.ts b/src/resources/ha-sidebar-edit-style.ts index 50bf37c75616..91d70bfa2b61 100644 --- a/src/resources/ha-sidebar-edit-style.ts +++ b/src/resources/ha-sidebar-edit-style.ts @@ -62,22 +62,16 @@ export const sidebarEditStyle = css` position: absolute; top: 0; right: 4px; + inset-inline-end: 4px; + inset-inline-start: initial; --mdc-icon-button-size: 40px; } - :host([rtl]) .show-panel { - right: initial; - left: 4px; - } - .hide-panel { top: 4px; right: 8px; - } - - :host([rtl]) .hide-panel { - right: initial; - left: 8px; + inset-inline-end: 8px; + inset-inline-start: initial; } :host([expanded]) .hide-panel { diff --git a/src/state-summary/state-card-alert.ts b/src/state-summary/state-card-alert.ts index e6ee8d3e05c3..fb85350993ab 100755 --- a/src/state-summary/state-card-alert.ts +++ b/src/state-summary/state-card-alert.ts @@ -2,7 +2,6 @@ import type { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { stateActive } from "../common/entity/state_active"; -import { computeRTL } from "../common/util/compute_rtl"; import "../components/entity/ha-entity-toggle"; import "../components/entity/state-info"; import { haStyle } from "../resources/styles"; @@ -16,9 +15,6 @@ class StateCardAlert extends LitElement { @property({ type: Boolean }) public inDialog = false; - // property used only in CSS - @property({ type: Boolean, reflect: true }) public rtl = false; - protected render(): TemplateResult { return html`
    @@ -40,18 +36,6 @@ class StateCardAlert extends LitElement { `; } - protected updated(changedProps) { - super.updated(changedProps); - 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); - } - } - static get styles(): CSSResultGroup { return [ haStyle, @@ -69,7 +53,6 @@ class StateCardAlert extends LitElement { overflow-wrap: break-word; display: flex; align-items: center; - direction: ltr; } ha-entity-toggle { margin: -4px -16px -4px 0; diff --git a/src/state-summary/state-card-display.ts b/src/state-summary/state-card-display.ts index 7263ab30bdd0..fe232520bf6b 100755 --- a/src/state-summary/state-card-display.ts +++ b/src/state-summary/state-card-display.ts @@ -3,7 +3,6 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { computeDomain } from "../common/entity/compute_domain"; -import { computeRTL } from "../common/util/compute_rtl"; import "../components/entity/state-info"; import { isUnavailableState } from "../data/entity"; import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../data/sensor"; @@ -53,18 +52,6 @@ class StateCardDisplay extends LitElement { `; } - protected updated(changedProps) { - super.updated(changedProps); - 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); - } - } - static get styles(): CSSResultGroup { return [ haStyle, @@ -83,7 +70,6 @@ class StateCardDisplay extends LitElement { word-break: break-word; display: flex; align-items: center; - direction: ltr; justify-content: flex-end; } .state.has-unit_of_measurement { diff --git a/src/state-summary/state-card-input_number.ts b/src/state-summary/state-card-input_number.ts index c5b2e177ecfe..884253d3e67e 100644 --- a/src/state-summary/state-card-input_number.ts +++ b/src/state-summary/state-card-input_number.ts @@ -1,7 +1,6 @@ import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; -import { computeRTLDirection } from "../common/util/compute_rtl"; import { debounce } from "../common/util/debounce"; import "../components/entity/state-info"; import "../components/ha-slider"; @@ -58,7 +57,6 @@ class StateCardInputNumber extends LitElement {
    = 5" - checksum: f8d20e4e18ea9cf17c95546f878213d57f3eb9fd71a01ebae1b5d1689c0da7c35f0fe39137994717e600c3d12cfaa754ab3d4cdd854a6cf4d0a907ae898bc9a5 + checksum: 2d70f0b9fa6afc7f259877acd7e69c14f0104a69a019efb594d5de603e12b982e4a96fec5b169005fab244655951f85bff77f469d9aeadb885974f963a7d9996 languageName: node linkType: hard @@ -9437,10 +9437,10 @@ __metadata: languageName: node linkType: hard -"hls.js@npm:1.5.2": - version: 1.5.2 - resolution: "hls.js@npm:1.5.2" - checksum: bf92e8f005eb4e906bc19a0baf6428df4cbfd344b517de70496b953d77f1231324d26b4dc88c375d3cd481311601c1d1d5aea34ec4e0fc96e9df22b9b0e28a84 +"hls.js@npm:1.5.3": + version: 1.5.3 + resolution: "hls.js@npm:1.5.3" + checksum: c5b7cb6fddd6ad425a82e1b8b4f818552e6e242254b35c7d7c7b9d3beb4dd40cc85d8bbc32a5d1b57031305d7d9f4973f9d0f80b70351bb8597393f4629072fc languageName: node linkType: hard @@ -9546,8 +9546,8 @@ __metadata: "@types/tar": "npm:6.1.11" "@types/ua-parser-js": "npm:0.7.39" "@types/webspeechapi": "npm:0.0.29" - "@typescript-eslint/eslint-plugin": "npm:6.19.1" - "@typescript-eslint/parser": "npm:6.19.1" + "@typescript-eslint/eslint-plugin": "npm:6.20.0" + "@typescript-eslint/parser": "npm:6.20.0" "@vaadin/combo-box": "npm:24.3.4" "@vaadin/vaadin-themable-mixin": "npm:24.3.4" "@vibrant/color": "npm:3.2.1-alpha.1" @@ -9580,7 +9580,7 @@ __metadata: eslint-plugin-disable: "npm:2.0.3" eslint-plugin-import: "npm:2.29.1" eslint-plugin-lit: "npm:1.11.0" - eslint-plugin-lit-a11y: "npm:4.1.1" + eslint-plugin-lit-a11y: "npm:4.1.2" eslint-plugin-unused-imports: "npm:3.0.0" eslint-plugin-wc: "npm:2.0.4" fancy-log: "npm:2.0.0" @@ -9594,10 +9594,10 @@ __metadata: gulp-merge-json: "npm:2.1.2" gulp-rename: "npm:2.0.0" gulp-zopfli-green: "npm:6.0.1" - hls.js: "npm:1.5.2" + hls.js: "npm:1.5.3" home-assistant-js-websocket: "npm:9.1.0" html-minifier-terser: "npm:7.2.0" - husky: "npm:9.0.6" + husky: "npm:9.0.7" idb-keyval: "npm:6.2.1" instant-mocha: "npm:1.5.2" intl-messageformat: "npm:10.5.11" @@ -9879,12 +9879,12 @@ __metadata: languageName: node linkType: hard -"husky@npm:9.0.6": - version: 9.0.6 - resolution: "husky@npm:9.0.6" +"husky@npm:9.0.7": + version: 9.0.7 + resolution: "husky@npm:9.0.7" bin: husky: bin.js - checksum: e198c90a59d460cf860c33e0a4c3927ecfb645d4fd4c2de3fbcd5fb56b858a923af452508d549f6ed020bb48de08290912cd77c006dd2a83e551c24c17340d5b + checksum: c5673acb9f224b6d2b36ee4892c6b99dbe9a077b041aee6013efeacb71d2d2cbe6ab7fac061f68fa5653c7dbe93aacfeed69eea01f755688e4c36c4044dcc5c0 languageName: node linkType: hard