Use ha-dialog-header for data-entry-flow (#25403)

* Use ha-dialog-header for data-entry-flow header

* Fix header wrap
This commit is contained in:
Wendelin 2025-05-13 13:17:54 +02:00 committed by GitHub
parent e1b9b47ac7
commit 910e7e10a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 208 additions and 142 deletions

View File

@ -1,17 +1,20 @@
import { mdiClose, mdiHelpCircle } from "@mdi/js";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues } from "lit";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import type { HASSDomEvent } from "../../common/dom/fire_event";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-dialog";
import "../../components/ha-dialog-header";
import "../../components/ha-icon-button";
import type { DataEntryFlowStep } from "../../data/data_entry_flow";
import {
subscribeDataEntryFlowProgress,
subscribeDataEntryFlowProgressed,
} from "../../data/data_entry_flow";
import type { DeviceRegistryEntry } from "../../data/device_registry";
import { haStyleDialog } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import { documentationUrl } from "../../util/documentation-url";
@ -171,6 +174,92 @@ class DataEntryFlowDialog extends LitElement {
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
private _devices = memoizeOne(
(
showDevices: boolean,
devices: DeviceRegistryEntry[],
entry_id?: string
) =>
showDevices && entry_id
? devices.filter((device) => device.config_entries.includes(entry_id))
: []
);
private _getDialogTitle(): string {
if (this._loading || !this._step || !this._params) {
return "";
}
switch (this._step.type) {
case "form":
return this._params.flowConfig.renderShowFormStepHeader(
this.hass,
this._step
);
case "abort":
return this._params.flowConfig.renderAbortHeader
? this._params.flowConfig.renderAbortHeader(this.hass, this._step)
: this.hass.localize(
`component.${this._params.domain ?? this._step.handler}.title`
);
case "progress":
return this._params.flowConfig.renderShowFormProgressHeader(
this.hass,
this._step
);
case "menu":
return this._params.flowConfig.renderMenuHeader(this.hass, this._step);
case "create_entry": {
const devicesLength = this._devices(
this._params.flowConfig.showDevices,
Object.values(this.hass.devices),
this._step.result?.entry_id
).length;
return this.hass.localize(
`ui.panel.config.integrations.config_flow.${
devicesLength ? "device_created" : "success"
}`,
{
number: devicesLength,
}
);
}
default:
return "";
}
}
private _getDialogSubtitle(): string | TemplateResult | undefined {
if (this._loading || !this._step || !this._params) {
return "";
}
switch (this._step.type) {
case "form":
return this._params.flowConfig.renderShowFormStepSubheader?.(
this.hass,
this._step
);
case "abort":
return this._params.flowConfig.renderAbortSubheader?.(
this.hass,
this._step
);
case "progress":
return this._params.flowConfig.renderShowFormProgressSubheader?.(
this.hass,
this._step
);
case "menu":
return this._params.flowConfig.renderMenuSubheader?.(
this.hass,
this._step
);
default:
return "";
}
}
protected render() {
if (!this._params) {
return nothing;
@ -187,6 +276,9 @@ class DataEntryFlowDialog extends LitElement {
this._params.manifest?.is_built_in) ||
!!this._params.manifest?.documentation;
const dialogTitle = this._getDialogTitle();
const dialogSubtitle = this._getDialogSubtitle();
return html`
<ha-dialog
open
@ -194,7 +286,50 @@ class DataEntryFlowDialog extends LitElement {
scrimClickAction
escapeKeyAction
hideActions
.heading=${dialogTitle}
>
<ha-dialog-header slot="heading">
<ha-icon-button
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
dialogAction="close"
slot="navigationIcon"
></ha-icon-button>
<div
slot="title"
class="dialog-title${this._step?.type === "form" ? " form" : ""}"
title=${dialogTitle}
>
${dialogTitle}
</div>
${dialogSubtitle
? html` <div slot="subtitle">${dialogSubtitle}</div>`
: nothing}
${showDocumentationLink && !this._loading && this._step
? html`
<a
slot="actionItems"
class="help"
href=${this._params.manifest!.is_built_in
? documentationUrl(
this.hass,
`/integrations/${this._params.manifest!.domain}`
)
: this._params.manifest!.documentation}
target="_blank"
rel="noreferrer noopener"
>
<ha-icon-button
.label=${this.hass.localize("ui.common.help")}
.path=${mdiHelpCircle}
>
</ha-icon-button
></a>
`
: nothing}
</ha-dialog-header>
<div>
${this._loading || this._step === null
? html`
@ -211,40 +346,12 @@ class DataEntryFlowDialog extends LitElement {
// to reset the element.
nothing
: html`
<div class="dialog-actions">
${showDocumentationLink
? html`
<a
href=${this._params.manifest!.is_built_in
? documentationUrl(
this.hass,
`/integrations/${this._params.manifest!.domain}`
)
: this._params.manifest!.documentation}
target="_blank"
rel="noreferrer noopener"
>
<ha-icon-button
.label=${this.hass.localize("ui.common.help")}
.path=${mdiHelpCircle}
>
</ha-icon-button
></a>
`
: nothing}
<ha-icon-button
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
dialogAction="close"
></ha-icon-button>
</div>
${this._step.type === "form"
? html`
<step-flow-form
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
.increasePaddingEnd=${showDocumentationLink}
></step-flow-form>
`
: this._step.type === "external"
@ -253,7 +360,6 @@ class DataEntryFlowDialog extends LitElement {
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
.increasePaddingEnd=${showDocumentationLink}
></step-flow-external>
`
: this._step.type === "abort"
@ -265,7 +371,6 @@ class DataEntryFlowDialog extends LitElement {
.handler=${this._step.handler}
.domain=${this._params.domain ??
this._step.handler}
.increasePaddingEnd=${showDocumentationLink}
></step-flow-abort>
`
: this._step.type === "progress"
@ -275,7 +380,6 @@ class DataEntryFlowDialog extends LitElement {
.step=${this._step}
.hass=${this.hass}
.progress=${this._progress}
.increasePaddingEnd=${showDocumentationLink}
></step-flow-progress>
`
: this._step.type === "menu"
@ -284,7 +388,6 @@ class DataEntryFlowDialog extends LitElement {
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
.increasePaddingEnd=${showDocumentationLink}
></step-flow-menu>
`
: html`
@ -294,7 +397,11 @@ class DataEntryFlowDialog extends LitElement {
.hass=${this.hass}
.navigateToResult=${this._params
.navigateToResult ?? false}
.increasePaddingEnd=${showDocumentationLink}
.devices=${this._devices(
this._params.flowConfig.showDevices,
Object.values(this.hass.devices),
this._step.result?.entry_id
)}
></step-flow-create-entry>
`}
`}
@ -384,16 +491,14 @@ class DataEntryFlowDialog extends LitElement {
ha-dialog {
--dialog-content-padding: 0;
}
.dialog-actions {
padding: 16px;
position: absolute;
top: 0;
right: 0;
inset-inline-start: initial;
inset-inline-end: 0px;
direction: var(--direction);
.dialog-title {
overflow: hidden;
text-overflow: ellipsis;
}
.dialog-actions > * {
.dialog-title.form {
white-space: normal;
}
.help {
color: var(--secondary-text-color);
}
`,

View File

@ -31,10 +31,12 @@ export interface FlowConfig {
deleteFlow(hass: HomeAssistant, flowId: string): Promise<unknown>;
renderAbortHeader?(
renderAbortHeader?(hass: HomeAssistant, step: DataEntryFlowStepAbort): string;
renderAbortSubheader?(
hass: HomeAssistant,
step: DataEntryFlowStepAbort
): TemplateResult | string;
): string | TemplateResult;
renderAbortDescription(
hass: HomeAssistant,
@ -44,6 +46,11 @@ export interface FlowConfig {
renderShowFormStepHeader(
hass: HomeAssistant,
step: DataEntryFlowStepForm
): string;
renderShowFormStepSubheader?(
hass: HomeAssistant,
step: DataEntryFlowStepForm
): string | TemplateResult;
renderShowFormStepDescription(
@ -100,6 +107,11 @@ export interface FlowConfig {
renderShowFormProgressHeader(
hass: HomeAssistant,
step: DataEntryFlowStepProgress
): string;
renderShowFormProgressSubheader?(
hass: HomeAssistant,
step: DataEntryFlowStepProgress
): string | TemplateResult;
renderShowFormProgressDescription(
@ -107,7 +119,9 @@ export interface FlowConfig {
step: DataEntryFlowStepProgress
): TemplateResult | "";
renderMenuHeader(
renderMenuHeader(hass: HomeAssistant, step: DataEntryFlowStepMenu): string;
renderMenuSubheader?(
hass: HomeAssistant,
step: DataEntryFlowStepMenu
): string | TemplateResult;

View File

@ -22,9 +22,6 @@ 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") {
@ -37,11 +34,6 @@ class StepFlowAbort extends LitElement {
return nothing;
}
return html`
<h2 class=${this.increasePaddingEnd ? "end-space" : ""}>
${this.params.flowConfig.renderAbortHeader
? this.params.flowConfig.renderAbortHeader(this.hass, this.step)
: this.hass.localize(`component.${this.domain}.title`)}
</h2>
<div class="content">
${this.params.flowConfig.renderAbortDescription(this.hass, this.step)}
</div>

View File

@ -36,8 +36,7 @@ class StepFlowCreateEntry extends LitElement {
@property({ attribute: false }) public step!: DataEntryFlowStepCreateEntry;
@property({ type: Boolean, attribute: "increase-padding-end" })
public increasePaddingEnd = false;
@property({ attribute: false }) public devices!: DeviceRegistryEntry[];
public navigateToResult = false;
@ -46,17 +45,6 @@ class StepFlowCreateEntry extends LitElement {
{ name?: string; area?: string }
> = {};
private _devices = memoizeOne(
(
showDevices: boolean,
devices: DeviceRegistryEntry[],
entry_id?: string
) =>
showDevices && entry_id
? devices.filter((device) => device.config_entries.includes(entry_id))
: []
);
private _deviceEntities = memoizeOne(
(
deviceId: string,
@ -75,22 +63,16 @@ class StepFlowCreateEntry extends LitElement {
return;
}
const devices = this._devices(
this.flowConfig.showDevices,
Object.values(this.hass.devices),
this.step.result?.entry_id
);
if (
devices.length !== 1 ||
devices[0].primary_config_entry !== this.step.result?.entry_id ||
this.devices.length !== 1 ||
this.devices[0].primary_config_entry !== this.step.result?.entry_id ||
this.step.result.domain === "voip"
) {
return;
}
const assistSatellites = this._deviceEntities(
devices[0].id,
this.devices[0].id,
Object.values(this.hass.entities),
"assist_satellite"
);
@ -103,26 +85,14 @@ class StepFlowCreateEntry extends LitElement {
this.navigateToResult = false;
this._flowDone();
showVoiceAssistantSetupDialog(this, {
deviceId: devices[0].id,
deviceId: this.devices[0].id,
});
}
}
protected render(): TemplateResult {
const localize = this.hass.localize;
const devices = this._devices(
this.flowConfig.showDevices,
Object.values(this.hass.devices),
this.step.result?.entry_id
);
return html`
<h2 class=${this.increasePaddingEnd ? "end-space" : ""}>
${devices.length
? localize("ui.panel.config.integrations.config_flow.assign_area", {
number: devices.length,
})
: `${localize("ui.panel.config.integrations.config_flow.success")}!`}
</h2>
<div class="content">
${this.flowConfig.renderCreateEntryDescription(this.hass, this.step)}
${this.step.result?.state === "not_loaded"
@ -132,10 +102,10 @@ class StepFlowCreateEntry extends LitElement {
)}</span
>`
: nothing}
${devices.length === 0 &&
${this.devices.length === 0 &&
["options_flow", "repair_flow"].includes(this.flowConfig.flowType)
? nothing
: devices.length === 0
: this.devices.length === 0
? html`<p>
${localize(
"ui.panel.config.integrations.config_flow.created_config",
@ -144,7 +114,7 @@ class StepFlowCreateEntry extends LitElement {
</p>`
: html`
<div class="devices">
${devices.map(
${this.devices.map(
(device) => html`
<div class="device">
<div class="device-info">
@ -203,7 +173,7 @@ class StepFlowCreateEntry extends LitElement {
<div class="buttons">
<mwc-button @click=${this._flowDone}
>${localize(
`ui.panel.config.integrations.config_flow.${!devices.length || Object.keys(this._deviceUpdate).length ? "finish" : "finish_skip"}`
`ui.panel.config.integrations.config_flow.${!this.devices.length || Object.keys(this._deviceUpdate).length ? "finish" : "finish_skip"}`
)}</mwc-button
>
</div>

View File

@ -15,16 +15,10 @@ 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`
<h2 class=${this.increasePaddingEnd ? "end-space" : ""}>
${this.flowConfig.renderExternalStepHeader(this.hass, this.step)}
</h2>
<div class="content">
${this.flowConfig.renderExternalStepDescription(this.hass, this.step)}
<div class="open-button">
@ -56,9 +50,6 @@ class StepFlowExternal extends LitElement {
.open-button a {
text-decoration: none;
}
h2.end-space {
padding-inline-end: 72px;
}
`,
];
}

View File

@ -6,18 +6,18 @@ import { dynamicElement } from "../../common/dom/dynamic-element-directive";
import { fireEvent } from "../../common/dom/fire_event";
import { isNavigationClick } from "../../common/dom/is-navigation-click";
import "../../components/ha-alert";
import "../../components/ha-spinner";
import { computeInitialHaFormData } from "../../components/ha-form/compute-initial-ha-form-data";
import "../../components/ha-form/ha-form";
import type { HaFormSchema } from "../../components/ha-form/types";
import "../../components/ha-markdown";
import "../../components/ha-spinner";
import { autocompleteLoginFields } from "../../data/auth";
import type { DataEntryFlowStepForm } from "../../data/data_entry_flow";
import { previewModule } from "../../data/preview";
import { haStyle } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import type { FlowConfig } from "./show-dialog-data-entry-flow";
import { configFlowContentStyles } from "./styles";
import { haStyle } from "../../resources/styles";
import { previewModule } from "../../data/preview";
@customElement("step-flow-form")
class StepFlowForm extends LitElement {
@ -27,9 +27,6 @@ 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<string, any>;
@ -46,9 +43,6 @@ class StepFlowForm extends LitElement {
const stepData = this._stepDataProcessed;
return html`
<h2 class=${this.increasePaddingEnd ? "end-space" : ""}>
${this.flowConfig.renderShowFormStepHeader(this.hass, this.step)}
</h2>
<div class="content" @click=${this._clickHandler}>
${this.flowConfig.renderShowFormStepDescription(this.hass, this.step)}
${this._errorMsg
@ -281,9 +275,6 @@ class StepFlowForm extends LitElement {
margin-top: 24px;
display: block;
}
h2 {
word-break: break-word;
}
`,
];
}

View File

@ -17,9 +17,6 @@ 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<string, string>;
@ -45,9 +42,6 @@ class StepFlowMenu extends LitElement {
);
return html`
<h2 class=${this.increasePaddingEnd ? "end-space" : ""}>
${this.flowConfig.renderMenuHeader(this.hass, this.step)}
</h2>
${description ? html`<div class="content">${description}</div>` : ""}
<div class="options">
${options.map(

View File

@ -2,13 +2,13 @@ import "@material/mwc-button";
import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { blankBeforePercent } from "../../common/translations/blank_before_percent";
import "../../components/ha-progress-ring";
import "../../components/ha-spinner";
import type { DataEntryFlowStepProgress } from "../../data/data_entry_flow";
import type { HomeAssistant } from "../../types";
import type { FlowConfig } from "./show-dialog-data-entry-flow";
import { configFlowContentStyles } from "./styles";
import { blankBeforePercent } from "../../common/translations/blank_before_percent";
@customElement("step-flow-progress")
class StepFlowProgress extends LitElement {
@ -24,14 +24,8 @@ 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`
<h2 class=${this.increasePaddingEnd ? "end-space" : ""}>
${this.flowConfig.renderShowFormProgressHeader(this.hass, this.step)}
</h2>
<div class="content">
${this.progress
? html`

View File

@ -25,9 +25,6 @@ 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 {

View File

@ -1,7 +1,6 @@
import { html, nothing } from "lit";
import type { DataEntryFlowStep } from "../../../data/data_entry_flow";
import { domainToName } from "../../../data/integration";
import "./dialog-repairs-issue-subtitle";
import type { RepairsIssue } from "../../../data/repairs";
import {
createRepairsFlow,
@ -14,6 +13,7 @@ import {
showFlowDialog,
} from "../../../dialogs/config-flow/show-dialog-data-entry-flow";
import type { HomeAssistant } from "../../../types";
import "./dialog-repairs-issue-subtitle";
const mergePlaceholders = (issue: RepairsIssue, step: DataEntryFlowStep) =>
step.description_placeholders && issue.translation_placeholders
@ -68,8 +68,11 @@ export const showRepairsFlowDialog = (
deleteFlow: deleteRepairsFlow,
renderAbortHeader(hass) {
return hass.localize("ui.dialogs.repair_flow.form.header");
},
renderAbortSubheader(hass) {
return html`
${hass.localize("ui.dialogs.repair_flow.form.header")}
<dialog-repairs-issue-subtitle
.hass=${hass}
.issue=${issue}
@ -98,13 +101,18 @@ export const showRepairsFlowDialog = (
},
renderShowFormStepHeader(hass, step) {
return html`
${hass.localize(
return (
hass.localize(
`component.${issue.domain}.issues.${
issue.translation_key || issue.issue_id
}.fix_flow.step.${step.step_id}.title`,
mergePlaceholders(issue, step)
) || hass.localize("ui.dialogs.repair_flow.form.header")}
) || hass.localize("ui.dialogs.repair_flow.form.header")
);
},
renderShowFormStepSubheader(hass) {
return html`
<dialog-repairs-issue-subtitle
.hass=${hass}
.issue=${issue}
@ -196,13 +204,18 @@ export const showRepairsFlowDialog = (
},
renderShowFormProgressHeader(hass, step) {
return html`
${hass.localize(
return (
hass.localize(
`component.${issue.domain}.issues.step.${
issue.translation_key || issue.issue_id
}.fix_flow.${step.step_id}.title`,
mergePlaceholders(issue, step)
) || hass.localize(`component.${issue.domain}.title`)}
) || hass.localize(`component.${issue.domain}.title`)
);
},
renderShowFormProgressSubheader(hass) {
return html`
<dialog-repairs-issue-subtitle
.hass=${hass}
.issue=${issue}
@ -229,13 +242,18 @@ export const showRepairsFlowDialog = (
},
renderMenuHeader(hass, step) {
return html`
${hass.localize(
return (
hass.localize(
`component.${issue.domain}.issues.${
issue.translation_key || issue.issue_id
}.fix_flow.step.${step.step_id}.title`,
mergePlaceholders(issue, step)
) || hass.localize(`component.${issue.domain}.title`)}
) || hass.localize(`component.${issue.domain}.title`)
);
},
renderMenuSubheader(hass) {
return html`
<dialog-repairs-issue-subtitle
.hass=${hass}
.issue=${issue}

View File

@ -5340,7 +5340,7 @@
},
"config_flow": {
"success": "Success",
"assign_area": "Assign {number, plural,\n one {device}\n other {devices}\n} to area",
"device_created": "{number, plural,\n one {Device}\n other {Devices}\n} created",
"device_name": "Device name",
"aborted": "Aborted",
"close": "Close",