diff --git a/pyproject.toml b/pyproject.toml index 8d2173dca9..5d879a754a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250502.1" +version = "20250506.0" license = "Apache-2.0" license-files = ["LICENSE*"] description = "The Home Assistant frontend" diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index 39447ca320..66d7a3ba60 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -603,7 +603,7 @@ export class HaDataTable extends LitElement { .map( ([key2, column2], i) => html`${i !== 0 - ? " ⸱ " + ? " · " : nothing}${column2.template ? column2.template(row) : row[key2]}` diff --git a/src/components/entity/ha-entity-combo-box.ts b/src/components/entity/ha-entity-combo-box.ts index f0f3ede62b..db18168c3a 100644 --- a/src/components/entity/ha-entity-combo-box.ts +++ b/src/components/entity/ha-entity-combo-box.ts @@ -5,7 +5,6 @@ import type { HassEntity } from "home-assistant-js-websocket"; import type { PropertyValues, TemplateResult } from "lit"; import { html, LitElement, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; -import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../common/dom/fire_event"; import { computeAreaName } from "../../common/entity/compute_area_name"; @@ -30,28 +29,17 @@ import "../ha-icon-button"; import "../ha-svg-icon"; import "./state-badge"; -const FAKE_ENTITY: HassEntity = { - entity_id: "", - state: "", - last_changed: "", - last_updated: "", - context: { id: "", user_id: null, parent_id: null }, - attributes: {}, -}; - -interface EntityComboBoxItem extends HassEntity { +interface EntityComboBoxItem { // Force empty label to always display empty value by default in the search field + id: string; label: ""; primary: string; secondary?: string; - translated_domain?: string; - show_entity_id?: boolean; - entity_name?: string; - area_name?: string; - device_name?: string; - friendly_name?: string; + domain_name?: string; + search_labels?: string[]; sorting_label?: string; icon_path?: string; + stateObj?: HassEntity; } export type HaEntityComboBoxEntityFilterFunc = (entity: HassEntity) => boolean; @@ -59,22 +47,6 @@ export type HaEntityComboBoxEntityFilterFunc = (entity: HassEntity) => boolean; const CREATE_ID = "___create-new-entity___"; const NO_ENTITIES_ID = "___no-entities___"; -const DOMAIN_STYLE = styleMap({ - fontSize: "var(--ha-font-size-s)", - fontWeight: "var(--ha-font-weight-normal)", - lineHeight: "var(--ha-line-height-normal)", - alignSelf: "flex-end", - maxWidth: "30%", - textOverflow: "ellipsis", - overflow: "hidden", - whiteSpace: "nowrap", -}); - -const ENTITY_ID_STYLE = styleMap({ - fontFamily: "var(--ha-font-family-code)", - fontSize: "var(--ha-font-size-xs)", -}); - @customElement("ha-entity-combo-box") export class HaEntityComboBox extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -177,33 +149,41 @@ export class HaEntityComboBox extends LitElement { private _rowRenderer: ComboBoxLitRenderer = ( item, { index } - ) => html` - - ${item.icon_path - ? html`` - : html` - - `} - ${item.primary} - ${item.secondary - ? html`${item.secondary}` - : nothing} - ${item.entity_id && item.show_entity_id - ? html`${item.entity_id}` - : nothing} - ${item.translated_domain && !item.show_entity_id - ? html`
- ${item.translated_domain} -
` - : nothing} -
- `; + ) => { + const showEntityId = this.hass.userData?.showEntityIdPicker; + + return html` + + ${item.icon_path + ? html` + + ` + : html` + + `} + ${item.primary} + ${item.secondary + ? html`${item.secondary}` + : nothing} + ${item.stateObj && showEntityId + ? html` + + ${item.stateObj.entity_id} + + ` + : nothing} + ${item.domain_name && !showEntityId + ? html` +
${item.domain_name}
+ ` + : nothing} +
+ `; + }; private _getItems = memoizeOne( ( @@ -218,7 +198,7 @@ export class HaEntityComboBox extends LitElement { excludeEntities: this["excludeEntities"], createDomains: this["createDomains"] ): EntityComboBoxItem[] => { - let states: EntityComboBoxItem[] = []; + let items: EntityComboBoxItem[] = []; let entityIds = Object.keys(hass.states); @@ -236,9 +216,8 @@ export class HaEntityComboBox extends LitElement { ); return { - ...FAKE_ENTITY, + id: CREATE_ID + domain, label: "", - entity_id: CREATE_ID + domain, primary: primary, secondary: this.hass.localize( "ui.components.entity.entity-picker.new_entity" @@ -251,9 +230,8 @@ export class HaEntityComboBox extends LitElement { if (!entityIds.length) { return [ { - ...FAKE_ENTITY, + id: NO_ENTITIES_ID, label: "", - entity_id: NO_ENTITIES_ID, primary: this.hass!.localize( "ui.components.entity.entity-picker.no_entities" ), @@ -289,7 +267,7 @@ export class HaEntityComboBox extends LitElement { const isRTL = computeRTL(this.hass); - states = entityIds + items = entityIds .map((entityId) => { const stateObj = hass!.states[entityId]; @@ -300,28 +278,32 @@ export class HaEntityComboBox extends LitElement { const deviceName = device ? computeDeviceName(device) : undefined; const areaName = area ? computeAreaName(area) : undefined; + const domainName = domainToName( + this.hass.localize, + computeDomain(entityId) + ); + const primary = entityName || deviceName || entityId; const secondary = [areaName, entityName ? deviceName : undefined] .filter(Boolean) .join(isRTL ? " ◂ " : " ▸ "); - const translatedDomain = domainToName( - this.hass.localize, - computeDomain(entityId) - ); - return { - ...hass!.states[entityId], + id: entityId, label: "", primary: primary, secondary: secondary, - translated_domain: translatedDomain, - sorting_label: [deviceName, entityName].filter(Boolean).join("-"), - entity_name: entityName || deviceName, - area_name: areaName, - device_name: deviceName, - friendly_name: friendlyName, - show_entity_id: hass.userData?.showEntityIdPicker, + domain_name: domainName, + sorting_label: [deviceName, entityName].filter(Boolean).join("_"), + search_labels: [ + entityName, + deviceName, + areaName, + domainName, + friendlyName, + entityId, + ].filter(Boolean) as string[], + stateObj: stateObj, }; }) .sort((entityA, entityB) => @@ -333,41 +315,43 @@ export class HaEntityComboBox extends LitElement { ); if (includeDeviceClasses) { - states = states.filter( - (stateObj) => + items = items.filter( + (item) => // We always want to include the entity of the current value - stateObj.entity_id === this.value || - (stateObj.attributes.device_class && - includeDeviceClasses.includes(stateObj.attributes.device_class)) + item.id === this.value || + (item.stateObj?.attributes.device_class && + includeDeviceClasses.includes( + item.stateObj.attributes.device_class + )) ); } if (includeUnitOfMeasurement) { - states = states.filter( - (stateObj) => + items = items.filter( + (item) => // We always want to include the entity of the current value - stateObj.entity_id === this.value || - (stateObj.attributes.unit_of_measurement && + item.id === this.value || + (item.stateObj?.attributes.unit_of_measurement && includeUnitOfMeasurement.includes( - stateObj.attributes.unit_of_measurement + item.stateObj.attributes.unit_of_measurement )) ); } if (entityFilter) { - states = states.filter( - (stateObj) => + items = items.filter( + (item) => // We always want to include the entity of the current value - stateObj.entity_id === this.value || entityFilter!(stateObj) + item.id === this.value || + (item.stateObj && entityFilter!(item.stateObj)) ); } - if (!states.length) { + if (!items.length) { return [ { - ...FAKE_ENTITY, + id: NO_ENTITIES_ID, label: "", - entity_id: NO_ENTITIES_ID, primary: this.hass!.localize( "ui.components.entity.entity-picker.no_match" ), @@ -378,10 +362,10 @@ export class HaEntityComboBox extends LitElement { } if (createItems?.length) { - states.push(...createItems); + items.push(...createItems); } - return states; + return items; } ); @@ -424,7 +408,7 @@ export class HaEntityComboBox extends LitElement { protected render(): TemplateResult { return html` - Fuse.createIndex( - [ - "entity_name", - "device_name", - "area_name", - "translated_domain", - "friendly_name", // for backwards compatibility - "entity_id", // for technical search - ], - states - ) + Fuse.createIndex(["search_labels"], states) ); private _filterChanged(ev: CustomEvent): void { @@ -503,9 +477,8 @@ export class HaEntityComboBox extends LitElement { if (results.length === 0) { target.filteredItems = [ { - ...FAKE_ENTITY, + id: NO_ENTITIES_ID, label: "", - entity_id: NO_ENTITIES_ID, primary: this.hass!.localize( "ui.components.entity.entity-picker.no_match" ), diff --git a/src/components/entity/ha-statistic-combo-box.ts b/src/components/entity/ha-statistic-combo-box.ts index 313bececcb..30749279ae 100644 --- a/src/components/entity/ha-statistic-combo-box.ts +++ b/src/components/entity/ha-statistic-combo-box.ts @@ -1,11 +1,10 @@ -import { mdiChartLine, mdiShape } from "@mdi/js"; +import { mdiChartLine, mdiHelpCircle, mdiShape } from "@mdi/js"; import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import Fuse from "fuse.js"; import type { HassEntity } from "home-assistant-js-websocket"; import type { PropertyValues, TemplateResult } from "lit"; import { html, LitElement, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; -import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; import { ensureArray } from "../../common/array/ensure-array"; import { fireEvent } from "../../common/dom/fire_event"; @@ -26,31 +25,27 @@ import type { HaComboBox } from "../ha-combo-box"; import "../ha-combo-box-item"; import "../ha-svg-icon"; import "./state-badge"; +import { documentationUrl } from "../../util/documentation-url"; type StatisticItemType = "entity" | "external" | "no_state"; interface StatisticItem { + // Force empty label to always display empty value by default in the search field id: string; + statistic_id?: string; label: ""; primary: string; secondary?: string; - show_entity_id?: boolean; - entity_name?: string; - area_name?: string; - device_name?: string; - friendly_name?: string; + search_labels?: string[]; sorting_label?: string; - state?: HassEntity; + icon_path?: string; type?: StatisticItemType; - iconPath?: string; + stateObj?: HassEntity; } -const TYPE_ORDER = ["entity", "external", "no_state"] as StatisticItemType[]; +const MISSING_ID = "___missing-entity___"; -const ENTITY_ID_STYLE = styleMap({ - fontFamily: "var(--ha-font-family-code)", - fontSize: "11px", -}); +const TYPE_ORDER = ["entity", "external", "no_state"] as StatisticItemType[]; @customElement("ha-statistic-combo-box") export class HaStatisticComboBox extends LitElement { @@ -131,37 +126,39 @@ export class HaStatisticComboBox extends LitElement { private _rowRenderer: ComboBoxLitRenderer = ( item, { index } - ) => html` - - ${!item.state - ? html` - - ` - : html` - - `} - - ${item.primary} - ${item.secondary - ? html`${item.secondary}` - : nothing} - ${item.id && item.show_entity_id - ? html` - - ${item.id} - - ` - : nothing} - - `; + ) => { + const showEntityId = this.hass.userData?.showEntityIdPicker; + return html` + + ${item.icon_path + ? html` + + ` + : item.stateObj + ? html` + + ` + : nothing} + ${item.primary} + ${item.secondary + ? html`${item.secondary}` + : nothing} + ${item.id && showEntityId + ? html` + ${item.statistic_id} + ` + : nothing} + + `; + }; private _getItems = memoizeOne( ( @@ -249,19 +246,22 @@ export class HaStatisticComboBox extends LitElement { label: "", type, sorting_label: label, - iconPath: mdiShape, + search_labels: [label, id], + icon_path: mdiShape, }); } else if (type === "external") { const domain = id.split(":")[0]; const domainName = domainToName(this.hass.localize, domain); output.push({ id, + statistic_id: id, primary: label, secondary: domainName, label: "", type, sorting_label: label, - iconPath: mdiChartLine, + search_labels: [label, domainName, id], + icon_path: mdiChartLine, }); } } @@ -283,17 +283,20 @@ export class HaStatisticComboBox extends LitElement { output.push({ id, + statistic_id: id, + label: "", primary, secondary, - label: "", - state: stateObj, + stateObj: stateObj, type: "entity", sorting_label: [deviceName, entityName].join("_"), - entity_name: entityName || deviceName, - area_name: areaName, - device_name: deviceName, - friendly_name: friendlyName, - show_entity_id: hass.userData?.showEntityIdPicker, + search_labels: [ + entityName, + deviceName, + areaName, + friendlyName, + id, + ].filter(Boolean) as string[], }); }); @@ -323,11 +326,12 @@ export class HaStatisticComboBox extends LitElement { } output.push({ - id: "__missing", + id: MISSING_ID, primary: this.hass.localize( "ui.components.statistic-picker.missing_entity" ), label: "", + icon_path: mdiHelpCircle, }); return output; @@ -422,8 +426,12 @@ export class HaStatisticComboBox extends LitElement { private _statisticChanged(ev: ValueChangedEvent) { ev.stopPropagation(); let newValue = ev.detail.value; - if (newValue === "__missing") { + if (newValue === MISSING_ID) { newValue = ""; + window.open( + documentationUrl(this.hass, this.helpMissingEntityUrl), + "_blank" + ); } if (newValue !== this._value) { @@ -436,16 +444,7 @@ export class HaStatisticComboBox extends LitElement { } private _fuseIndex = memoizeOne((states: StatisticItem[]) => - Fuse.createIndex( - [ - "entity_name", - "device_name", - "area_name", - "friendly_name", // for backwards compatibility - "id", // for technical search - ], - states - ) + Fuse.createIndex(["search_labels"], states) ); private _filterChanged(ev: CustomEvent): void { diff --git a/src/components/ha-combo-box-item.ts b/src/components/ha-combo-box-item.ts index ddbf56da26..1a061174fb 100644 --- a/src/components/ha-combo-box-item.ts +++ b/src/components/ha-combo-box-item.ts @@ -35,6 +35,20 @@ export class HaComboBoxItem extends HaMdListItem { width: 32px; height: 32px; } + ::slotted(.code) { + font-family: var(--ha-font-family-code); + font-size: var(--ha-font-size-xs); + } + [slot="trailing-supporting-text"] { + font-size: var(--ha-font-size-s); + font-weight: var(--ha-font-weight-normal); + line-height: var(--ha-line-height-normal); + align-self: flex-end; + max-width: 30%; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } `, ]; } diff --git a/src/components/ha-outlined-icon-button.ts b/src/components/ha-outlined-icon-button.ts index 7e0835bc5a..2615e1fc45 100644 --- a/src/components/ha-outlined-icon-button.ts +++ b/src/components/ha-outlined-icon-button.ts @@ -6,6 +6,13 @@ import { customElement } from "lit/decorators"; @customElement("ha-outlined-icon-button") export class HaOutlinedIconButton extends IconButton { + protected override getRenderClasses() { + return { + ...super.getRenderClasses(), + outlined: true, + }; + } + static override styles = [ css` .icon-button { diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index b59b15405e..a286613cb0 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -852,8 +852,8 @@ class HaSidebar extends SubscribeMixin(LitElement) { color: var(--sidebar-icon-color); } .title { - margin-left: 19px; - margin-inline-start: 19px; + margin-left: 3px; + margin-inline-start: 3px; margin-inline-end: initial; width: 100%; display: none; @@ -940,7 +940,6 @@ class HaSidebar extends SubscribeMixin(LitElement) { ha-md-list-item .item-text { display: none; - max-width: calc(100% - 56px); font-weight: 500; font-size: 14px; } diff --git a/src/components/media-player/ha-browse-media-tts.ts b/src/components/media-player/ha-browse-media-tts.ts index e6fbdd62d7..1924af09a4 100644 --- a/src/components/media-player/ha-browse-media-tts.ts +++ b/src/components/media-player/ha-browse-media-tts.ts @@ -2,6 +2,7 @@ import "@material/mwc-button/mwc-button"; import type { PropertyValues } from "lit"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; +import { mdiContentCopy } from "@mdi/js"; import { storage } from "../../common/decorators/storage"; import { fireEvent } from "../../common/dom/fire_event"; import type { @@ -17,6 +18,8 @@ import "../ha-language-picker"; import "../ha-tts-voice-picker"; import "../ha-card"; import { fetchCloudStatus } from "../../data/cloud"; +import { copyToClipboard } from "../../common/util/copy-clipboard"; +import { showToast } from "../../util/toast"; export interface TtsMediaPickedEvent { item: MediaPlayerItem; @@ -51,50 +54,69 @@ class BrowseMediaTTS extends LitElement { private _message?: string; protected render() { - return html` -
- - - ${this._provider?.supported_languages?.length - ? html`
- - -
` - : nothing} -
-
- - ${this.hass.localize( - `ui.components.media-browser.tts.action_${this.action}` - )} - -
-
`; + return html` + +
+ + + ${this._provider?.supported_languages?.length + ? html`
+ + +
` + : nothing} +
+
+ + ${this.hass.localize( + `ui.components.media-browser.tts.action_${this.action}` + )} + +
+
+ ${this._voice + ? html` + + ` + : nothing} + `; } protected override willUpdate(changedProps: PropertyValues): void { @@ -197,6 +219,14 @@ class BrowseMediaTTS extends LitElement { fireEvent(this, "tts-picked", { item }); } + private async _copyVoiceId(ev) { + ev.preventDefault(); + await copyToClipboard(this._voice); + showToast(this, { + message: this.hass.localize("ui.common.copied_clipboard"), + }); + } + static override styles = [ buttonLinkStyle, css` @@ -218,6 +248,23 @@ class BrowseMediaTTS extends LitElement { button.link { color: var(--primary-color); } + .footer { + font-size: var(--ha-font-size-s); + color: var(--secondary-text-color); + margin: 16px 0; + text-align: center; + } + .footer code { + font-weight: var(--ha-font-weight-bold); + } + .footer { + --mdc-icon-size: 14px; + --mdc-icon-button-size: 24px; + display: flex; + justify-content: center; + align-items: center; + gap: 6px; + } `, ]; } diff --git a/src/dialogs/config-flow/dialog-data-entry-flow.ts b/src/dialogs/config-flow/dialog-data-entry-flow.ts index 94959185ec..726143bdb1 100644 --- a/src/dialogs/config-flow/dialog-data-entry-flow.ts +++ b/src/dialogs/config-flow/dialog-data-entry-flow.ts @@ -1,4 +1,3 @@ -import "@material/mwc-button"; import { mdiClose, mdiHelpCircle } from "@mdi/js"; import type { UnsubscribeFunc } from "home-assistant-js-websocket"; import type { CSSResultGroup, PropertyValues } from "lit"; @@ -177,6 +176,17 @@ class DataEntryFlowDialog extends LitElement { return nothing; } + const showDocumentationLink = + ([ + "form", + "menu", + "external", + "progress", + "data_entry_flow_progressed", + ].includes(this._step?.type as any) && + this._params.manifest?.is_built_in) || + !!this._params.manifest?.documentation; + return html` @@ -199,26 +209,18 @@ class DataEntryFlowDialog extends LitElement { : this._step === undefined ? // When we are going to next step, we render 1 round of empty // to reset the element. - "" + nothing : html`
- ${([ - "form", - "menu", - "external", - "progress", - "data_entry_flow_progressed", - ].includes(this._step?.type as any) && - this._params.manifest?.is_built_in) || - this._params.manifest?.documentation + ${showDocumentationLink ? html` @@ -229,7 +231,7 @@ class DataEntryFlowDialog extends LitElement { ` - : ""} + : nothing} ` : this._step.type === "external" @@ -250,6 +253,7 @@ class DataEntryFlowDialog extends LitElement { .flowConfig=${this._params.flowConfig} .step=${this._step} .hass=${this.hass} + .increasePaddingEnd=${showDocumentationLink} > ` : this._step.type === "abort" @@ -261,6 +265,7 @@ class DataEntryFlowDialog extends LitElement { .handler=${this._step.handler} .domain=${this._params.domain ?? this._step.handler} + .increasePaddingEnd=${showDocumentationLink} > ` : this._step.type === "progress" @@ -270,6 +275,7 @@ class DataEntryFlowDialog extends LitElement { .step=${this._step} .hass=${this.hass} .progress=${this._progress} + .increasePaddingEnd=${showDocumentationLink} > ` : this._step.type === "menu" @@ -278,6 +284,7 @@ class DataEntryFlowDialog extends LitElement { .flowConfig=${this._params.flowConfig} .step=${this._step} .hass=${this.hass} + .increasePaddingEnd=${showDocumentationLink} > ` : html` @@ -286,7 +293,8 @@ class DataEntryFlowDialog extends LitElement { .step=${this._step} .hass=${this.hass} .navigateToResult=${this._params - .navigateToResult} + .navigateToResult ?? false} + .increasePaddingEnd=${showDocumentationLink} > `} `} diff --git a/src/dialogs/config-flow/step-flow-abort.ts b/src/dialogs/config-flow/step-flow-abort.ts index fa54d4ca57..9f0ad9abb0 100644 --- a/src/dialogs/config-flow/step-flow-abort.ts +++ b/src/dialogs/config-flow/step-flow-abort.ts @@ -22,6 +22,9 @@ class StepFlowAbort extends LitElement { @property({ attribute: false }) public handler!: string; + @property({ type: Boolean, attribute: "increase-padding-end" }) + public increasePaddingEnd = false; + protected firstUpdated(changed: PropertyValues) { super.firstUpdated(changed); if (this.step.reason === "missing_credentials") { @@ -34,7 +37,7 @@ class StepFlowAbort extends LitElement { return nothing; } return html` -

+

${this.params.flowConfig.renderAbortHeader ? this.params.flowConfig.renderAbortHeader(this.hass, this.step) : this.hass.localize(`component.${this.domain}.title`)} diff --git a/src/dialogs/config-flow/step-flow-create-entry.ts b/src/dialogs/config-flow/step-flow-create-entry.ts index 6f59bf593d..327b5b783a 100644 --- a/src/dialogs/config-flow/step-flow-create-entry.ts +++ b/src/dialogs/config-flow/step-flow-create-entry.ts @@ -36,6 +36,9 @@ class StepFlowCreateEntry extends LitElement { @property({ attribute: false }) public step!: DataEntryFlowStepCreateEntry; + @property({ type: Boolean, attribute: "increase-padding-end" }) + public increasePaddingEnd = false; + public navigateToResult = false; @state() private _deviceUpdate: Record< @@ -113,7 +116,7 @@ class StepFlowCreateEntry extends LitElement { this.step.result?.entry_id ); return html` -

+

${devices.length ? localize("ui.panel.config.integrations.config_flow.assign_area", { number: devices.length, diff --git a/src/dialogs/config-flow/step-flow-external.ts b/src/dialogs/config-flow/step-flow-external.ts index 4e3b10512c..98d98c61ef 100644 --- a/src/dialogs/config-flow/step-flow-external.ts +++ b/src/dialogs/config-flow/step-flow-external.ts @@ -15,11 +15,16 @@ class StepFlowExternal extends LitElement { @property({ attribute: false }) public step!: DataEntryFlowStepExternal; + @property({ type: Boolean, attribute: "increase-padding-end" }) + public increasePaddingEnd = false; + protected render(): TemplateResult { const localize = this.hass.localize; return html` -

${this.flowConfig.renderExternalStepHeader(this.hass, this.step)}

+

+ ${this.flowConfig.renderExternalStepHeader(this.hass, this.step)} +

${this.flowConfig.renderExternalStepDescription(this.hass, this.step)}
@@ -51,6 +56,9 @@ class StepFlowExternal extends LitElement { .open-button a { text-decoration: none; } + h2.end-space { + padding-inline-end: 72px; + } `, ]; } diff --git a/src/dialogs/config-flow/step-flow-form.ts b/src/dialogs/config-flow/step-flow-form.ts index 0c6da5eda2..06ec17dc0d 100644 --- a/src/dialogs/config-flow/step-flow-form.ts +++ b/src/dialogs/config-flow/step-flow-form.ts @@ -27,6 +27,9 @@ class StepFlowForm extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; + @property({ type: Boolean, attribute: "increase-padding-end" }) + public increasePaddingEnd = false; + @state() private _loading = false; @state() private _stepData?: Record; @@ -43,7 +46,9 @@ class StepFlowForm extends LitElement { const stepData = this._stepDataProcessed; return html` -

${this.flowConfig.renderShowFormStepHeader(this.hass, this.step)}

+

+ ${this.flowConfig.renderShowFormStepHeader(this.hass, this.step)} +

${this.flowConfig.renderShowFormStepDescription(this.hass, this.step)} ${this._errorMsg diff --git a/src/dialogs/config-flow/step-flow-menu.ts b/src/dialogs/config-flow/step-flow-menu.ts index 7c1f1e35f6..b0fcd3ba64 100644 --- a/src/dialogs/config-flow/step-flow-menu.ts +++ b/src/dialogs/config-flow/step-flow-menu.ts @@ -17,6 +17,9 @@ class StepFlowMenu extends LitElement { @property({ attribute: false }) public step!: DataEntryFlowStepMenu; + @property({ type: Boolean, attribute: "increase-padding-end" }) + public increasePaddingEnd = false; + protected render(): TemplateResult { let options: string[]; let translations: Record; @@ -42,7 +45,9 @@ class StepFlowMenu extends LitElement { ); return html` -

${this.flowConfig.renderMenuHeader(this.hass, this.step)}

+

+ ${this.flowConfig.renderMenuHeader(this.hass, this.step)} +

${description ? html`
${description}
` : ""}
${options.map( diff --git a/src/dialogs/config-flow/step-flow-progress.ts b/src/dialogs/config-flow/step-flow-progress.ts index ef56fa271d..c71efcb98c 100644 --- a/src/dialogs/config-flow/step-flow-progress.ts +++ b/src/dialogs/config-flow/step-flow-progress.ts @@ -24,9 +24,12 @@ class StepFlowProgress extends LitElement { @property({ type: Number }) public progress?: number; + @property({ type: Boolean, attribute: "increase-padding-end" }) + public increasePaddingEnd = false; + protected render(): TemplateResult { return html` -

+

${this.flowConfig.renderShowFormProgressHeader(this.hass, this.step)}

diff --git a/src/dialogs/config-flow/styles.ts b/src/dialogs/config-flow/styles.ts index daaa9342af..0e2aca3e7b 100644 --- a/src/dialogs/config-flow/styles.ts +++ b/src/dialogs/config-flow/styles.ts @@ -22,6 +22,9 @@ export const configFlowContentStyles = css` text-transform: var(--mdc-typography-headline6-text-transform, inherit); box-sizing: border-box; } + h2.end-space { + padding-inline-end: 72px; + } .content, .preview { diff --git a/src/dialogs/more-info/controls/more-info-cover.ts b/src/dialogs/more-info/controls/more-info-cover.ts index 166d5367a6..b041b44b5e 100644 --- a/src/dialogs/more-info/controls/more-info-cover.ts +++ b/src/dialogs/more-info/controls/more-info-cover.ts @@ -57,7 +57,7 @@ class MoreInfoCover extends LitElement { ); if (positionStateDisplay) { - return `${stateDisplay} ⸱ ${positionStateDisplay}`; + return `${stateDisplay} · ${positionStateDisplay}`; } return stateDisplay; } diff --git a/src/dialogs/more-info/controls/more-info-valve.ts b/src/dialogs/more-info/controls/more-info-valve.ts index 66158025c8..84a0e43eaf 100644 --- a/src/dialogs/more-info/controls/more-info-valve.ts +++ b/src/dialogs/more-info/controls/more-info-valve.ts @@ -57,7 +57,7 @@ class MoreInfoValve extends LitElement { ); if (positionStateDisplay) { - return `${stateDisplay} ⸱ ${positionStateDisplay}`; + return `${stateDisplay} · ${positionStateDisplay}`; } return stateDisplay; } diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index 3af88bb787..68833ad0bf 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -95,22 +95,6 @@ type BaseNavigationCommand = Pick< "primaryText" | "path" >; -const DOMAIN_STYLE = styleMap({ - fontSize: "var(--ha-font-size-s)", - fontWeight: "var(--ha-font-weight-normal)", - lineHeight: "var(--ha-line-height-normal)", - alignSelf: "flex-end", - maxWidth: "30%", - textOverflow: "ellipsis", - overflow: "hidden", - whiteSpace: "nowrap", -}); - -const ENTITY_ID_STYLE = styleMap({ - fontFamily: "var(--ha-font-family-code)", - fontSize: "var(--ha-font-size-xs)", -}); - @customElement("ha-quick-bar") export class QuickBar extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -397,12 +381,12 @@ export class QuickBar extends LitElement { ? html` ${item.altText} ` : nothing} ${item.entityId && showEntityId - ? html`${item.entityId}` + ? html` + ${item.entityId} + ` : nothing} ${item.translatedDomain && !showEntityId - ? html`
+ ? html`
${item.translatedDomain}
` : nothing} @@ -1038,6 +1022,22 @@ export class QuickBar extends LitElement { --md-list-item-bottom-space: 8px; } + ha-md-list-item .code { + font-family: var(--ha-font-family-code); + font-size: var(--ha-font-size-xs); + } + + ha-md-list-item [slot="trailing-supporting-text"] { + font-size: var(--ha-font-size-s); + font-weight: var(--ha-font-weight-normal); + line-height: var(--ha-line-height-normal); + align-self: flex-end; + max-width: 30%; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + ha-tip { padding: 20px; } diff --git a/src/panels/config/backup/components/config/ha-backup-config-agents.ts b/src/panels/config/backup/components/config/ha-backup-config-agents.ts index 687321f7c5..f808d3fe99 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-agents.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-agents.ts @@ -1,5 +1,6 @@ import { mdiCog, mdiDelete, mdiHarddisk, mdiNas } from "@mdi/js"; -import { css, html, LitElement, nothing } from "lit"; +import { css, html, LitElement, nothing, type TemplateResult } from "lit"; +import { join } from "lit/directives/join"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; @@ -57,26 +58,51 @@ class HaBackupConfigAgents extends LitElement { ); } + const texts: (TemplateResult | string)[] = []; + + if (isNetworkMountAgent(agentId)) { + texts.push( + this.hass.localize( + "ui.panel.config.backup.agents.network_mount_agent_description" + ) + ); + } + const encryptionTurnedOff = this.agentsConfig?.[agentId]?.protected === false; if (encryptionTurnedOff) { - return html` - - - ${this.hass.localize( - "ui.panel.config.backup.agents.encryption_turned_off" - )} - - `; - } - - if (isNetworkMountAgent(agentId)) { - return this.hass.localize( - "ui.panel.config.backup.agents.network_mount_agent_description" + texts.push( + html`
+ + + ${this.hass.localize( + "ui.panel.config.backup.agents.encryption_turned_off" + )} + +
` ); } - return ""; + + const retention = this.agentsConfig?.[agentId]?.retention; + + if (retention) { + if (retention.copies === null && retention.days === null) { + texts.push( + this.hass.localize("ui.panel.config.backup.agents.retention_all") + ); + } else { + texts.push( + this.hass.localize( + `ui.panel.config.backup.agents.retention_${retention.copies ? "backups" : "days"}`, + { + count: retention.copies || retention.days, + } + ) + ); + } + } + return join(texts, html` · `); } private _availableAgents = memoizeOne( @@ -287,6 +313,11 @@ class HaBackupConfigAgents extends LitElement { gap: 8px; line-height: normal; } + .unencrypted-warning { + display: flex; + align-items: center; + gap: 4px; + } .dot { display: block; position: relative; @@ -294,11 +325,22 @@ class HaBackupConfigAgents extends LitElement { height: 8px; background-color: var(--disabled-color); border-radius: 50%; - flex: none; } .dot.warning { background-color: var(--warning-color); } + @media all and (max-width: 500px) { + .separator { + display: none; + } + ha-md-list-item [slot="supporting-text"] { + display: flex; + align-items: flex-start; + flex-direction: column; + justify-content: flex-start; + gap: 4px; + } + } `; } diff --git a/src/panels/config/info/ha-config-info.ts b/src/panels/config/info/ha-config-info.ts index 26acffcabd..d58f4a858e 100644 --- a/src/panels/config/info/ha-config-info.ts +++ b/src/panels/config/info/ha-config-info.ts @@ -156,7 +156,7 @@ class HaConfigInfo extends LitElement { )} - ${JS_VERSION}${JS_TYPE !== "modern" ? ` ⸱ ${JS_TYPE}` : ""} + ${JS_VERSION}${JS_TYPE !== "modern" ? ` · ${JS_TYPE}` : ""} diff --git a/src/panels/config/logs/dialog-download-logs.ts b/src/panels/config/logs/dialog-download-logs.ts index 801ef1db22..528235c4e5 100644 --- a/src/panels/config/logs/dialog-download-logs.ts +++ b/src/panels/config/logs/dialog-download-logs.ts @@ -70,7 +70,7 @@ class DownloadLogsDialog extends LitElement { ${this._dialogParams.header}${this._dialogParams.boot === 0 ? "" - : ` ⸱ ${this._dialogParams.boot === -1 ? this.hass.localize("ui.panel.config.logs.previous") : this.hass.localize("ui.panel.config.logs.startups_ago", { boot: this._dialogParams.boot * -1 })}`} + : ` · ${this._dialogParams.boot === -1 ? this.hass.localize("ui.panel.config.logs.previous") : this.hass.localize("ui.panel.config.logs.startups_ago", { boot: this._dialogParams.boot * -1 })}`}
diff --git a/src/panels/config/repairs/dialog-repairs-issue-subtitle.ts b/src/panels/config/repairs/dialog-repairs-issue-subtitle.ts index 6ea15ed6e4..d0c39bc951 100644 --- a/src/panels/config/repairs/dialog-repairs-issue-subtitle.ts +++ b/src/panels/config/repairs/dialog-repairs-issue-subtitle.ts @@ -20,7 +20,7 @@ class DialogRepairsIssueSubtitle extends LitElement { protected render() { const domainName = domainToName(this.hass.localize, this.issue.domain); const reportedBy = domainName - ? ` ⸱ ${this.hass.localize("ui.panel.config.repairs.reported_by", { + ? ` · ${this.hass.localize("ui.panel.config.repairs.reported_by", { integration: domainName, })}` : ""; diff --git a/src/panels/config/repairs/ha-config-repairs.ts b/src/panels/config/repairs/ha-config-repairs.ts index 77345f3e86..f4729b0287 100644 --- a/src/panels/config/repairs/ha-config-repairs.ts +++ b/src/panels/config/repairs/ha-config-repairs.ts @@ -100,13 +100,13 @@ class HaConfigRepairs extends LitElement { ${(issue.severity === "critical" || issue.severity === "error") && issue.created - ? " ⸱ " + ? " · " : ""} ${createdBy ? html`${createdBy}` : nothing} ${issue.ignored - ? ` ⸱ ${this.hass.localize( + ? ` · ${this.hass.localize( "ui.panel.config.repairs.dialog.ignored_in_version_short", { version: issue.dismissed_version } )}` diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index 8083dd947c..1ea4475d63 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -1096,6 +1096,8 @@ class HUIRoot extends LitElement { .edit-mode sl-tab-group { flex-grow: 0; color: var(--app-header-edit-text-color, #fff); + --ha-tab-active-text-color: var(--app-header-edit-text-color, #fff); + --ha-tab-indicator-color: var(--app-header-edit-text-color, #fff); } .edit-mode sl-tab { height: 54px; diff --git a/src/panels/lovelace/strategies/areas/areas-overview-view-strategy.ts b/src/panels/lovelace/strategies/areas/areas-overview-view-strategy.ts index 40a034140b..fde3545216 100644 --- a/src/panels/lovelace/strategies/areas/areas-overview-view-strategy.ts +++ b/src/panels/lovelace/strategies/areas/areas-overview-view-strategy.ts @@ -50,6 +50,7 @@ export class AreasOverviewViewStrategy extends ReactiveElement { const entities = [ ...groups.lights, + ...groups.covers, ...groups.climate, ...groups.media_players, ...groups.security, diff --git a/src/state-control/climate/ha-state-control-climate-temperature.ts b/src/state-control/climate/ha-state-control-climate-temperature.ts index 6b2000222b..a3317dfde3 100644 --- a/src/state-control/climate/ha-state-control-climate-temperature.ts +++ b/src/state-control/climate/ha-state-control-climate-temperature.ts @@ -366,7 +366,7 @@ export class HaStateControlClimateTemperature extends LitElement { > ${this._renderTarget(this._targetTemperature.low!, "normal", true)} - + ·