mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-11 12:01:07 +00:00
ha-selector-selector implementation (#18421)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
297
src/components/ha-selector/ha-selector-selector.ts
Normal file
297
src/components/ha-selector/ha-selector-selector.ts
Normal file
@@ -0,0 +1,297 @@
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { LocalizeFunc, LocalizeKeys } from "../../common/translations/localize";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-alert";
|
||||
import "../ha-form/ha-form";
|
||||
|
||||
const SELECTOR_DEFAULTS = {
|
||||
number: {
|
||||
min: 1,
|
||||
max: 100,
|
||||
},
|
||||
};
|
||||
|
||||
const SELECTOR_SCHEMAS = {
|
||||
action: [] as const,
|
||||
area: [
|
||||
{
|
||||
name: "multiple",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
] as const,
|
||||
attribute: [
|
||||
{
|
||||
name: "entity_id",
|
||||
selector: { entity: {} },
|
||||
},
|
||||
] as const,
|
||||
boolean: [] as const,
|
||||
color_temp: [
|
||||
{
|
||||
name: "unit",
|
||||
selector: { select: { options: ["kelvin", "mired"] } },
|
||||
},
|
||||
{
|
||||
name: "min",
|
||||
selector: { number: { mode: "box" } },
|
||||
},
|
||||
{
|
||||
name: "max",
|
||||
selector: { number: { mode: "box" } },
|
||||
},
|
||||
] as const,
|
||||
condition: [] as const,
|
||||
date: [] as const,
|
||||
datetime: [] as const,
|
||||
device: [
|
||||
{
|
||||
name: "multiple",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
] as const,
|
||||
duration: [
|
||||
{
|
||||
name: "enable_day",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
] as const,
|
||||
entity: [
|
||||
{
|
||||
name: "multiple",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
] as const,
|
||||
icon: [] as const,
|
||||
location: [] as const,
|
||||
media: [] as const,
|
||||
number: [
|
||||
{
|
||||
name: "min",
|
||||
selector: { number: { mode: "box" } },
|
||||
},
|
||||
{
|
||||
name: "max",
|
||||
selector: { number: { mode: "box" } },
|
||||
},
|
||||
{
|
||||
name: "step",
|
||||
selector: { number: { mode: "box" } },
|
||||
},
|
||||
] as const,
|
||||
object: [] as const,
|
||||
color_rgb: [] as const,
|
||||
select: [
|
||||
{
|
||||
name: "options",
|
||||
selector: { object: {} },
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
] as const,
|
||||
state: [
|
||||
{
|
||||
name: "entity_id",
|
||||
selector: { entity: {} },
|
||||
},
|
||||
] as const,
|
||||
target: [] as const,
|
||||
template: [] as const,
|
||||
text: [
|
||||
{
|
||||
name: "multiple",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
{
|
||||
name: "multiline",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
{ name: "prefix", selector: { text: {} } },
|
||||
{ name: "suffix", selector: { text: {} } },
|
||||
] as const,
|
||||
theme: [] as const,
|
||||
time: [] as const,
|
||||
};
|
||||
|
||||
@customElement("ha-selector-selector")
|
||||
export class HaSelectorSelector extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public value?: any;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public required = true;
|
||||
|
||||
private _yamlMode = false;
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues) {
|
||||
if (changedProps.size === 1 && changedProps.has("hass")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(choice: string, localize: LocalizeFunc) =>
|
||||
[
|
||||
{
|
||||
name: "type",
|
||||
selector: {
|
||||
select: {
|
||||
mode: "dropdown",
|
||||
required: true,
|
||||
options: Object.keys(SELECTOR_SCHEMAS)
|
||||
.concat("manual")
|
||||
.map((key) => ({
|
||||
label:
|
||||
localize(
|
||||
`ui.components.selectors.selector.types.${key}` as LocalizeKeys
|
||||
) || key,
|
||||
value: key,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
...(choice === "manual"
|
||||
? ([
|
||||
{
|
||||
name: "manual",
|
||||
selector: { object: {} },
|
||||
},
|
||||
] as const)
|
||||
: []),
|
||||
...(SELECTOR_SCHEMAS[choice]
|
||||
? SELECTOR_SCHEMAS[choice].length > 1
|
||||
? [
|
||||
{
|
||||
name: "",
|
||||
type: "expandable",
|
||||
title: localize("ui.components.selectors.selector.options"),
|
||||
schema: SELECTOR_SCHEMAS[choice],
|
||||
},
|
||||
]
|
||||
: SELECTOR_SCHEMAS[choice]
|
||||
: []),
|
||||
] as const
|
||||
);
|
||||
|
||||
protected render() {
|
||||
let data;
|
||||
let type;
|
||||
if (this._yamlMode) {
|
||||
type = "manual";
|
||||
data = { type, manual: this.value };
|
||||
} else {
|
||||
type = Object.keys(this.value)[0];
|
||||
const value0 = Object.values(this.value)[0];
|
||||
data = {
|
||||
type,
|
||||
...(typeof value0 === "object" ? value0 : []),
|
||||
};
|
||||
}
|
||||
|
||||
const schema = this._schema(type, this.hass.localize);
|
||||
|
||||
return html`<ha-card>
|
||||
<div class="card-content">
|
||||
<p>${this.label ? this.label : ""}</p>
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form></div
|
||||
></ha-card>`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail.value;
|
||||
|
||||
const type = value.type;
|
||||
if (!type || typeof value !== "object" || Object.keys(value).length === 0) {
|
||||
// not sure how this happens, but reject it
|
||||
return;
|
||||
}
|
||||
|
||||
const oldType = Object.keys(this.value)[0];
|
||||
if (type === "manual" && !this._yamlMode) {
|
||||
this._yamlMode = true;
|
||||
this.requestUpdate();
|
||||
return;
|
||||
}
|
||||
if (type === "manual" && value.manual === undefined) {
|
||||
return;
|
||||
}
|
||||
if (type !== "manual") {
|
||||
this._yamlMode = false;
|
||||
}
|
||||
delete value.type;
|
||||
|
||||
let newValue;
|
||||
if (type === "manual") {
|
||||
newValue = value.manual;
|
||||
} else if (type === oldType) {
|
||||
newValue = {
|
||||
[type]: { ...(value.manual ? value.manual[oldType] : value) },
|
||||
};
|
||||
} else {
|
||||
newValue = { [type]: { ...SELECTOR_DEFAULTS[type] } };
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (schema: any): string =>
|
||||
this.hass.localize(
|
||||
`ui.components.selectors.selector.${schema.name}` as LocalizeKeys
|
||||
) || schema.name;
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
--expansion-panel-summary-padding: 0 16px;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-card {
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
ha-card.disabled {
|
||||
pointer-events: none;
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
.card-content {
|
||||
padding: 0px 16px 16px 16px;
|
||||
}
|
||||
.title {
|
||||
font-size: 16px;
|
||||
padding-top: 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 16px;
|
||||
padding-right: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-selector": HaSelectorSelector;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user