import { LitElement, PropertyValues, html, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; import { translateText } from "../Utils"; import { CARD_LABEL_CLASS, INPUT_CLASS, cardClass } from "./InputCardStyles"; @customElement("toggle-input-card") export class ToggleInputCard extends LitElement { @property({ attribute: false }) labelKey = ""; @property({ type: Boolean, attribute: false }) checked = false; @property({ attribute: false }) inputId?: string; @property({ attribute: false }) inputType = "number"; @property({ attribute: false }) inputMin?: number | string; @property({ attribute: false }) inputMax?: number | string; @property({ attribute: false }) inputStep?: number | string; @property({ attribute: false }) inputValue?: number | string; @property({ attribute: false }) inputAriaLabel?: string; @property({ attribute: false }) inputPlaceholder?: string; // Optional hint shown under the input when its value is 0 (e.g. "Disabled"), // so a 0 that means "off" isn't cryptic. @property({ attribute: false }) zeroLabel?: string; @property({ attribute: false }) defaultInputValue?: number | string; @property({ attribute: false }) minValidOnEnable?: number; @property({ attribute: false }) onToggle?: ( checked: boolean, value: number | string | undefined, ) => void; @property({ attribute: false }) onInput?: (e: Event) => void; @property({ attribute: false }) onChange?: (e: Event) => void; @property({ attribute: false }) onKeyDown?: (e: KeyboardEvent) => void; createRenderRoot() { return this; } // Autofocus + select the number input when the card is toggled on. Safe now // that the input is always mounted (focusing a freshly-inserted one janked). protected updated(changedProperties: PropertyValues) { if (!changedProperties.has("checked")) return; if (changedProperties.get("checked") === false && this.checked) { const input = this.querySelector("input"); input?.focus(); input?.select(); } } private toOptionalNumber( value: number | string | undefined, ): number | undefined { if (typeof value === "number") { return Number.isFinite(value) ? value : undefined; } if (typeof value === "string") { const trimmed = value.trim(); if (!trimmed) return undefined; const numeric = Number(trimmed); return Number.isFinite(numeric) ? numeric : undefined; } return undefined; } private resolveValueOnEnable(): number | string | undefined { const currentValue = this.inputValue; if ( currentValue === undefined || currentValue === null || currentValue === "" ) { return this.defaultInputValue; } if (this.minValidOnEnable === undefined) { return currentValue; } const numericValue = this.toOptionalNumber(currentValue); if (numericValue === undefined || numericValue < this.minValidOnEnable) { return this.defaultInputValue; } return numericValue; } private emitToggle() { const nextChecked = !this.checked; const nextValue = nextChecked ? this.resolveValueOnEnable() : undefined; this.onToggle?.(nextChecked, nextValue); } private handleCardClick = () => { this.emitToggle(); }; render() { return html` ${this.checked ? html` ` : ""} ${this.checked ? html`` : html``} ${translateText(this.labelKey)} ${this.checked && this.zeroLabel !== undefined && this.toOptionalNumber(this.inputValue) === 0 ? html` ${this.zeroLabel} ` : nothing} `; } }