Files
frontend/src/dialogs/enter-code/dialog-enter-code.ts
2025-10-22 09:43:56 +02:00

253 lines
7.0 KiB
TypeScript

import { mdiCheck, mdiClose } from "@mdi/js";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-button";
import "../../components/ha-control-button";
import { createCloseHeading } from "../../components/ha-dialog";
import "../../components/ha-textfield";
import type { HaTextField } from "../../components/ha-textfield";
import type { HomeAssistant } from "../../types";
import type { HassDialog } from "../make-dialog-manager";
import type { EnterCodeDialogParams } from "./show-enter-code-dialog";
const BUTTONS = [
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"0",
"clear",
"submit",
];
@customElement("dialog-enter-code")
export class DialogEnterCode
extends LitElement
implements HassDialog<EnterCodeDialogParams>
{
@property({ attribute: false }) public hass?: HomeAssistant;
@state() private _dialogParams?: EnterCodeDialogParams;
@query("#code") private _input?: HaTextField;
@state() private _showClearButton = false;
@state() private _narrow = false;
public async showDialog(dialogParams: EnterCodeDialogParams): Promise<void> {
this._dialogParams = dialogParams;
this._narrow = matchMedia(
"all and (max-width: 450px), all and (max-height: 500px)"
).matches;
await this.updateComplete;
}
public closeDialog() {
this._dialogParams = undefined;
this._showClearButton = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
return true;
}
private _submit(): void {
this._dialogParams?.submit?.(this._input?.value ?? "");
this.closeDialog();
}
private _cancel(): void {
this._dialogParams?.cancel?.();
this.closeDialog();
}
private _numberClick(e: MouseEvent): void {
const val = (e.currentTarget! as any).value;
this._input!.value = this._input!.value + val;
this._showClearButton = true;
}
private _clear(): void {
this._input!.value = "";
this._showClearButton = false;
}
private _inputValueChange(e) {
const field = e.currentTarget as HaTextField;
const val = field.value;
this._showClearButton = !!val;
}
protected render() {
if (!this._dialogParams || !this.hass) {
return nothing;
}
const isText = this._dialogParams.codeFormat === "text";
if (isText) {
return html`
<ha-dialog
open
@closed=${this._cancel}
.heading=${this._dialogParams.title ??
this.hass.localize("ui.dialogs.enter_code.title")}
>
<ha-textfield
class="input"
?dialogInitialFocus=${!this._narrow}
id="code"
.label=${this.hass.localize("ui.dialogs.enter_code.input_label")}
type="password"
autoValidate
validateOnInitialRender
pattern=${ifDefined(this._dialogParams.codePattern)}
inputmode="text"
></ha-textfield>
<ha-button
appearance="plain"
slot="secondaryAction"
dialogAction="cancel"
>
${this._dialogParams.cancelText ??
this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button @click=${this._submit} slot="primaryAction">
${this._dialogParams.submitText ??
this.hass.localize("ui.common.submit")}
</ha-button>
</ha-dialog>
`;
}
return html`
<ha-dialog
open
.heading=${createCloseHeading(
this.hass,
this._dialogParams.title ?? "Enter code"
)}
@closed=${this._cancel}
hideActions
>
<div class="container">
<ha-textfield
@input=${this._inputValueChange}
id="code"
.label=${this.hass.localize("ui.dialogs.enter_code.input_label")}
type="password"
inputmode="numeric"
?dialogInitialFocus=${!this._narrow}
></ha-textfield>
<div class="keypad">
${BUTTONS.map((value) =>
value === ""
? html`<span></span>`
: value === "clear"
? html`
<ha-control-button
@click=${this._clear}
class="clear"
.disabled=${!this._showClearButton}
.label=${this.hass!.localize("ui.common.clear")}
>
<ha-svg-icon path=${mdiClose}></ha-svg-icon>
</ha-control-button>
`
: value === "submit"
? html`
<ha-control-button
@click=${this._submit}
class="submit"
.label=${this._dialogParams!.submitText ??
this.hass!.localize("ui.common.submit")}
>
<ha-svg-icon path=${mdiCheck}></ha-svg-icon>
</ha-control-button>
`
: html`
<ha-control-button
.value=${value}
@click=${this._numberClick}
.label=${value}
>
${value}
</ha-control-button>
`
)}
</div>
</div>
</ha-dialog>
`;
}
static styles = css`
ha-dialog {
/* Place above other dialogs */
--dialog-z-index: 104;
}
ha-textfield {
width: 100%;
max-width: 300px;
margin: auto;
}
.container {
display: flex;
align-items: center;
flex-direction: column;
}
.keypad {
--keypad-columns: 3;
margin-top: 12px;
padding: 12px;
display: grid;
grid-template-columns: repeat(var(--keypad-columns), auto);
grid-auto-rows: auto;
grid-gap: var(--ha-space-6);
justify-items: center;
align-items: center;
direction: ltr;
}
.clear {
grid-row-start: 4;
grid-column-start: 0;
}
@media all and (max-height: 450px) {
.keypad {
--keypad-columns: 6;
}
.clear {
grid-row-start: 1;
grid-column-start: 6;
}
}
ha-control-button {
width: 56px;
height: 56px;
--control-button-border-radius: var(--ha-border-radius-4xl);
--mdc-icon-size: 24px;
font-size: var(--ha-font-size-2xl);
}
.submit {
--control-button-background-color: var(--green-color);
--control-button-icon-color: var(--green-color);
}
.clear {
--control-button-background-color: var(--red-color);
--control-button-icon-color: var(--red-color);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"dialog-enter-code": DialogEnterCode;
}
}