Files
2026-07-14 23:57:03 -04:00

164 lines
4.8 KiB
TypeScript

/** Reusable confirmation dialog wrapping <ha-dialog>. */
import { LitElement, html, css, nothing } from "lit";
import { property, state } from "lit/decorators.js";
import type { HomeAssistant } from "../types";
import { t, nativeFieldStyles } from "../styles";
export interface ConfirmOptions {
title: string;
message: string;
confirmText?: string;
danger?: boolean;
}
export interface PromptOptions extends ConfirmOptions {
inputLabel?: string;
inputType?: string; // "text" | "date" etc.
inputValue?: string;
}
export interface PromptResult {
confirmed: boolean;
value: string;
}
export class MaintenanceConfirmDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _open = false;
@state() private _title = "";
@state() private _message = "";
@state() private _confirmText = "";
@state() private _danger = false;
@state() private _inputLabel = "";
@state() private _inputType = "";
@state() private _inputValue = "";
private _resolve: ((value: boolean) => void) | null = null;
private _promptResolve: ((value: PromptResult) => void) | null = null;
public confirm(opts: ConfirmOptions): Promise<boolean> {
this._title = opts.title;
this._message = opts.message;
this._confirmText = opts.confirmText || "OK";
this._danger = opts.danger || false;
this._inputLabel = "";
this._inputType = "";
this._inputValue = "";
this._open = true;
return new Promise<boolean>((resolve) => {
this._resolve = resolve;
this._promptResolve = null;
});
}
public prompt(opts: PromptOptions): Promise<PromptResult> {
this._title = opts.title;
this._message = opts.message;
this._confirmText = opts.confirmText || "OK";
this._danger = opts.danger || false;
this._inputLabel = opts.inputLabel || "";
this._inputType = opts.inputType || "text";
this._inputValue = opts.inputValue || "";
this._open = true;
return new Promise<PromptResult>((resolve) => {
this._promptResolve = resolve;
this._resolve = null;
});
}
private _cancel(): void {
this._open = false;
if (this._promptResolve) {
this._promptResolve({ confirmed: false, value: "" });
this._promptResolve = null;
}
this._resolve?.(false);
this._resolve = null;
}
private _confirmAction(): void {
this._open = false;
if (this._promptResolve) {
this._promptResolve({ confirmed: true, value: this._inputValue });
this._promptResolve = null;
}
this._resolve?.(true);
this._resolve = null;
}
render() {
if (!this._open) return nothing;
const lang = this.hass?.language || "en";
return html`
<ha-dialog open @closed=${this._cancel}>
<div class="dialog-title">${this._title}</div>
<div class="content">
${this._message}
${this._inputLabel ? html`
<!-- Native <input> rather than <ha-textfield>: HA loads
ha-textfield lazily for its own panels, so inside this custom
panel it can be unregistered and render with zero height —
the prompt then shows no field at all (caught live testing
the pause/replace prompts; same fix as complete-dialog). -->
<label class="field">
<span class="field-label">${this._inputLabel}</span>
<input class="field-input"
type="${this._inputType || "text"}"
.value=${this._inputValue}
@input=${(e: Event) => (this._inputValue = (e.target as HTMLInputElement).value)} />
</label>
` : nothing}
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${this._cancel}>
${t("cancel", lang)}
</ha-button>
<ha-button
class="${this._danger ? "danger" : ""}"
@click=${this._confirmAction}
>
${this._confirmText}
</ha-button>
</div>
</ha-dialog>
`;
}
static styles = [nativeFieldStyles, css`
.dialog-title {
font-size: 18px;
font-weight: 500;
padding-bottom: 12px;
}
/* shared native-field scaffold from nativeFieldStyles; the prompt input
follows the message text, hence the extra top margin here */
.field { margin-top: 12px; }
.content {
padding: 8px 0;
min-width: 280px;
line-height: 1.5;
display: flex;
flex-direction: column;
gap: 12px;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 16px;
}
ha-textfield {
display: block;
}
ha-button.danger {
--mdc-theme-primary: var(--error-color, #f44336);
}
`];
}
if (!customElements.get("maintenance-confirm-dialog")) {
customElements.define("maintenance-confirm-dialog", MaintenanceConfirmDialog);
}