mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-19 14:37:22 +00:00
Compare commits
4 Commits
dev
...
picture-el
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb36e4aa42 | ||
|
|
5c9e052030 | ||
|
|
392a87f33a | ||
|
|
72cd243ee3 |
@@ -20,6 +20,8 @@ module.exports.ignorePackages = () => [];
|
||||
// Files from NPM packages that we should replace with empty file
|
||||
module.exports.emptyPackages = ({ isHassioBuild, isLandingPageBuild }) =>
|
||||
[
|
||||
require.resolve("@vaadin/vaadin-material-styles/typography.js"),
|
||||
require.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
|
||||
// Icons in supervisor conflict with icons in HA so we don't load.
|
||||
(isHassioBuild || isLandingPageBuild) &&
|
||||
require.resolve(
|
||||
|
||||
@@ -168,16 +168,12 @@ const createRspackConfig = ({
|
||||
);
|
||||
},
|
||||
}),
|
||||
bundle.emptyPackages({ isHassioBuild, isLandingPageBuild }).length
|
||||
? new rspack.NormalModuleReplacementPlugin(
|
||||
new RegExp(
|
||||
bundle
|
||||
.emptyPackages({ isHassioBuild, isLandingPageBuild })
|
||||
.join("|")
|
||||
),
|
||||
path.resolve(paths.root_dir, "src/util/empty.js")
|
||||
)
|
||||
: false,
|
||||
new rspack.NormalModuleReplacementPlugin(
|
||||
new RegExp(
|
||||
bundle.emptyPackages({ isHassioBuild, isLandingPageBuild }).join("|")
|
||||
),
|
||||
path.resolve(paths.root_dir, "src/util/empty.js")
|
||||
),
|
||||
!isProdBuild && new LogStartCompilePlugin(),
|
||||
isProdBuild &&
|
||||
new StatsWriterPlugin({
|
||||
|
||||
@@ -5,19 +5,17 @@ const castContext = framework.CastReceiverContext.getInstance();
|
||||
const playerManager = castContext.getPlayerManager();
|
||||
|
||||
playerManager.setMessageInterceptor(
|
||||
"LOAD" as framework.messages.MessageType.LOAD,
|
||||
framework.messages.MessageType.LOAD,
|
||||
(loadRequestData) => {
|
||||
const media = loadRequestData.media;
|
||||
// Special handling if it came from Google Assistant
|
||||
if (media.entity) {
|
||||
media.contentId = media.entity;
|
||||
media.streamType = "LIVE" as framework.messages.StreamType.LIVE;
|
||||
media.streamType = framework.messages.StreamType.LIVE;
|
||||
media.contentType = "application/vnd.apple.mpegurl";
|
||||
// @ts-ignore
|
||||
// type definition is wrong, should be "FMP4" instead of "fmp4"
|
||||
// https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages#.HlsVideoSegmentFormat
|
||||
media.hlsVideoSegmentFormat =
|
||||
"FMP4" as framework.messages.HlsVideoSegmentFormat.FMP4;
|
||||
framework.messages.HlsVideoSegmentFormat.FMP4;
|
||||
}
|
||||
return loadRequestData;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { framework } from "./cast_framework";
|
||||
import { CAST_NS } from "../../../src/cast/const";
|
||||
import type { HassMessage } from "../../../src/cast/receiver_messages";
|
||||
import "../../../src/resources/custom-card-support";
|
||||
import { castContext } from "./cast_context";
|
||||
import { framework } from "./cast_framework";
|
||||
import { HcMain } from "./layout/hc-main";
|
||||
import type { ReceivedMessage } from "./types";
|
||||
|
||||
const lovelaceController = new HcMain();
|
||||
document.body.append(lovelaceController);
|
||||
@@ -39,8 +40,7 @@ const playDummyMedia = (viewTitle?: string) => {
|
||||
loadRequestData.media.contentId =
|
||||
"https://cast.home-assistant.io/images/google-nest-hub.png";
|
||||
loadRequestData.media.contentType = "image/jpeg";
|
||||
loadRequestData.media.streamType =
|
||||
"NONE" as framework.messages.StreamType.NONE;
|
||||
loadRequestData.media.streamType = framework.messages.StreamType.NONE;
|
||||
const metadata = new framework.messages.GenericMediaMetadata();
|
||||
metadata.title = viewTitle;
|
||||
loadRequestData.media.metadata = metadata;
|
||||
@@ -89,30 +89,31 @@ const showMediaPlayer = () => {
|
||||
const options = new framework.CastReceiverOptions();
|
||||
options.disableIdleTimeout = true;
|
||||
options.customNamespaces = {
|
||||
// type definition is wrong, should be "JSON" instead of "json"
|
||||
// https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.system#.MessageType
|
||||
[CAST_NS]: "JSON" as framework.system.MessageType.JSON,
|
||||
[CAST_NS]: framework.system.MessageType.JSON,
|
||||
};
|
||||
|
||||
castContext.addCustomMessageListener(CAST_NS, (ev) => {
|
||||
// We received a show Lovelace command, stop media from playing, hide media player and show Lovelace controller
|
||||
if (
|
||||
playerManager.getPlayerState() !==
|
||||
("IDLE" as framework.messages.PlayerState.IDLE)
|
||||
) {
|
||||
playerManager.stop();
|
||||
} else {
|
||||
showLovelaceController();
|
||||
castContext.addCustomMessageListener(
|
||||
CAST_NS,
|
||||
// @ts-ignore
|
||||
(ev: ReceivedMessage<HassMessage>) => {
|
||||
// We received a show Lovelace command, stop media from playing, hide media player and show Lovelace controller
|
||||
if (
|
||||
playerManager.getPlayerState() !== framework.messages.PlayerState.IDLE
|
||||
) {
|
||||
playerManager.stop();
|
||||
} else {
|
||||
showLovelaceController();
|
||||
}
|
||||
const msg = ev.data;
|
||||
msg.senderId = ev.senderId;
|
||||
lovelaceController.processIncomingMessage(msg);
|
||||
}
|
||||
const msg = ev.data as HassMessage;
|
||||
msg.senderId = ev.senderId;
|
||||
lovelaceController.processIncomingMessage(msg);
|
||||
});
|
||||
);
|
||||
|
||||
const playerManager = castContext.getPlayerManager();
|
||||
|
||||
playerManager.setMessageInterceptor(
|
||||
"LOAD" as framework.messages.MessageType.LOAD,
|
||||
framework.messages.MessageType.LOAD,
|
||||
(loadRequestData) => {
|
||||
if (
|
||||
loadRequestData.media.contentId ===
|
||||
@@ -126,26 +127,24 @@ playerManager.setMessageInterceptor(
|
||||
// Special handling if it came from Google Assistant
|
||||
if (media.entity) {
|
||||
media.contentId = media.entity;
|
||||
media.streamType = "LIVE" as framework.messages.StreamType.LIVE;
|
||||
media.streamType = framework.messages.StreamType.LIVE;
|
||||
media.contentType = "application/vnd.apple.mpegurl";
|
||||
// type definition is wrong, should be "FMP4" instead of "fmp4"
|
||||
// https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages#.HlsVideoSegmentFormat
|
||||
// @ts-ignore
|
||||
media.hlsVideoSegmentFormat =
|
||||
"FMP4" as framework.messages.HlsVideoSegmentFormat.FMP4;
|
||||
framework.messages.HlsVideoSegmentFormat.FMP4;
|
||||
}
|
||||
return loadRequestData;
|
||||
}
|
||||
);
|
||||
|
||||
playerManager.addEventListener(
|
||||
"MEDIA_STATUS" as framework.events.EventType.MEDIA_STATUS,
|
||||
framework.events.EventType.MEDIA_STATUS,
|
||||
(event) => {
|
||||
if (
|
||||
event.mediaStatus?.playerState ===
|
||||
("IDLE" as framework.messages.PlayerState.IDLE) &&
|
||||
event.mediaStatus?.playerState === framework.messages.PlayerState.IDLE &&
|
||||
event.mediaStatus?.idleReason &&
|
||||
event.mediaStatus?.idleReason !==
|
||||
("INTERRUPTED" as framework.messages.IdleReason.INTERRUPTED)
|
||||
framework.messages.IdleReason.INTERRUPTED
|
||||
) {
|
||||
// media finished or stopped, return to default Lovelace
|
||||
showLovelaceController();
|
||||
|
||||
6
cast/src/receiver/types.ts
Normal file
6
cast/src/receiver/types.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface ReceivedMessage<T> {
|
||||
gj: boolean;
|
||||
data: T;
|
||||
senderId: string;
|
||||
type: "message";
|
||||
}
|
||||
10
package.json
10
package.json
@@ -89,6 +89,8 @@
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@tsparticles/engine": "3.9.1",
|
||||
"@tsparticles/preset-links": "3.2.0",
|
||||
"@vaadin/combo-box": "24.9.6",
|
||||
"@vaadin/vaadin-themable-mixin": "24.9.6",
|
||||
"@vibrant/color": "4.0.0",
|
||||
"@vue/web-component-wrapper": "1.3.0",
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||
@@ -155,11 +157,11 @@
|
||||
"@octokit/auth-oauth-device": "8.0.3",
|
||||
"@octokit/plugin-retry": "8.0.3",
|
||||
"@octokit/rest": "22.0.1",
|
||||
"@rsdoctor/rspack-plugin": "1.3.16",
|
||||
"@rsdoctor/rspack-plugin": "1.3.15",
|
||||
"@rspack/core": "1.6.7",
|
||||
"@rspack/dev-server": "1.1.4",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.25",
|
||||
"@types/chromecast-caf-receiver": "6.0.22",
|
||||
"@types/chromecast-caf-sender": "1.0.11",
|
||||
"@types/color-name": "2.0.0",
|
||||
"@types/culori": "4.0.1",
|
||||
@@ -215,8 +217,8 @@
|
||||
"terser-webpack-plugin": "5.3.16",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.50.0",
|
||||
"vite-tsconfig-paths": "6.0.1",
|
||||
"typescript-eslint": "8.49.0",
|
||||
"vite-tsconfig-paths": "5.1.4",
|
||||
"vitest": "4.0.15",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
"webpackbar": "7.0.0",
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
// From https://github.com/epoberezkin/fast-deep-equal
|
||||
// MIT License - Copyright (c) 2017 Evgeny Poberezkin
|
||||
export const deepEqual = (a: any, b: any): boolean => {
|
||||
|
||||
interface DeepEqualOptions {
|
||||
/** Compare Symbol properties in addition to string keys */
|
||||
compareSymbols?: boolean;
|
||||
}
|
||||
|
||||
export const deepEqual = (
|
||||
a: any,
|
||||
b: any,
|
||||
options?: DeepEqualOptions
|
||||
): boolean => {
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
@@ -18,7 +28,7 @@ export const deepEqual = (a: any, b: any): boolean => {
|
||||
return false;
|
||||
}
|
||||
for (i = length; i-- !== 0; ) {
|
||||
if (!deepEqual(a[i], b[i])) {
|
||||
if (!deepEqual(a[i], b[i], options)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -35,7 +45,7 @@ export const deepEqual = (a: any, b: any): boolean => {
|
||||
}
|
||||
}
|
||||
for (i of a.entries()) {
|
||||
if (!deepEqual(i[1], b.get(i[0]))) {
|
||||
if (!deepEqual(i[1], b.get(i[0]), options)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -93,11 +103,28 @@ export const deepEqual = (a: any, b: any): boolean => {
|
||||
for (i = length; i-- !== 0; ) {
|
||||
const key = keys[i];
|
||||
|
||||
if (!deepEqual(a[key], b[key])) {
|
||||
if (!deepEqual(a[key], b[key], options)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Compare Symbol properties if requested
|
||||
if (options?.compareSymbols) {
|
||||
const symbolsA = Object.getOwnPropertySymbols(a);
|
||||
const symbolsB = Object.getOwnPropertySymbols(b);
|
||||
if (symbolsA.length !== symbolsB.length) {
|
||||
return false;
|
||||
}
|
||||
for (const sym of symbolsA) {
|
||||
if (!Object.prototype.hasOwnProperty.call(b, sym)) {
|
||||
return false;
|
||||
}
|
||||
if (!deepEqual(a[sym], b[sym], options)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { html, LitElement, nothing, type PropertyValues } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
@@ -162,7 +162,7 @@ export class HaDevicePicker extends LitElement {
|
||||
}
|
||||
);
|
||||
|
||||
private _rowRenderer: RenderItemFunction<DevicePickerItem> = (item) => html`
|
||||
private _rowRenderer: ComboBoxLitRenderer<DevicePickerItem> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
${item.domain
|
||||
? html`
|
||||
|
||||
@@ -61,6 +61,7 @@ class HaDevicesPicker extends LitElement {
|
||||
(entityId) => html`
|
||||
<div>
|
||||
<ha-device-picker
|
||||
allow-custom-entity
|
||||
.curValue=${entityId}
|
||||
.hass=${this.hass}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
@@ -78,6 +79,7 @@ class HaDevicesPicker extends LitElement {
|
||||
)}
|
||||
<div>
|
||||
<ha-device-picker
|
||||
allow-custom-entity
|
||||
.hass=${this.hass}
|
||||
.helper=${this.helper}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
|
||||
@@ -99,6 +99,7 @@ class HaEntitiesPicker extends LitElement {
|
||||
(entityId) => html`
|
||||
<div class="entity">
|
||||
<ha-entity-picker
|
||||
allow-custom-entity
|
||||
.curValue=${entityId}
|
||||
.hass=${this.hass}
|
||||
.includeDomains=${this.includeDomains}
|
||||
@@ -128,6 +129,7 @@ class HaEntitiesPicker extends LitElement {
|
||||
</ha-sortable>
|
||||
<div>
|
||||
<ha-entity-picker
|
||||
allow-custom-entity
|
||||
.hass=${this.hass}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../ha-generic-picker";
|
||||
import type { PickerComboBoxItem } from "../ha-picker-combo-box";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
|
||||
interface AttributeOption {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
@customElement("ha-entity-attribute-picker")
|
||||
class HaEntityAttributePicker extends LitElement {
|
||||
@@ -37,44 +42,51 @@ class HaEntityAttributePicker extends LitElement {
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
private _getItemsMemoized = memoizeOne(
|
||||
(
|
||||
entityId: string | string[] | undefined,
|
||||
hideAttributes: string[] | undefined,
|
||||
hass: HomeAssistant
|
||||
): PickerComboBoxItem[] => {
|
||||
const entityIds = entityId ? ensureArray(entityId) : [];
|
||||
const options: PickerComboBoxItem[] = [];
|
||||
const optionsSet = new Set<string>();
|
||||
@state() private _opened = false;
|
||||
|
||||
for (const id of entityIds) {
|
||||
const stateObj = hass.states[id];
|
||||
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues) {
|
||||
return !(!changedProps.has("_opened") && this._opened);
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (
|
||||
(changedProps.has("_opened") && this._opened) ||
|
||||
changedProps.has("entityId") ||
|
||||
changedProps.has("attribute")
|
||||
) {
|
||||
const entityIds = this.entityId ? ensureArray(this.entityId) : [];
|
||||
const entitiesOptions = entityIds.map<AttributeOption[]>((entityId) => {
|
||||
const stateObj = this.hass.states[entityId];
|
||||
if (!stateObj) {
|
||||
continue;
|
||||
return [];
|
||||
}
|
||||
|
||||
const attributes = Object.keys(stateObj.attributes).filter(
|
||||
(a) => !hideAttributes?.includes(a)
|
||||
(a) => !this.hideAttributes?.includes(a)
|
||||
);
|
||||
|
||||
for (const attribute of attributes) {
|
||||
if (!optionsSet.has(attribute)) {
|
||||
optionsSet.add(attribute);
|
||||
options.push({
|
||||
id: attribute,
|
||||
primary: hass.formatEntityAttributeName(stateObj, attribute),
|
||||
sorting_label: attribute,
|
||||
});
|
||||
return attributes.map((a) => ({
|
||||
value: a,
|
||||
label: this.hass.formatEntityAttributeName(stateObj, a),
|
||||
}));
|
||||
});
|
||||
|
||||
const options: AttributeOption[] = [];
|
||||
const optionsSet = new Set<string>();
|
||||
for (const entityOptions of entitiesOptions) {
|
||||
for (const option of entityOptions) {
|
||||
if (!optionsSet.has(option.value)) {
|
||||
optionsSet.add(option.value);
|
||||
options.push(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
(this._comboBox as any).filteredItems = options;
|
||||
}
|
||||
);
|
||||
|
||||
private _getItems = () =>
|
||||
this._getItemsMemoized(this.entityId, this.hideAttributes, this.hass);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass) {
|
||||
@@ -82,9 +94,10 @@ class HaEntityAttributePicker extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-generic-picker
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.autofocus=${this.autofocus}
|
||||
.label=${this.label ??
|
||||
this.hass.localize(
|
||||
"ui.components.entity.entity-attribute-picker.attribute"
|
||||
@@ -93,22 +106,39 @@ class HaEntityAttributePicker extends LitElement {
|
||||
.required=${this.required}
|
||||
.helper=${this.helper}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
.getItems=${this._getItems}
|
||||
item-id-path="value"
|
||||
item-value-path="value"
|
||||
item-label-path="label"
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
</ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return this.value || "";
|
||||
}
|
||||
|
||||
private _openedChanged(ev: ValueChangedEvent<boolean>) {
|
||||
this._opened = ev.detail.value;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: ValueChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
if (newValue !== this.value) {
|
||||
this.value = newValue;
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
fireEvent(this, "change");
|
||||
if (newValue !== this._value) {
|
||||
this._setValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private _setValue(value: string) {
|
||||
this.value = value;
|
||||
setTimeout(() => {
|
||||
fireEvent(this, "value-changed", { value });
|
||||
fireEvent(this, "change");
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import "@material/mwc-menu/mwc-menu-surface";
|
||||
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { IFuseOptions } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
import type { EntityNameItem } from "../../common/entity/compute_entity_name_display";
|
||||
import { getEntityContext } from "../../common/entity/context/get_entity_context";
|
||||
import type { EntityNameType } from "../../common/translations/entity-state";
|
||||
@@ -14,18 +18,20 @@ import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../chips/ha-assist-chip";
|
||||
import "../chips/ha-chip-set";
|
||||
import "../chips/ha-input-chip";
|
||||
import "../ha-combo-box-item";
|
||||
import "../ha-generic-picker";
|
||||
import type { HaGenericPicker } from "../ha-generic-picker";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-input-helper-text";
|
||||
import {
|
||||
NO_ITEMS_AVAILABLE_ID,
|
||||
type PickerComboBoxItem,
|
||||
} from "../ha-picker-combo-box";
|
||||
import "../ha-sortable";
|
||||
|
||||
const rowRenderer: RenderItemFunction<PickerComboBoxItem> = (item) => html`
|
||||
<ha-combo-box-item type="button" compact>
|
||||
interface EntityNameOption {
|
||||
primary: string;
|
||||
secondary?: string;
|
||||
field_label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<EntityNameOption> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
<span slot="headline">${item.primary}</span>
|
||||
${item.secondary
|
||||
? html`<span slot="supporting-text">${item.secondary}</span>`
|
||||
@@ -73,7 +79,11 @@ export class HaEntityNamePicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@query("ha-generic-picker", true) private _picker?: HaGenericPicker;
|
||||
@query(".container", true) private _container?: HTMLDivElement;
|
||||
|
||||
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
private _editIndex?: number;
|
||||
|
||||
@@ -105,7 +115,7 @@ export class HaEntityNamePicker extends LitElement {
|
||||
return options;
|
||||
});
|
||||
|
||||
private _getItems = memoizeOne((entityId?: string) => {
|
||||
private _getOptions = memoizeOne((entityId?: string) => {
|
||||
if (!entityId) {
|
||||
return [];
|
||||
}
|
||||
@@ -114,7 +124,7 @@ export class HaEntityNamePicker extends LitElement {
|
||||
|
||||
const items = (
|
||||
["entity", "device", "area", "floor"] as const
|
||||
).map<PickerComboBoxItem>((name) => {
|
||||
).map<EntityNameOption>((name) => {
|
||||
const stateObj = this.hass.states[entityId];
|
||||
const isValid = types.has(name);
|
||||
const primary = this.hass.localize(
|
||||
@@ -127,39 +137,25 @@ export class HaEntityNamePicker extends LitElement {
|
||||
`ui.components.entity.entity-name-picker.types.${name}_missing` as LocalizeKeys
|
||||
)) || "-";
|
||||
|
||||
const id = formatOptionValue({ type: name });
|
||||
|
||||
return {
|
||||
id,
|
||||
primary,
|
||||
secondary,
|
||||
search_labels: {
|
||||
primary,
|
||||
secondary: secondary || null,
|
||||
id,
|
||||
},
|
||||
sorting_label: primary,
|
||||
field_label: primary,
|
||||
value: formatOptionValue({ type: name }),
|
||||
};
|
||||
});
|
||||
|
||||
return items;
|
||||
});
|
||||
|
||||
private _customNameOption = memoizeOne(
|
||||
(text: string): PickerComboBoxItem => ({
|
||||
id: formatOptionValue({ type: "text", text }),
|
||||
primary: this.hass.localize(
|
||||
"ui.components.entity.entity-name-picker.custom_name"
|
||||
),
|
||||
secondary: `"${text}"`,
|
||||
search_labels: {
|
||||
primary: text,
|
||||
secondary: `"${text}"`,
|
||||
id: formatOptionValue({ type: "text", text }),
|
||||
},
|
||||
sorting_label: text,
|
||||
})
|
||||
);
|
||||
private _customNameOption = memoizeOne((text: string) => ({
|
||||
primary: this.hass.localize(
|
||||
"ui.components.entity.entity-name-picker.custom_name"
|
||||
),
|
||||
secondary: `"${text}"`,
|
||||
field_label: text,
|
||||
value: formatOptionValue({ type: "text", text }),
|
||||
}));
|
||||
|
||||
private _formatItem = (item: EntityNameItem) => {
|
||||
if (item.type === "text") {
|
||||
@@ -175,80 +171,88 @@ export class HaEntityNamePicker extends LitElement {
|
||||
|
||||
protected render() {
|
||||
const value = this._items;
|
||||
const options = this._getOptions(this.entityId);
|
||||
const validTypes = this._validTypes(this.entityId);
|
||||
|
||||
return html`
|
||||
${this.label ? html`<label>${this.label}</label>` : nothing}
|
||||
<ha-generic-picker
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required && !value.length}
|
||||
.getItems=${this._getFilteredItems}
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
.rowRenderer=${rowRenderer}
|
||||
.searchFn=${this._searchFn}
|
||||
.notFoundLabel=${this.hass.localize(
|
||||
"ui.components.entity.entity-name-picker.no_match"
|
||||
)}
|
||||
.value=${this._getPickerValue()}
|
||||
allow-custom-value
|
||||
.customValueLabel=${this.hass.localize(
|
||||
"ui.components.entity.entity-name-picker.custom_name"
|
||||
)}
|
||||
@value-changed=${this._pickerValueChanged}
|
||||
>
|
||||
<div slot="field" class="container">
|
||||
<ha-sortable
|
||||
no-style
|
||||
@item-moved=${this._moveItem}
|
||||
<div class="container">
|
||||
<ha-sortable
|
||||
no-style
|
||||
@item-moved=${this._moveItem}
|
||||
.disabled=${this.disabled}
|
||||
handle-selector="button.primary.action"
|
||||
filter=".add"
|
||||
>
|
||||
<ha-chip-set>
|
||||
${repeat(
|
||||
this._items,
|
||||
(item) => item,
|
||||
(item: EntityNameItem, idx) => {
|
||||
const label = this._formatItem(item);
|
||||
const isValid = validTypes.has(item.type);
|
||||
return html`
|
||||
<ha-input-chip
|
||||
data-idx=${idx}
|
||||
@remove=${this._removeItem}
|
||||
@click=${this._editItem}
|
||||
.label=${label}
|
||||
.selected=${!this.disabled}
|
||||
.disabled=${this.disabled}
|
||||
class=${!isValid ? "invalid" : ""}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
<span>${label}</span>
|
||||
</ha-input-chip>
|
||||
`;
|
||||
}
|
||||
)}
|
||||
${this.disabled
|
||||
? nothing
|
||||
: html`
|
||||
<ha-assist-chip
|
||||
@click=${this._addItem}
|
||||
.disabled=${this.disabled}
|
||||
label=${this.hass.localize(
|
||||
"ui.components.entity.entity-name-picker.add"
|
||||
)}
|
||||
class="add"
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-assist-chip>
|
||||
`}
|
||||
</ha-chip-set>
|
||||
</ha-sortable>
|
||||
|
||||
<mwc-menu-surface
|
||||
.open=${this._opened}
|
||||
@closed=${this._onClosed}
|
||||
@opened=${this._onOpened}
|
||||
@input=${stopPropagation}
|
||||
.anchor=${this._container}
|
||||
>
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.value=${""}
|
||||
.autofocus=${this.autofocus}
|
||||
.disabled=${this.disabled}
|
||||
handle-selector="button.primary.action"
|
||||
filter=".add"
|
||||
.required=${this.required && !value.length}
|
||||
.items=${options}
|
||||
allow-custom-value
|
||||
item-id-path="value"
|
||||
item-value-path="value"
|
||||
item-label-path="field_label"
|
||||
.renderer=${rowRenderer}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._comboBoxValueChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
>
|
||||
<ha-chip-set>
|
||||
${repeat(
|
||||
this._items,
|
||||
(item) => item,
|
||||
(item: EntityNameItem, idx) => {
|
||||
const label = this._formatItem(item);
|
||||
const isValid = validTypes.has(item.type);
|
||||
return html`
|
||||
<ha-input-chip
|
||||
data-idx=${idx}
|
||||
@remove=${this._removeItem}
|
||||
@click=${this._editItem}
|
||||
.label=${label}
|
||||
.selected=${!this.disabled}
|
||||
.disabled=${this.disabled}
|
||||
class=${!isValid ? "invalid" : ""}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
<span>${label}</span>
|
||||
</ha-input-chip>
|
||||
`;
|
||||
}
|
||||
)}
|
||||
${this.disabled
|
||||
? nothing
|
||||
: html`
|
||||
<ha-assist-chip
|
||||
@click=${this._addItem}
|
||||
.disabled=${this.disabled}
|
||||
label=${this.hass.localize(
|
||||
"ui.components.entity.entity-name-picker.add"
|
||||
)}
|
||||
class="add"
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-assist-chip>
|
||||
`}
|
||||
</ha-chip-set>
|
||||
</ha-sortable>
|
||||
</div>
|
||||
</ha-generic-picker>
|
||||
</ha-combo-box>
|
||||
</mwc-menu-surface>
|
||||
</div>
|
||||
${this._renderHelper()}
|
||||
`;
|
||||
}
|
||||
@@ -263,22 +267,32 @@ export class HaEntityNamePicker extends LitElement {
|
||||
: nothing;
|
||||
}
|
||||
|
||||
private async _addItem(ev: Event) {
|
||||
private _onClosed(ev) {
|
||||
ev.stopPropagation();
|
||||
this._opened = false;
|
||||
this._editIndex = undefined;
|
||||
await this.updateComplete;
|
||||
await this._picker?.open();
|
||||
}
|
||||
|
||||
private async _editItem(ev: Event) {
|
||||
private async _onOpened(ev) {
|
||||
if (!this._opened) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const idx = parseInt(
|
||||
(ev.currentTarget as HTMLElement).dataset.idx || "",
|
||||
10
|
||||
);
|
||||
this._opened = true;
|
||||
await this._comboBox?.focus();
|
||||
await this._comboBox?.open();
|
||||
}
|
||||
|
||||
private async _addItem(ev) {
|
||||
ev.stopPropagation();
|
||||
this._opened = true;
|
||||
}
|
||||
|
||||
private async _editItem(ev) {
|
||||
ev.stopPropagation();
|
||||
const idx = parseInt(ev.currentTarget.dataset.idx, 10);
|
||||
this._editIndex = idx;
|
||||
await this.updateComplete;
|
||||
await this._picker?.open();
|
||||
this._opened = true;
|
||||
}
|
||||
|
||||
private get _items(): EntityNameItem[] {
|
||||
@@ -308,80 +322,78 @@ export class HaEntityNamePicker extends LitElement {
|
||||
}
|
||||
);
|
||||
|
||||
private _getPickerValue(): string | undefined {
|
||||
if (this._editIndex != null) {
|
||||
const item = this._items[this._editIndex];
|
||||
return item ? formatOptionValue(item) : undefined;
|
||||
private _openedChanged(ev: ValueChangedEvent<boolean>) {
|
||||
const open = ev.detail.value;
|
||||
if (open) {
|
||||
const options = this._comboBox.items || [];
|
||||
|
||||
const initialItem =
|
||||
this._editIndex != null ? this._items[this._editIndex] : undefined;
|
||||
|
||||
const initialValue = initialItem ? formatOptionValue(initialItem) : "";
|
||||
|
||||
const filteredItems = this._filterSelectedOptions(options, initialValue);
|
||||
|
||||
if (initialItem?.type === "text" && initialItem.text) {
|
||||
filteredItems.push(this._customNameOption(initialItem.text));
|
||||
}
|
||||
|
||||
this._comboBox.filteredItems = filteredItems;
|
||||
this._comboBox.setInputValue(initialValue);
|
||||
} else {
|
||||
this._opened = false;
|
||||
this._comboBox.setInputValue("");
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _getFilteredItems = (
|
||||
searchString?: string,
|
||||
_section?: string
|
||||
): PickerComboBoxItem[] => {
|
||||
const items = this._getItems(this.entityId);
|
||||
const currentItem =
|
||||
this._editIndex != null ? this._items[this._editIndex] : undefined;
|
||||
const currentValue = currentItem ? formatOptionValue(currentItem) : "";
|
||||
private _filterSelectedOptions = (
|
||||
options: EntityNameOption[],
|
||||
current?: string
|
||||
) => {
|
||||
const items = this._items;
|
||||
|
||||
const excludedValues = new Set(
|
||||
this._items
|
||||
items
|
||||
.filter((item) => UNIQUE_TYPES.has(item.type))
|
||||
.map((item) => formatOptionValue(item))
|
||||
);
|
||||
|
||||
const filteredItems = items.filter(
|
||||
(item) => !excludedValues.has(item.id) || item.id === currentValue
|
||||
const filteredOptions = options.filter(
|
||||
(option) => !excludedValues.has(option.value) || option.value === current
|
||||
);
|
||||
|
||||
// When editing an existing text item, include it in the base items
|
||||
if (currentItem?.type === "text" && currentItem.text && !searchString) {
|
||||
filteredItems.push(this._customNameOption(currentItem.text));
|
||||
}
|
||||
|
||||
return filteredItems;
|
||||
return filteredOptions;
|
||||
};
|
||||
|
||||
private _getAdditionalItems = (
|
||||
searchString?: string
|
||||
): PickerComboBoxItem[] => {
|
||||
if (!searchString) {
|
||||
return [];
|
||||
}
|
||||
private _filterChanged(ev: ValueChangedEvent<string>) {
|
||||
const input = ev.detail.value;
|
||||
const filter = input?.toLowerCase() || "";
|
||||
const options = this._comboBox.items || [];
|
||||
|
||||
const currentItem =
|
||||
this._editIndex != null ? this._items[this._editIndex] : undefined;
|
||||
|
||||
// Don't add if it's the same as the current item being edited
|
||||
if (
|
||||
currentItem?.type === "text" &&
|
||||
currentItem.text &&
|
||||
currentItem.text === searchString
|
||||
) {
|
||||
return [];
|
||||
const currentValue = currentItem ? formatOptionValue(currentItem) : "";
|
||||
|
||||
let filteredItems = this._filterSelectedOptions(options, currentValue);
|
||||
|
||||
if (!filter) {
|
||||
this._comboBox.filteredItems = filteredItems;
|
||||
return;
|
||||
}
|
||||
|
||||
// Always return custom name option when there's a search string
|
||||
// This prevents "No matching items found" from showing
|
||||
return [this._customNameOption(searchString)];
|
||||
};
|
||||
const fuseOptions: IFuseOptions<EntityNameOption> = {
|
||||
keys: ["primary", "secondary", "value"],
|
||||
isCaseSensitive: false,
|
||||
minMatchCharLength: Math.min(filter.length, 2),
|
||||
threshold: 0.2,
|
||||
ignoreDiacritics: true,
|
||||
};
|
||||
|
||||
private _searchFn = (
|
||||
search: string,
|
||||
filteredItems: PickerComboBoxItem[],
|
||||
_allItems: PickerComboBoxItem[]
|
||||
): PickerComboBoxItem[] => {
|
||||
// Remove NO_ITEMS_AVAILABLE_ID if we have additional items (custom name option)
|
||||
// This prevents "No matching items found" from showing when custom values are allowed
|
||||
const hasAdditionalItems = this._getAdditionalItems(search).length > 0;
|
||||
if (hasAdditionalItems) {
|
||||
return filteredItems.filter(
|
||||
(item) => typeof item !== "string" || item !== NO_ITEMS_AVAILABLE_ID
|
||||
);
|
||||
}
|
||||
return filteredItems;
|
||||
};
|
||||
const fuse = new Fuse(filteredItems, fuseOptions);
|
||||
filteredItems = fuse.search(filter).map((result) => result.item);
|
||||
filteredItems.push(this._customNameOption(input));
|
||||
this._comboBox.filteredItems = filteredItems;
|
||||
}
|
||||
|
||||
private async _moveItem(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
@@ -391,21 +403,25 @@ export class HaEntityNamePicker extends LitElement {
|
||||
const element = newValue.splice(oldIndex, 1)[0];
|
||||
newValue.splice(newIndex, 0, element);
|
||||
this._setValue(newValue);
|
||||
await this.updateComplete;
|
||||
this._filterChanged({ detail: { value: "" } } as ValueChangedEvent<string>);
|
||||
}
|
||||
|
||||
private async _removeItem(ev: Event) {
|
||||
private async _removeItem(ev) {
|
||||
ev.stopPropagation();
|
||||
const value = [...this._items];
|
||||
const idx = parseInt((ev.target as HTMLElement).dataset.idx || "", 10);
|
||||
const idx = parseInt(ev.target.dataset.idx, 10);
|
||||
value.splice(idx, 1);
|
||||
this._setValue(value);
|
||||
await this.updateComplete;
|
||||
this._filterChanged({ detail: { value: "" } } as ValueChangedEvent<string>);
|
||||
}
|
||||
|
||||
private _pickerValueChanged(ev: ValueChangedEvent<string>): void {
|
||||
private _comboBoxValueChanged(ev: ValueChangedEvent<string>): void {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (this.disabled || !value) {
|
||||
if (this.disabled || value === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -415,16 +431,11 @@ export class HaEntityNamePicker extends LitElement {
|
||||
|
||||
if (this._editIndex != null) {
|
||||
newValue[this._editIndex] = item;
|
||||
this._editIndex = undefined;
|
||||
} else {
|
||||
newValue.push(item);
|
||||
}
|
||||
|
||||
this._setValue(newValue);
|
||||
|
||||
if (this._picker) {
|
||||
this._picker.value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _setValue(value: EntityNameItem[]) {
|
||||
@@ -486,6 +497,10 @@ export class HaEntityNamePicker extends LitElement {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
mwc-menu-surface {
|
||||
--mdc-menu-min-width: 100%;
|
||||
}
|
||||
|
||||
ha-chip-set {
|
||||
padding: var(--ha-space-2) var(--ha-space-2);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||
import { mdiPlus, mdiShape } from "@mdi/js";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { html, LitElement, nothing, type PropertyValues } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -172,9 +172,9 @@ export class HaEntityPicker extends LitElement {
|
||||
return this.showEntityId || this.hass.userData?.showEntityIdPicker;
|
||||
}
|
||||
|
||||
private _rowRenderer: RenderItemFunction<EntityComboBoxItem> = (
|
||||
private _rowRenderer: ComboBoxLitRenderer<EntityComboBoxItem> = (
|
||||
item,
|
||||
index
|
||||
{ index }
|
||||
) => {
|
||||
const showEntityId = this._showEntityId;
|
||||
|
||||
@@ -277,13 +277,12 @@ export class HaEntityPicker extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
.autofocus=${this.autofocus}
|
||||
.allowCustomValue=${this.allowCustomEntity}
|
||||
.required=${this.required}
|
||||
.label=${this.label}
|
||||
.placeholder=${placeholder}
|
||||
.helper=${this.helper}
|
||||
.value=${this.addButton ? undefined : this.value}
|
||||
.searchLabel=${this.searchLabel}
|
||||
.notFoundLabel=${this._notFoundLabel}
|
||||
.placeholder=${placeholder}
|
||||
.value=${this.addButton ? undefined : this.value}
|
||||
.rowRenderer=${this._rowRenderer}
|
||||
.getItems=${this._getItems}
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
@@ -291,7 +290,6 @@ export class HaEntityPicker extends LitElement {
|
||||
.searchFn=${this._searchFn}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.searchKeys=${entityComboBoxKeys}
|
||||
use-top-label
|
||||
.addButtonLabel=${this.addButton
|
||||
? this.hass.localize("ui.components.entity.entity-picker.add")
|
||||
: undefined}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import "@material/mwc-menu/mwc-menu-surface";
|
||||
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { IFuseOptions } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import {
|
||||
STATE_DISPLAY_SPECIAL_CONTENT,
|
||||
@@ -15,13 +20,21 @@ import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../chips/ha-assist-chip";
|
||||
import "../chips/ha-chip-set";
|
||||
import "../chips/ha-input-chip";
|
||||
import "../ha-combo-box-item";
|
||||
import "../ha-generic-picker";
|
||||
import type { HaGenericPicker } from "../ha-generic-picker";
|
||||
import "../ha-input-helper-text";
|
||||
import type { PickerComboBoxItem } from "../ha-picker-combo-box";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-sortable";
|
||||
|
||||
interface StateContentOption {
|
||||
primary: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<StateContentOption> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
<span slot="headline">${item.primary}</span>
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
|
||||
const HIDDEN_ATTRIBUTES = [
|
||||
"access_token",
|
||||
"available_modes",
|
||||
@@ -98,88 +111,63 @@ export class HaStateContentPicker extends LitElement {
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@query("ha-generic-picker", true) private _picker?: HaGenericPicker;
|
||||
@query(".container", true) private _container?: HTMLDivElement;
|
||||
|
||||
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
private _editIndex?: number;
|
||||
|
||||
private _getItems = memoizeOne(
|
||||
private _options = memoizeOne(
|
||||
(entityId?: string, stateObj?: HassEntity, allowName?: boolean) => {
|
||||
const domain = entityId ? computeDomain(entityId) : undefined;
|
||||
const items: PickerComboBoxItem[] = [
|
||||
return [
|
||||
{
|
||||
id: "state",
|
||||
primary: this.hass.localize(
|
||||
"ui.components.state-content-picker.state"
|
||||
),
|
||||
sorting_label: this.hass.localize(
|
||||
"ui.components.state-content-picker.state"
|
||||
),
|
||||
value: "state",
|
||||
},
|
||||
...(allowName
|
||||
? [
|
||||
{
|
||||
id: "name",
|
||||
primary: this.hass.localize(
|
||||
"ui.components.state-content-picker.name"
|
||||
),
|
||||
sorting_label: this.hass.localize(
|
||||
"ui.components.state-content-picker.name"
|
||||
),
|
||||
} satisfies PickerComboBoxItem,
|
||||
value: "name",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
id: "last_changed",
|
||||
primary: this.hass.localize(
|
||||
"ui.components.state-content-picker.last_changed"
|
||||
),
|
||||
sorting_label: this.hass.localize(
|
||||
"ui.components.state-content-picker.last_changed"
|
||||
),
|
||||
value: "last_changed",
|
||||
},
|
||||
{
|
||||
id: "last_updated",
|
||||
primary: this.hass.localize(
|
||||
"ui.components.state-content-picker.last_updated"
|
||||
),
|
||||
sorting_label: this.hass.localize(
|
||||
"ui.components.state-content-picker.last_updated"
|
||||
),
|
||||
value: "last_updated",
|
||||
},
|
||||
...(domain
|
||||
? STATE_DISPLAY_SPECIAL_CONTENT.filter((content) =>
|
||||
STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS[domain]?.includes(content)
|
||||
).map(
|
||||
(content) =>
|
||||
({
|
||||
id: content,
|
||||
primary: this.hass.localize(
|
||||
`ui.components.state-content-picker.${content}`
|
||||
),
|
||||
sorting_label: this.hass.localize(
|
||||
`ui.components.state-content-picker.${content}`
|
||||
),
|
||||
}) satisfies PickerComboBoxItem
|
||||
)
|
||||
).map((content) => ({
|
||||
primary: this.hass.localize(
|
||||
`ui.components.state-content-picker.${content}`
|
||||
),
|
||||
value: content,
|
||||
}))
|
||||
: []),
|
||||
...Object.keys(stateObj?.attributes ?? {})
|
||||
.filter((a) => !HIDDEN_ATTRIBUTES.includes(a))
|
||||
.map(
|
||||
(attribute) =>
|
||||
({
|
||||
id: attribute,
|
||||
primary: this.hass.formatEntityAttributeName(
|
||||
stateObj!,
|
||||
attribute
|
||||
),
|
||||
sorting_label: this.hass.formatEntityAttributeName(
|
||||
stateObj!,
|
||||
attribute
|
||||
),
|
||||
}) satisfies PickerComboBoxItem
|
||||
),
|
||||
];
|
||||
return items;
|
||||
.map((attribute) => ({
|
||||
primary: this.hass.formatEntityAttributeName(stateObj!, attribute),
|
||||
value: attribute,
|
||||
})),
|
||||
] satisfies StateContentOption[];
|
||||
}
|
||||
);
|
||||
|
||||
@@ -190,123 +178,122 @@ export class HaStateContentPicker extends LitElement {
|
||||
? this.hass.states[this.entityId]
|
||||
: undefined;
|
||||
|
||||
const options = this._options(this.entityId, stateObj, this.allowName);
|
||||
|
||||
return html`
|
||||
${this.label ? html`<label>${this.label}</label>` : nothing}
|
||||
<ha-generic-picker
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required && !value.length}
|
||||
.value=${this._getPickerValue()}
|
||||
.getItems=${this._getFilteredItems}
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
.notFoundLabel=${this.hass.localize("ui.components.combo-box.no_match")}
|
||||
allow-custom-value
|
||||
.customValueLabel=${this.hass.localize(
|
||||
"ui.components.entity.entity-state-content-picker.custom_state"
|
||||
)}
|
||||
@value-changed=${this._pickerValueChanged}
|
||||
>
|
||||
<div slot="field" class="container">
|
||||
<ha-sortable
|
||||
no-style
|
||||
@item-moved=${this._moveItem}
|
||||
.disabled=${this.disabled}
|
||||
handle-selector="button.primary.action"
|
||||
filter=".add"
|
||||
<div class="container ${this.disabled ? "disabled" : ""}">
|
||||
<ha-sortable
|
||||
no-style
|
||||
@item-moved=${this._moveItem}
|
||||
.disabled=${this.disabled}
|
||||
handle-selector="button.primary.action"
|
||||
filter=".add"
|
||||
>
|
||||
<ha-chip-set>
|
||||
${repeat(
|
||||
this._value,
|
||||
(item) => item,
|
||||
(item: string, idx) => {
|
||||
const label = options.find((o) => o.value === item)?.primary;
|
||||
const isValid = !!label;
|
||||
return html`
|
||||
<ha-input-chip
|
||||
data-idx=${idx}
|
||||
@remove=${this._removeItem}
|
||||
@click=${this._editItem}
|
||||
.label=${label || item}
|
||||
.selected=${!this.disabled}
|
||||
.disabled=${this.disabled}
|
||||
class=${!isValid ? "invalid" : ""}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
</ha-input-chip>
|
||||
`;
|
||||
}
|
||||
)}
|
||||
${this.disabled
|
||||
? nothing
|
||||
: html`
|
||||
<ha-assist-chip
|
||||
@click=${this._addItem}
|
||||
.disabled=${this.disabled}
|
||||
label=${this.hass.localize(
|
||||
"ui.components.entity.entity-state-content-picker.add"
|
||||
)}
|
||||
class="add"
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-assist-chip>
|
||||
`}
|
||||
</ha-chip-set>
|
||||
</ha-sortable>
|
||||
|
||||
<mwc-menu-surface
|
||||
.open=${this._opened}
|
||||
@closed=${this._onClosed}
|
||||
@opened=${this._onOpened}
|
||||
@input=${stopPropagation}
|
||||
.anchor=${this._container}
|
||||
>
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.value=${""}
|
||||
.autofocus=${this.autofocus}
|
||||
.disabled=${this.disabled || !this.entityId}
|
||||
.required=${this.required && !value.length}
|
||||
.helper=${this.helper}
|
||||
.items=${options}
|
||||
allow-custom-value
|
||||
item-id-path="value"
|
||||
item-value-path="value"
|
||||
item-label-path="primary"
|
||||
.renderer=${rowRenderer}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._comboBoxValueChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
>
|
||||
<ha-chip-set>
|
||||
${repeat(
|
||||
this._value,
|
||||
(item) => item,
|
||||
(item: string, idx) => {
|
||||
const label = this._getItemLabel(item, stateObj);
|
||||
const isValid = !!label;
|
||||
return html`
|
||||
<ha-input-chip
|
||||
data-idx=${idx}
|
||||
@remove=${this._removeItem}
|
||||
@click=${this._editItem}
|
||||
.label=${label || item}
|
||||
.selected=${!this.disabled}
|
||||
.disabled=${this.disabled}
|
||||
class=${!isValid ? "invalid" : ""}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
</ha-input-chip>
|
||||
`;
|
||||
}
|
||||
)}
|
||||
${this.disabled
|
||||
? nothing
|
||||
: html`
|
||||
<ha-assist-chip
|
||||
@click=${this._addItem}
|
||||
.disabled=${this.disabled}
|
||||
label=${this.hass.localize(
|
||||
"ui.components.entity.entity-state-content-picker.add"
|
||||
)}
|
||||
class="add"
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-assist-chip>
|
||||
`}
|
||||
</ha-chip-set>
|
||||
</ha-sortable>
|
||||
</div>
|
||||
</ha-generic-picker>
|
||||
${this._renderHelper()}
|
||||
</ha-combo-box>
|
||||
</mwc-menu-surface>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderHelper() {
|
||||
return this.helper
|
||||
? html`
|
||||
<ha-input-helper-text .disabled=${this.disabled}>
|
||||
${this.helper}
|
||||
</ha-input-helper-text>
|
||||
`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
private async _addItem(ev: Event) {
|
||||
private _onClosed(ev) {
|
||||
ev.stopPropagation();
|
||||
this._opened = false;
|
||||
this._editIndex = undefined;
|
||||
await this.updateComplete;
|
||||
await this._picker?.open();
|
||||
}
|
||||
|
||||
private async _editItem(ev: Event) {
|
||||
private async _onOpened(ev) {
|
||||
if (!this._opened) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const idx = parseInt(
|
||||
(ev.currentTarget as HTMLElement).dataset.idx || "",
|
||||
10
|
||||
);
|
||||
this._opened = true;
|
||||
await this._comboBox?.focus();
|
||||
await this._comboBox?.open();
|
||||
}
|
||||
|
||||
private async _addItem(ev) {
|
||||
ev.stopPropagation();
|
||||
this._opened = true;
|
||||
}
|
||||
|
||||
private async _editItem(ev) {
|
||||
ev.stopPropagation();
|
||||
const idx = parseInt(ev.currentTarget.dataset.idx, 10);
|
||||
this._editIndex = idx;
|
||||
await this.updateComplete;
|
||||
await this._picker?.open();
|
||||
this._opened = true;
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return !this.value ? [] : ensureArray(this.value);
|
||||
}
|
||||
|
||||
private _getItemLabel = memoizeOne(
|
||||
(value: string, stateObj?: HassEntity): string | undefined => {
|
||||
const stateObjForItems = this.entityId
|
||||
? this.hass.states[this.entityId]
|
||||
: stateObj;
|
||||
const items = this._getItems(
|
||||
this.entityId,
|
||||
stateObjForItems,
|
||||
this.allowName
|
||||
);
|
||||
return items.find((item) => item.id === value)?.primary;
|
||||
}
|
||||
);
|
||||
|
||||
private _toValue = memoizeOne((value: string[]): typeof this.value => {
|
||||
if (value.length === 0) {
|
||||
return undefined;
|
||||
@@ -317,87 +304,63 @@ export class HaStateContentPicker extends LitElement {
|
||||
return value;
|
||||
});
|
||||
|
||||
private _getPickerValue(): string | undefined {
|
||||
if (this._editIndex != null) {
|
||||
return this._value[this._editIndex];
|
||||
private _openedChanged(ev: ValueChangedEvent<boolean>) {
|
||||
const open = ev.detail.value;
|
||||
if (open) {
|
||||
const options = this._comboBox.items || [];
|
||||
|
||||
const initialValue =
|
||||
this._editIndex != null ? this._value[this._editIndex] : "";
|
||||
const filteredItems = this._filterSelectedOptions(options, initialValue);
|
||||
|
||||
this._comboBox.filteredItems = filteredItems;
|
||||
this._comboBox.setInputValue(initialValue);
|
||||
} else {
|
||||
this._opened = false;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _customValueOption = memoizeOne(
|
||||
(text: string): PickerComboBoxItem => ({
|
||||
id: text,
|
||||
primary: this.hass.localize(
|
||||
"ui.components.entity.entity-state-content-picker.custom_state"
|
||||
),
|
||||
secondary: `"${text}"`,
|
||||
search_labels: {
|
||||
primary: text,
|
||||
secondary: `"${text}"`,
|
||||
id: text,
|
||||
},
|
||||
sorting_label: text,
|
||||
})
|
||||
);
|
||||
|
||||
private _getFilteredItems = (
|
||||
searchString?: string,
|
||||
_section?: string
|
||||
): PickerComboBoxItem[] => {
|
||||
const stateObj = this.entityId
|
||||
? this.hass.states[this.entityId]
|
||||
: undefined;
|
||||
const items = this._getItems(this.entityId, stateObj, this.allowName);
|
||||
const currentValue =
|
||||
this._editIndex != null ? this._value[this._editIndex] : undefined;
|
||||
|
||||
private _filterSelectedOptions = (
|
||||
options: StateContentOption[],
|
||||
current?: string
|
||||
) => {
|
||||
const value = this._value;
|
||||
|
||||
const filteredItems = items.filter(
|
||||
(item) => !value.includes(item.id) || item.id === currentValue
|
||||
return options.filter(
|
||||
(option) => !value.includes(option.value) || option.value === current
|
||||
);
|
||||
|
||||
// When editing an existing custom value, include it in the base items
|
||||
if (
|
||||
currentValue &&
|
||||
!items.find((item) => item.id === currentValue) &&
|
||||
!searchString
|
||||
) {
|
||||
filteredItems.push(this._customValueOption(currentValue));
|
||||
}
|
||||
|
||||
return filteredItems;
|
||||
};
|
||||
|
||||
private _getAdditionalItems = (
|
||||
searchString?: string
|
||||
): PickerComboBoxItem[] => {
|
||||
if (!searchString) {
|
||||
return [];
|
||||
}
|
||||
private _filterChanged(ev: ValueChangedEvent<string>) {
|
||||
const input = ev.detail.value;
|
||||
const filter = input?.toLowerCase() || "";
|
||||
const options = this._comboBox.items || [];
|
||||
|
||||
const currentValue =
|
||||
this._editIndex != null ? this._value[this._editIndex] : undefined;
|
||||
this._editIndex != null ? this._value[this._editIndex] : "";
|
||||
|
||||
// Don't add if it's the same as the current item being edited
|
||||
if (currentValue && currentValue === searchString) {
|
||||
return [];
|
||||
this._comboBox.filteredItems = this._filterSelectedOptions(
|
||||
options,
|
||||
currentValue
|
||||
);
|
||||
|
||||
if (!filter) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the search string matches an existing item
|
||||
const stateObj = this.entityId
|
||||
? this.hass.states[this.entityId]
|
||||
: undefined;
|
||||
const items = this._getItems(this.entityId, stateObj, this.allowName);
|
||||
const existingItem = items.find((item) => item.id === searchString);
|
||||
const fuseOptions: IFuseOptions<StateContentOption> = {
|
||||
keys: ["primary", "secondary", "value"],
|
||||
isCaseSensitive: false,
|
||||
minMatchCharLength: Math.min(filter.length, 2),
|
||||
threshold: 0.2,
|
||||
ignoreDiacritics: true,
|
||||
};
|
||||
|
||||
// Only return custom value option if it doesn't match an existing item
|
||||
if (!existingItem) {
|
||||
return [this._customValueOption(searchString)];
|
||||
}
|
||||
const fuse = new Fuse(this._comboBox.filteredItems, fuseOptions);
|
||||
const filteredItems = fuse.search(filter).map((result) => result.item);
|
||||
|
||||
return [];
|
||||
};
|
||||
this._comboBox.filteredItems = filteredItems;
|
||||
}
|
||||
|
||||
private async _moveItem(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
@@ -407,21 +370,25 @@ export class HaStateContentPicker extends LitElement {
|
||||
const element = newValue.splice(oldIndex, 1)[0];
|
||||
newValue.splice(newIndex, 0, element);
|
||||
this._setValue(newValue);
|
||||
await this.updateComplete;
|
||||
this._filterChanged({ detail: { value: "" } } as ValueChangedEvent<string>);
|
||||
}
|
||||
|
||||
private async _removeItem(ev: Event) {
|
||||
private async _removeItem(ev) {
|
||||
ev.stopPropagation();
|
||||
const value = [...this._value];
|
||||
const idx = parseInt((ev.target as HTMLElement).dataset.idx || "", 10);
|
||||
const idx = parseInt(ev.target.dataset.idx, 10);
|
||||
value.splice(idx, 1);
|
||||
this._setValue(value);
|
||||
await this.updateComplete;
|
||||
this._filterChanged({ detail: { value: "" } } as ValueChangedEvent<string>);
|
||||
}
|
||||
|
||||
private _pickerValueChanged(ev: ValueChangedEvent<string>): void {
|
||||
private _comboBoxValueChanged(ev: ValueChangedEvent<string>): void {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (this.disabled || !value) {
|
||||
if (this.disabled || value === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -429,16 +396,11 @@ export class HaStateContentPicker extends LitElement {
|
||||
|
||||
if (this._editIndex != null) {
|
||||
newValue[this._editIndex] = value;
|
||||
this._editIndex = undefined;
|
||||
} else {
|
||||
newValue.push(value);
|
||||
}
|
||||
|
||||
this._setValue(newValue);
|
||||
|
||||
if (this._picker) {
|
||||
this._picker.value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _setValue(value: string[]) {
|
||||
@@ -480,7 +442,7 @@ export class HaStateContentPicker extends LitElement {
|
||||
height 180ms ease-in-out,
|
||||
background-color 180ms ease-in-out;
|
||||
}
|
||||
:host([disabled]) .container:after {
|
||||
.container.disabled:after {
|
||||
background-color: var(
|
||||
--mdc-text-field-disabled-line-color,
|
||||
rgba(0, 0, 0, 0.42)
|
||||
@@ -500,6 +462,10 @@ export class HaStateContentPicker extends LitElement {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
mwc-menu-surface {
|
||||
--mdc-menu-min-width: 100%;
|
||||
}
|
||||
|
||||
ha-chip-set {
|
||||
padding: var(--ha-space-2) var(--ha-space-2);
|
||||
}
|
||||
@@ -520,11 +486,6 @@ export class HaStateContentPicker extends LitElement {
|
||||
.sortable-drag {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
ha-input-helper-text {
|
||||
display: block;
|
||||
margin: var(--ha-space-2) 0 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { getStates } from "../../common/entity/get_states";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../ha-generic-picker";
|
||||
import type { PickerComboBoxItem } from "../ha-picker-combo-box";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
|
||||
interface StateOption {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
@customElement("ha-entity-state-picker")
|
||||
export class HaEntityStatePicker extends LitElement {
|
||||
class HaEntityStatePicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public entityId?: string | string[];
|
||||
|
||||
@property() public attribute?: string;
|
||||
|
||||
@property({ attribute: false }) public extraOptions?: PickerComboBoxItem[];
|
||||
@property({ attribute: false }) public extraOptions?: any[];
|
||||
|
||||
// eslint-disable-next-line lit/no-native-attributes
|
||||
@property({ type: Boolean }) public autofocus = false;
|
||||
@@ -37,76 +42,59 @@ export class HaEntityStatePicker extends LitElement {
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
private _getItems = memoizeOne(
|
||||
(
|
||||
hass: HomeAssistant,
|
||||
entityId: string | string[] | undefined,
|
||||
attribute: string | undefined,
|
||||
hideStates: string[] | undefined,
|
||||
extraOptions: PickerComboBoxItem[] | undefined
|
||||
): PickerComboBoxItem[] => {
|
||||
const entityIds = entityId ? ensureArray(entityId) : [];
|
||||
@state() private _opened = false;
|
||||
|
||||
const entitiesOptions = entityIds.map<PickerComboBoxItem[]>(
|
||||
(entityIdItem) => {
|
||||
const stateObj = hass.states[entityIdItem] || {
|
||||
entity_id: entityIdItem,
|
||||
attributes: {},
|
||||
};
|
||||
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
||||
|
||||
const states = getStates(hass, stateObj, attribute).filter(
|
||||
(s) => !hideStates?.includes(s)
|
||||
);
|
||||
protected shouldUpdate(changedProps: PropertyValues) {
|
||||
return !(!changedProps.has("_opened") && this._opened);
|
||||
}
|
||||
|
||||
return states
|
||||
.map((s) => {
|
||||
const primary = attribute
|
||||
? hass.formatEntityAttributeValue(stateObj, attribute, s)
|
||||
: hass.formatEntityState(stateObj, s);
|
||||
return {
|
||||
id: s,
|
||||
primary,
|
||||
sorting_label: primary,
|
||||
};
|
||||
})
|
||||
.filter((option) => option.id && option.primary);
|
||||
}
|
||||
);
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (
|
||||
(changedProps.has("_opened") && this._opened) ||
|
||||
changedProps.has("entityId") ||
|
||||
changedProps.has("attribute") ||
|
||||
changedProps.has("extraOptions")
|
||||
) {
|
||||
const entityIds = this.entityId ? ensureArray(this.entityId) : [];
|
||||
|
||||
const options: PickerComboBoxItem[] = [];
|
||||
const entitiesOptions = entityIds.map<StateOption[]>((entityId) => {
|
||||
const stateObj = this.hass.states[entityId] || {
|
||||
entity_id: entityId,
|
||||
attributes: {},
|
||||
};
|
||||
|
||||
const states = getStates(this.hass, stateObj, this.attribute).filter(
|
||||
(s) => !this.hideStates?.includes(s)
|
||||
);
|
||||
|
||||
return states.map((s) => ({
|
||||
value: s,
|
||||
label: this.attribute
|
||||
? this.hass.formatEntityAttributeValue(stateObj, this.attribute, s)
|
||||
: this.hass.formatEntityState(stateObj, s),
|
||||
}));
|
||||
});
|
||||
|
||||
const options: StateOption[] = [];
|
||||
const optionsSet = new Set<string>();
|
||||
for (const entityOptions of entitiesOptions) {
|
||||
for (const option of entityOptions) {
|
||||
if (!optionsSet.has(option.id)) {
|
||||
optionsSet.add(option.id);
|
||||
if (!optionsSet.has(option.value)) {
|
||||
optionsSet.add(option.value);
|
||||
options.push(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (extraOptions) {
|
||||
// Filter out any extraOptions with empty primary or id fields
|
||||
const validExtraOptions = extraOptions.filter(
|
||||
(option) => option.id && option.primary
|
||||
);
|
||||
options.unshift(...validExtraOptions);
|
||||
if (this.extraOptions) {
|
||||
options.unshift(...this.extraOptions);
|
||||
}
|
||||
|
||||
return options;
|
||||
(this._comboBox as any).filteredItems = options;
|
||||
}
|
||||
);
|
||||
|
||||
private _getFilteredItems = (
|
||||
_searchString?: string,
|
||||
_section?: string
|
||||
): PickerComboBoxItem[] =>
|
||||
this._getItems(
|
||||
this.hass,
|
||||
this.entityId,
|
||||
this.attribute,
|
||||
this.hideStates,
|
||||
this.extraOptions
|
||||
);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass) {
|
||||
@@ -114,39 +102,48 @@ export class HaEntityStatePicker extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-generic-picker
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
.disabled=${this.disabled || !this.entityId}
|
||||
.value=${this._value}
|
||||
.autofocus=${this.autofocus}
|
||||
.required=${this.required}
|
||||
.label=${this.label ??
|
||||
this.hass.localize("ui.components.entity.entity-state-picker.state")}
|
||||
.disabled=${this.disabled || !this.entityId}
|
||||
.required=${this.required}
|
||||
.helper=${this.helper}
|
||||
.value=${this.value}
|
||||
.getItems=${this._getFilteredItems}
|
||||
.notFoundLabel=${this.hass.localize("ui.components.combo-box.no_match")}
|
||||
.customValueLabel=${this.hass.localize(
|
||||
"ui.components.entity.entity-state-picker.add_custom_state"
|
||||
)}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
item-id-path="value"
|
||||
item-value-path="value"
|
||||
item-label-path="label"
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
</ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return this.value || "";
|
||||
}
|
||||
|
||||
private _openedChanged(ev: ValueChangedEvent<boolean>) {
|
||||
this._opened = ev.detail.value;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: ValueChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
if (newValue !== this.value) {
|
||||
if (newValue !== this._value) {
|
||||
this._setValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private _setValue(value: string | undefined) {
|
||||
private _setValue(value: string) {
|
||||
this.value = value;
|
||||
fireEvent(this, "value-changed", { value });
|
||||
fireEvent(this, "change");
|
||||
setTimeout(() => {
|
||||
fireEvent(this, "value-changed", { value });
|
||||
fireEvent(this, "change");
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||
import { mdiChartLine, mdiHelpCircle, mdiShape } from "@mdi/js";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { html, LitElement, nothing, type PropertyValues } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
@@ -424,9 +424,9 @@ export class HaStatisticPicker extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
private _rowRenderer: RenderItemFunction<StatisticComboBoxItem> = (
|
||||
private _rowRenderer: ComboBoxLitRenderer<StatisticComboBoxItem> = (
|
||||
item,
|
||||
index
|
||||
{ index }
|
||||
) => {
|
||||
const showEntityId = this.hass.userData?.showEntityIdPicker;
|
||||
return html`
|
||||
@@ -471,14 +471,14 @@ export class HaStatisticPicker extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.autofocus=${this.autofocus}
|
||||
.allowCustomValue=${this.allowCustomEntity}
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.label}
|
||||
.placeholder=${placeholder}
|
||||
.value=${this.value}
|
||||
.disabled=${this.disabled}
|
||||
.notFoundLabel=${this._notFoundLabel}
|
||||
.emptyLabel=${this.hass.localize(
|
||||
"ui.components.statistic-picker.no_statistics"
|
||||
)}
|
||||
.placeholder=${placeholder}
|
||||
.value=${this.value}
|
||||
.rowRenderer=${this._rowRenderer}
|
||||
.getItems=${this._getItems}
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import type { HassioAddonInfo } from "../data/hassio/addon";
|
||||
import { fetchHassioAddonsInfo } from "../data/hassio/addon";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import "./ha-alert";
|
||||
import "./ha-combo-box";
|
||||
import type { HaComboBox } from "./ha-combo-box";
|
||||
import "./ha-combo-box-item";
|
||||
import "./ha-generic-picker";
|
||||
import type { HaGenericPicker } from "./ha-generic-picker";
|
||||
import type { PickerComboBoxItem } from "./ha-picker-combo-box";
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "primary", weight: 10 },
|
||||
{ name: "secondary", weight: 8 },
|
||||
{ name: "search_labels.description", weight: 6 },
|
||||
{ name: "search_labels.repository", weight: 5 },
|
||||
];
|
||||
|
||||
const rowRenderer: RenderItemFunction<PickerComboBoxItem> = (item) => html`
|
||||
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
<span slot="headline">${item.primary}</span>
|
||||
<span slot="supporting-text">${item.secondary}</span>
|
||||
<span slot="headline">${item.name}</span>
|
||||
<span slot="supporting-text">${item.slug}</span>
|
||||
${item.icon
|
||||
? html` <img alt="" slot="start" .src=${item.icon} /> `
|
||||
? html`
|
||||
<img
|
||||
alt=""
|
||||
slot="start"
|
||||
.src="/api/hassio/addons/${item.slug}/icon"
|
||||
/>
|
||||
`
|
||||
: nothing}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
@@ -38,22 +38,22 @@ class HaAddonPicker extends LitElement {
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@state() private _addons?: PickerComboBoxItem[];
|
||||
@state() private _addons?: HassioAddonInfo[];
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@query("ha-generic-picker") private _genericPicker!: HaGenericPicker;
|
||||
@query("ha-combo-box") private _comboBox!: HaComboBox;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
public open() {
|
||||
this._genericPicker?.open();
|
||||
this._comboBox?.open();
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this._genericPicker?.focus();
|
||||
this._comboBox?.focus();
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
@@ -61,34 +61,29 @@ class HaAddonPicker extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const label =
|
||||
this.label === undefined && this.hass
|
||||
? this.hass.localize("ui.components.addon-picker.addon")
|
||||
: this.label;
|
||||
|
||||
if (this._error) {
|
||||
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
|
||||
}
|
||||
if (!this._addons) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-generic-picker
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.autofocus=${this.autofocus}
|
||||
.label=${label}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.label === undefined && this.hass
|
||||
? this.hass.localize("ui.components.addon-picker.addon")
|
||||
: this.label}
|
||||
.value=${this._value}
|
||||
.required=${this.required}
|
||||
.value=${this.value}
|
||||
.getItems=${this._getItems}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
.rowRenderer=${rowRenderer}
|
||||
.disabled=${this.disabled}
|
||||
.helper=${this.helper}
|
||||
.renderer=${rowRenderer}
|
||||
.items=${this._addons}
|
||||
item-value-path="slug"
|
||||
item-id-path="slug"
|
||||
item-label-path="name"
|
||||
@value-changed=${this._addonChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
></ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -98,19 +93,9 @@ class HaAddonPicker extends LitElement {
|
||||
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
|
||||
this._addons = addonsInfo.addons
|
||||
.filter((addon) => addon.version)
|
||||
.map((addon) => ({
|
||||
id: addon.slug,
|
||||
primary: addon.name,
|
||||
secondary: addon.slug,
|
||||
icon: addon.icon
|
||||
? `/api/hassio/addons/${addon.slug}/icon`
|
||||
: undefined,
|
||||
search_labels: {
|
||||
description: addon.description || null,
|
||||
repository: addon.repository || null,
|
||||
},
|
||||
sorting_label: [addon.name, addon.slug].filter(Boolean).join("_"),
|
||||
}));
|
||||
.sort((a, b) =>
|
||||
stringCompare(a.name, b.name, this.hass.locale.language)
|
||||
);
|
||||
} else {
|
||||
this._error = this.hass.localize(
|
||||
"ui.components.addon-picker.error.no_supervisor"
|
||||
@@ -123,8 +108,6 @@ class HaAddonPicker extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _getItems = () => this._addons!;
|
||||
|
||||
private get _value() {
|
||||
return this.value || "";
|
||||
}
|
||||
@@ -145,17 +128,6 @@ class HaAddonPicker extends LitElement {
|
||||
fireEvent(this, "change");
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private _valueRenderer = (itemId: string) => {
|
||||
const item = this._addons!.find((addon) => addon.id === itemId);
|
||||
return html`${item?.icon
|
||||
? html`<img
|
||||
slot="start"
|
||||
alt=${item.primary ?? "Unknown"}
|
||||
.src=${item.icon}
|
||||
/>`
|
||||
: nothing}<span slot="headline">${item?.primary || "Unknown"}</span>`;
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -51,6 +51,9 @@ export class HaAreaPicker extends LitElement {
|
||||
@property({ type: Boolean, attribute: "no-add" })
|
||||
public noAdd = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "show-label" })
|
||||
public showLabel = false;
|
||||
|
||||
/**
|
||||
* Show only areas with entities from specific domains.
|
||||
* @type {Array}
|
||||
@@ -363,19 +366,16 @@ export class HaAreaPicker extends LitElement {
|
||||
};
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const baseLabel =
|
||||
this.label ?? this.hass.localize("ui.components.area-picker.area");
|
||||
const placeholder =
|
||||
this.placeholder ?? this.hass.localize("ui.components.area-picker.area");
|
||||
const valueRenderer = this._computeValueRenderer(this.hass.areas);
|
||||
|
||||
// Only show label if there's no floor
|
||||
let label: string | undefined = baseLabel;
|
||||
if (this.value && baseLabel) {
|
||||
let showLabel = this.showLabel;
|
||||
if (this.value) {
|
||||
const area = this.hass.areas[this.value];
|
||||
if (area) {
|
||||
const { floor } = getAreaContext(area, this.hass.floors);
|
||||
if (floor) {
|
||||
label = undefined;
|
||||
}
|
||||
showLabel = !floor && this.showLabel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,12 +383,14 @@ export class HaAreaPicker extends LitElement {
|
||||
<ha-generic-picker
|
||||
.hass=${this.hass}
|
||||
.autofocus=${this.autofocus}
|
||||
.label=${label}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.notFoundLabel=${this._notFoundLabel}
|
||||
.emptyLabel=${this.hass.localize("ui.components.area-picker.no_areas")}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.placeholder=${placeholder}
|
||||
.showLabel=${showLabel}
|
||||
.value=${this.value}
|
||||
.getItems=${this._getItems}
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import { mdiInvertColorsOff, mdiPalette } from "@mdi/js";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeCssColor, THEME_COLORS } from "../common/color/compute-color";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import type { LocalizeKeys } from "../common/translations/localize";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-generic-picker";
|
||||
import type { PickerComboBoxItem } from "./ha-picker-combo-box";
|
||||
import type { PickerValueRenderer } from "./ha-picker-field";
|
||||
import "./ha-list-item";
|
||||
import "./ha-md-divider";
|
||||
import "./ha-select";
|
||||
import type { HaSelect } from "./ha-select";
|
||||
|
||||
@customElement("ha-color-picker")
|
||||
export class HaColorPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property({ type: String, attribute: "default_color" })
|
||||
@@ -31,178 +33,137 @@ export class HaColorPicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
@query("ha-select") private _select?: HaSelect;
|
||||
|
||||
render() {
|
||||
const effectiveValue = this.value ?? this.defaultColor ?? "";
|
||||
|
||||
return html`
|
||||
<ha-generic-picker
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.hideClearIcon=${!this.value && !!this.defaultColor}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.value=${effectiveValue}
|
||||
.getItems=${this._getItems}
|
||||
.rowRenderer=${this._rowRenderer}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
`;
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
// Refresh layout options when the field is connected to the DOM to ensure current value displayed
|
||||
this._select?.layoutOptions();
|
||||
}
|
||||
|
||||
private _getItems = () =>
|
||||
this._getColors(
|
||||
this.includeNone,
|
||||
this.includeState,
|
||||
this.defaultColor,
|
||||
this.value
|
||||
);
|
||||
|
||||
private _getColors = (
|
||||
includeNone: boolean,
|
||||
includeState: boolean,
|
||||
defaultColor: string | undefined,
|
||||
currentValue: string | undefined
|
||||
): PickerComboBoxItem[] => {
|
||||
const items: PickerComboBoxItem[] = [];
|
||||
|
||||
const defaultSuffix = this.hass.localize(
|
||||
"ui.components.color-picker.default"
|
||||
);
|
||||
|
||||
const addDefaultSuffix = (label: string, isDefault: boolean) =>
|
||||
isDefault && defaultSuffix ? `${label} (${defaultSuffix})` : label;
|
||||
|
||||
if (includeNone) {
|
||||
const noneLabel =
|
||||
this.hass.localize("ui.components.color-picker.none") || "None";
|
||||
items.push({
|
||||
id: "none",
|
||||
primary: addDefaultSuffix(noneLabel, defaultColor === "none"),
|
||||
icon_path: mdiInvertColorsOff,
|
||||
sorting_label: noneLabel,
|
||||
});
|
||||
}
|
||||
|
||||
if (includeState) {
|
||||
const stateLabel =
|
||||
this.hass.localize("ui.components.color-picker.state") || "State";
|
||||
items.push({
|
||||
id: "state",
|
||||
primary: addDefaultSuffix(stateLabel, defaultColor === "state"),
|
||||
icon_path: mdiPalette,
|
||||
sorting_label: stateLabel,
|
||||
});
|
||||
}
|
||||
|
||||
Array.from(THEME_COLORS).forEach((color) => {
|
||||
const themeLabel =
|
||||
this.hass.localize(
|
||||
`ui.components.color-picker.colors.${color}` as LocalizeKeys
|
||||
) || color;
|
||||
items.push({
|
||||
id: color,
|
||||
primary: addDefaultSuffix(themeLabel, defaultColor === color),
|
||||
sorting_label: themeLabel,
|
||||
});
|
||||
private _valueSelected(ev) {
|
||||
ev.stopPropagation();
|
||||
if (!this.isConnected) return;
|
||||
const value = ev.target.value;
|
||||
this.value = value === this.defaultColor ? undefined : value;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: this.value,
|
||||
});
|
||||
}
|
||||
|
||||
const isSpecial =
|
||||
currentValue === "none" ||
|
||||
currentValue === "state" ||
|
||||
THEME_COLORS.has(currentValue || "");
|
||||
render() {
|
||||
const value = this.value || this.defaultColor || "";
|
||||
|
||||
const hasValue = currentValue && currentValue.length > 0;
|
||||
|
||||
if (hasValue && !isSpecial) {
|
||||
items.push({
|
||||
id: currentValue!,
|
||||
primary: currentValue!,
|
||||
sorting_label: currentValue!,
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
private _rowRenderer: (
|
||||
item: PickerComboBoxItem,
|
||||
index?: number
|
||||
) => ReturnType<typeof html> = (item) => html`
|
||||
<ha-combo-box-item type="button" compact>
|
||||
${item.id === "none"
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiInvertColorsOff}
|
||||
></ha-svg-icon>`
|
||||
: item.id === "state"
|
||||
? html`<ha-svg-icon slot="start" .path=${mdiPalette}></ha-svg-icon>`
|
||||
: html`<span slot="start">
|
||||
${this._renderColorCircle(item.id)}
|
||||
</span>`}
|
||||
<span slot="headline">${item.primary}</span>
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
|
||||
private _valueRenderer: PickerValueRenderer = (value: string) => {
|
||||
if (value === "none") {
|
||||
return html`
|
||||
<ha-svg-icon slot="start" .path=${mdiInvertColorsOff}></ha-svg-icon>
|
||||
<span slot="headline">
|
||||
${this.hass.localize("ui.components.color-picker.none")}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
if (value === "state") {
|
||||
return html`
|
||||
<ha-svg-icon slot="start" .path=${mdiPalette}></ha-svg-icon>
|
||||
<span slot="headline">
|
||||
${this.hass.localize("ui.components.color-picker.state")}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
const isCustom = !(
|
||||
THEME_COLORS.has(value) ||
|
||||
value === "none" ||
|
||||
value === "state"
|
||||
);
|
||||
|
||||
return html`
|
||||
<span slot="start">${this._renderColorCircle(value)}</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
`ui.components.color-picker.colors.${value}` as LocalizeKeys
|
||||
) || value}
|
||||
</span>
|
||||
<ha-select
|
||||
.icon=${Boolean(value)}
|
||||
.label=${this.label}
|
||||
.value=${value}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
@closed=${stopPropagation}
|
||||
@selected=${this._valueSelected}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
.clearable=${!this.defaultColor}
|
||||
>
|
||||
${value
|
||||
? html`
|
||||
<span slot="icon">
|
||||
${value === "none"
|
||||
? html`
|
||||
<ha-svg-icon path=${mdiInvertColorsOff}></ha-svg-icon>
|
||||
`
|
||||
: value === "state"
|
||||
? html`<ha-svg-icon path=${mdiPalette}></ha-svg-icon>`
|
||||
: this._renderColorCircle(value || "grey")}
|
||||
</span>
|
||||
`
|
||||
: nothing}
|
||||
${this.includeNone
|
||||
? html`
|
||||
<ha-list-item value="none" graphic="icon">
|
||||
${this.hass.localize("ui.components.color-picker.none")}
|
||||
${this.defaultColor === "none"
|
||||
? ` (${this.hass.localize("ui.components.color-picker.default")})`
|
||||
: nothing}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
path=${mdiInvertColorsOff}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
`
|
||||
: nothing}
|
||||
${this.includeState
|
||||
? html`
|
||||
<ha-list-item value="state" graphic="icon">
|
||||
${this.hass.localize("ui.components.color-picker.state")}
|
||||
${this.defaultColor === "state"
|
||||
? ` (${this.hass.localize("ui.components.color-picker.default")})`
|
||||
: nothing}
|
||||
<ha-svg-icon slot="graphic" path=${mdiPalette}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
`
|
||||
: nothing}
|
||||
${this.includeState || this.includeNone
|
||||
? html`<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>`
|
||||
: nothing}
|
||||
${Array.from(THEME_COLORS).map(
|
||||
(color) => html`
|
||||
<ha-list-item .value=${color} graphic="icon">
|
||||
${this.hass.localize(
|
||||
`ui.components.color-picker.colors.${color}` as LocalizeKeys
|
||||
) || color}
|
||||
${this.defaultColor === color
|
||||
? ` (${this.hass.localize("ui.components.color-picker.default")})`
|
||||
: nothing}
|
||||
<span slot="graphic">${this._renderColorCircle(color)}</span>
|
||||
</ha-list-item>
|
||||
`
|
||||
)}
|
||||
${isCustom
|
||||
? html`
|
||||
<ha-list-item .value=${value} graphic="icon">
|
||||
${value}
|
||||
<span slot="graphic">${this._renderColorCircle(value)}</span>
|
||||
</ha-list-item>
|
||||
`
|
||||
: nothing}
|
||||
</ha-select>
|
||||
`;
|
||||
};
|
||||
}
|
||||
|
||||
private _renderColorCircle(color: string) {
|
||||
return html`
|
||||
<span
|
||||
class="circle-color"
|
||||
style=${styleMap({
|
||||
"--circle-color": computeCssColor(color),
|
||||
display: "block",
|
||||
"background-color": "var(--circle-color, var(--divider-color))",
|
||||
border: "1px solid var(--outline-color)",
|
||||
"border-radius": "var(--ha-border-radius-pill)",
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
"box-sizing": "border-box",
|
||||
})}
|
||||
></span>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent<{ value?: string }>) {
|
||||
ev.stopPropagation();
|
||||
const selected = ev.detail.value;
|
||||
const normalized =
|
||||
selected && selected === this.defaultColor
|
||||
? undefined
|
||||
: (selected ?? undefined);
|
||||
this.value = normalized;
|
||||
fireEvent(this, "value-changed", { value: this.value });
|
||||
}
|
||||
static styles = css`
|
||||
.circle-color {
|
||||
display: block;
|
||||
background-color: var(--circle-color, var(--divider-color));
|
||||
border: 1px solid var(--outline-color);
|
||||
border-radius: var(--ha-border-radius-pill);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
ha-select {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
433
src/components/ha-combo-box.ts
Normal file
433
src/components/ha-combo-box.ts
Normal file
@@ -0,0 +1,433 @@
|
||||
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { comboBoxRenderer } from "@vaadin/combo-box/lit";
|
||||
import "@vaadin/combo-box/theme/material/vaadin-combo-box-light";
|
||||
import type {
|
||||
ComboBoxDataProvider,
|
||||
ComboBoxLight,
|
||||
ComboBoxLightFilterChangedEvent,
|
||||
ComboBoxLightOpenedChangedEvent,
|
||||
ComboBoxLightValueChangedEvent,
|
||||
} from "@vaadin/combo-box/vaadin-combo-box-light";
|
||||
import { registerStyles } from "@vaadin/vaadin-themable-mixin/register-styles";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } 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 type { HomeAssistant } from "../types";
|
||||
import "./ha-combo-box-item";
|
||||
import "./ha-combo-box-textfield";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-input-helper-text";
|
||||
import "./ha-textfield";
|
||||
import type { HaTextField } from "./ha-textfield";
|
||||
|
||||
registerStyles(
|
||||
"vaadin-combo-box-item",
|
||||
css`
|
||||
:host {
|
||||
padding: 0 !important;
|
||||
}
|
||||
:host([focused]:not([disabled])) {
|
||||
background-color: rgba(var(--rgb-primary-text-color, 0, 0, 0), 0.12);
|
||||
}
|
||||
:host([selected]:not([disabled])) {
|
||||
background-color: transparent;
|
||||
color: var(--mdc-theme-primary);
|
||||
--mdc-ripple-color: var(--mdc-theme-primary);
|
||||
--mdc-theme-text-primary-on-background: var(--mdc-theme-primary);
|
||||
}
|
||||
:host([selected]:not([disabled])):before {
|
||||
background-color: var(--mdc-theme-primary);
|
||||
opacity: 0.12;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
:host([selected][focused]:not([disabled])):before {
|
||||
opacity: 0.24;
|
||||
}
|
||||
:host(:hover:not([disabled])) {
|
||||
background-color: transparent;
|
||||
}
|
||||
[part="content"] {
|
||||
width: 100%;
|
||||
}
|
||||
[part="checkmark"] {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
@customElement("ha-combo-box")
|
||||
export class HaComboBox extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property({ attribute: false }) public validationMessage?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ attribute: "error-message" }) public errorMessage?: string;
|
||||
|
||||
@property({ type: Boolean }) public invalid = false;
|
||||
|
||||
@property({ type: Boolean }) public icon = false;
|
||||
|
||||
@property({ attribute: false }) public items?: any[];
|
||||
|
||||
@property({ attribute: false }) public filteredItems?: any[];
|
||||
|
||||
@property({ attribute: false })
|
||||
public dataProvider?: ComboBoxDataProvider<any>;
|
||||
|
||||
@property({ attribute: "allow-custom-value", type: Boolean })
|
||||
public allowCustomValue = false;
|
||||
|
||||
@property({ attribute: "item-value-path" }) public itemValuePath = "value";
|
||||
|
||||
@property({ attribute: "item-label-path" }) public itemLabelPath = "label";
|
||||
|
||||
@property({ attribute: "item-id-path" }) public itemIdPath?: string;
|
||||
|
||||
@property({ attribute: false }) public renderer?: ComboBoxLitRenderer<any>;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public opened = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "hide-clear-icon" })
|
||||
public hideClearIcon = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "clear-initial-value" })
|
||||
public clearInitialValue = false;
|
||||
|
||||
@query("vaadin-combo-box-light", true) private _comboBox!: ComboBoxLight;
|
||||
|
||||
@query("ha-combo-box-textfield", true) private _inputElement!: HaTextField;
|
||||
|
||||
@state({ type: Boolean }) private _forceBlankValue = false;
|
||||
|
||||
private _overlayMutationObserver?: MutationObserver;
|
||||
|
||||
private _bodyMutationObserver?: MutationObserver;
|
||||
|
||||
public async open() {
|
||||
await this.updateComplete;
|
||||
this._comboBox?.open();
|
||||
}
|
||||
|
||||
public async focus() {
|
||||
await this.updateComplete;
|
||||
await this._inputElement?.updateComplete;
|
||||
this._inputElement?.focus();
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (this._overlayMutationObserver) {
|
||||
this._overlayMutationObserver.disconnect();
|
||||
this._overlayMutationObserver = undefined;
|
||||
}
|
||||
if (this._bodyMutationObserver) {
|
||||
this._bodyMutationObserver.disconnect();
|
||||
this._bodyMutationObserver = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public get selectedItem() {
|
||||
return this._comboBox.selectedItem;
|
||||
}
|
||||
|
||||
public setInputValue(value: string) {
|
||||
this._comboBox.value = value;
|
||||
}
|
||||
|
||||
public setTextFieldValue(value: string) {
|
||||
this._inputElement.value = value;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<!-- @ts-ignore Tag definition is not included in theme folder -->
|
||||
<vaadin-combo-box-light
|
||||
.itemValuePath=${this.itemValuePath}
|
||||
.itemIdPath=${this.itemIdPath}
|
||||
.itemLabelPath=${this.itemLabelPath}
|
||||
.items=${this.items}
|
||||
.value=${this.value || ""}
|
||||
.filteredItems=${this.filteredItems}
|
||||
.dataProvider=${this.dataProvider}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
${comboBoxRenderer(this.renderer || this._defaultRowRenderer)}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
attr-for-value="value"
|
||||
>
|
||||
<ha-combo-box-textfield
|
||||
label=${ifDefined(this.label)}
|
||||
placeholder=${ifDefined(this.placeholder)}
|
||||
?disabled=${this.disabled}
|
||||
?required=${this.required}
|
||||
validationMessage=${ifDefined(this.validationMessage)}
|
||||
.errorMessage=${this.errorMessage}
|
||||
class="input"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
.autocorrect=${false}
|
||||
input-spellcheck="false"
|
||||
.suffix=${html`<div
|
||||
style="width: 28px;"
|
||||
role="none presentation"
|
||||
></div>`}
|
||||
.icon=${this.icon}
|
||||
.invalid=${this.invalid}
|
||||
.forceBlankValue=${this._forceBlankValue}
|
||||
>
|
||||
<slot name="icon" slot="leadingIcon"></slot>
|
||||
</ha-combo-box-textfield>
|
||||
${this.value && !this.hideClearIcon
|
||||
? html`<ha-svg-icon
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
aria-label=${ifDefined(this.hass?.localize("ui.common.clear"))}
|
||||
class=${`clear-button ${this.label ? "" : "no-label"}`}
|
||||
.path=${mdiClose}
|
||||
?disabled=${this.disabled}
|
||||
@click=${this._clearValue}
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
<ha-svg-icon
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
aria-label=${ifDefined(this.label)}
|
||||
aria-expanded=${this.opened ? "true" : "false"}
|
||||
class=${`toggle-button ${this.label ? "" : "no-label"}`}
|
||||
.path=${this.opened ? mdiMenuUp : mdiMenuDown}
|
||||
?disabled=${this.disabled}
|
||||
@click=${this._toggleOpen}
|
||||
></ha-svg-icon>
|
||||
</vaadin-combo-box-light>
|
||||
${this._renderHelper()}
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderHelper() {
|
||||
return this.helper
|
||||
? html`<ha-input-helper-text .disabled=${this.disabled}
|
||||
>${this.helper}</ha-input-helper-text
|
||||
>`
|
||||
: "";
|
||||
}
|
||||
|
||||
private _defaultRowRenderer: ComboBoxLitRenderer<
|
||||
string | Record<string, any>
|
||||
> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
${this.itemLabelPath ? item[this.itemLabelPath] : item}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
|
||||
private _clearValue(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", { value: undefined });
|
||||
}
|
||||
|
||||
private _toggleOpen(ev: Event) {
|
||||
if (this.opened) {
|
||||
this._comboBox?.close();
|
||||
ev.stopPropagation();
|
||||
} else {
|
||||
this._comboBox?.inputElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _openedChanged(ev: ComboBoxLightOpenedChangedEvent) {
|
||||
ev.stopPropagation();
|
||||
const opened = ev.detail.value;
|
||||
// delay this so we can handle click event for toggle button before setting _opened
|
||||
setTimeout(() => {
|
||||
this.opened = opened;
|
||||
fireEvent(this, "opened-changed", { value: ev.detail.value });
|
||||
}, 0);
|
||||
|
||||
if (this.clearInitialValue) {
|
||||
this.setTextFieldValue("");
|
||||
if (opened) {
|
||||
// Wait 100ms to be sure vaddin-combo-box-light already tried to set the value
|
||||
setTimeout(() => {
|
||||
this._forceBlankValue = false;
|
||||
}, 100);
|
||||
} else {
|
||||
this._forceBlankValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (opened) {
|
||||
const overlay = document.querySelector<HTMLElement>(
|
||||
"vaadin-combo-box-overlay"
|
||||
);
|
||||
|
||||
if (overlay) {
|
||||
this._removeInert(overlay);
|
||||
}
|
||||
this._observeBody();
|
||||
} else {
|
||||
this._bodyMutationObserver?.disconnect();
|
||||
this._bodyMutationObserver = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _observeBody() {
|
||||
if ("MutationObserver" in window && !this._bodyMutationObserver) {
|
||||
this._bodyMutationObserver = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node.nodeName === "VAADIN-COMBO-BOX-OVERLAY") {
|
||||
this._removeInert(node as HTMLElement);
|
||||
}
|
||||
});
|
||||
mutation.removedNodes.forEach((node) => {
|
||||
if (node.nodeName === "VAADIN-COMBO-BOX-OVERLAY") {
|
||||
this._overlayMutationObserver?.disconnect();
|
||||
this._overlayMutationObserver = undefined;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this._bodyMutationObserver.observe(document.body, {
|
||||
childList: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _removeInert(overlay: HTMLElement) {
|
||||
if (overlay.inert) {
|
||||
overlay.inert = false;
|
||||
this._overlayMutationObserver?.disconnect();
|
||||
this._overlayMutationObserver = undefined;
|
||||
return;
|
||||
}
|
||||
if ("MutationObserver" in window && !this._overlayMutationObserver) {
|
||||
this._overlayMutationObserver = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.attributeName === "inert") {
|
||||
const target = mutation.target as HTMLElement;
|
||||
if (target.inert) {
|
||||
this._overlayMutationObserver?.disconnect();
|
||||
this._overlayMutationObserver = undefined;
|
||||
target.inert = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this._overlayMutationObserver.observe(overlay, {
|
||||
attributes: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _filterChanged(ev: ComboBoxLightFilterChangedEvent) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "filter-changed", { value: ev.detail.value });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: ComboBoxLightValueChangedEvent) {
|
||||
ev.stopPropagation();
|
||||
if (!this.allowCustomValue) {
|
||||
// @ts-ignore
|
||||
this._comboBox._closeOnBlurIsPrevented = true;
|
||||
}
|
||||
if (!this.opened) {
|
||||
return;
|
||||
}
|
||||
const newValue = ev.detail.value;
|
||||
if (newValue !== this.value) {
|
||||
fireEvent(this, "value-changed", { value: newValue || undefined });
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
vaadin-combo-box-light {
|
||||
position: relative;
|
||||
}
|
||||
ha-combo-box-textfield {
|
||||
width: 100%;
|
||||
}
|
||||
ha-combo-box-textfield > ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
padding: 2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-svg-icon {
|
||||
color: var(--input-dropdown-icon-color);
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
.toggle-button {
|
||||
right: 12px;
|
||||
top: -10px;
|
||||
inset-inline-start: initial;
|
||||
inset-inline-end: 12px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
:host([opened]) .toggle-button {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.toggle-button[disabled],
|
||||
.clear-button[disabled] {
|
||||
color: var(--disabled-text-color);
|
||||
pointer-events: none;
|
||||
}
|
||||
.toggle-button.no-label {
|
||||
top: -3px;
|
||||
}
|
||||
.clear-button {
|
||||
--mdc-icon-size: 20px;
|
||||
top: -7px;
|
||||
right: 36px;
|
||||
inset-inline-start: initial;
|
||||
inset-inline-end: 36px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.clear-button.no-label {
|
||||
top: 0;
|
||||
}
|
||||
ha-input-helper-text {
|
||||
margin-top: 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-combo-box": HaComboBox;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"filter-changed": { value: string };
|
||||
"opened-changed": { value: boolean };
|
||||
}
|
||||
}
|
||||
@@ -57,9 +57,10 @@ class HaConfigEntryPicker extends LitElement {
|
||||
return html`
|
||||
<ha-generic-picker
|
||||
.hass=${this.hass}
|
||||
.label=${this.label === undefined && this.hass
|
||||
.placeholder=${this.label === undefined && this.hass
|
||||
? this.hass.localize("ui.components.config-entry-picker.config_entry")
|
||||
: this.label}
|
||||
show-label
|
||||
.value=${this.value}
|
||||
.required=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||
import { mdiPlus, mdiTextureBox } from "@mdi/js";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, html } from "lit";
|
||||
@@ -303,7 +303,7 @@ export class HaFloorPicker extends LitElement {
|
||||
}
|
||||
);
|
||||
|
||||
private _rowRenderer: RenderItemFunction<FloorComboBoxItem> = (item) => html`
|
||||
private _rowRenderer: ComboBoxLitRenderer<FloorComboBoxItem> = (item) => html`
|
||||
<ha-combo-box-item type="button" compact>
|
||||
${item.icon_path
|
||||
? html`
|
||||
@@ -389,14 +389,14 @@ export class HaFloorPicker extends LitElement {
|
||||
<ha-generic-picker
|
||||
.hass=${this.hass}
|
||||
.autofocus=${this.autofocus}
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.placeholder=${placeholder}
|
||||
.disabled=${this.disabled}
|
||||
.notFoundLabel=${this._notFoundLabel}
|
||||
.emptyLabel=${this.hass.localize(
|
||||
"ui.components.floor-picker.no_floors"
|
||||
)}
|
||||
.placeholder=${placeholder}
|
||||
.value=${this.value}
|
||||
.getItems=${this._getItems}
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
|
||||
@@ -7,10 +7,8 @@ import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { tinykeys } from "tinykeys";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { PickerMixin } from "../mixins/picker-mixin";
|
||||
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { isIosApp } from "../util/is_ios";
|
||||
import "./ha-bottom-sheet";
|
||||
import "./ha-button";
|
||||
import "./ha-combo-box-item";
|
||||
@@ -22,18 +20,39 @@ import type {
|
||||
PickerComboBoxSearchFn,
|
||||
} from "./ha-picker-combo-box";
|
||||
import "./ha-picker-field";
|
||||
import type { PickerValueRenderer } from "./ha-picker-field";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-generic-picker")
|
||||
export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
export class HaGenericPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "allow-custom-value" })
|
||||
public allowCustomValue;
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public icon?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property({ type: String, attribute: "search-label" })
|
||||
public searchLabel?: string;
|
||||
|
||||
@property({ attribute: "hide-clear-icon", type: Boolean })
|
||||
public hideClearIcon = false;
|
||||
|
||||
@property({ attribute: "show-label", type: Boolean })
|
||||
public showLabel = false;
|
||||
|
||||
/** To prevent lags, getItems needs to be memoized */
|
||||
@property({ attribute: false })
|
||||
public getItems!: (
|
||||
@@ -47,6 +66,9 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
@property({ attribute: false })
|
||||
public rowRenderer?: RenderItemFunction<PickerComboBoxItem>;
|
||||
|
||||
@property({ attribute: false })
|
||||
public valueRenderer?: PickerValueRenderer;
|
||||
|
||||
@property({ attribute: false })
|
||||
public searchFn?: PickerComboBoxSearchFn<PickerComboBoxItem>;
|
||||
|
||||
@@ -96,11 +118,7 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
|
||||
@property({ attribute: "selected-section" }) public selectedSection?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "use-top-label" })
|
||||
public useTopLabel = false;
|
||||
|
||||
@property({ attribute: "custom-value-label" })
|
||||
public customValueLabel?: string;
|
||||
@property({ attribute: "unknown-item-text" }) public unknownItemText?: string;
|
||||
|
||||
@query(".container") private _containerElement?: HTMLDivElement;
|
||||
|
||||
@@ -131,13 +149,11 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
private _unsubscribeTinyKeys?: () => void;
|
||||
|
||||
protected render() {
|
||||
// Only show label if it's not a top label and there is a value.
|
||||
const label = this.useTopLabel && this.value ? undefined : this.label;
|
||||
|
||||
return html`<div class="container">
|
||||
${this.useTopLabel && this.label
|
||||
? html`<label ?disabled=${this.disabled}>${this.label}</label>`
|
||||
: nothing}
|
||||
return html`
|
||||
${this.label
|
||||
? html`<label ?disabled=${this.disabled}>${this.label}</label>`
|
||||
: nothing}
|
||||
<div class="container">
|
||||
<div id="picker">
|
||||
<slot name="field">
|
||||
${this.addButtonLabel && !this.value
|
||||
@@ -157,20 +173,14 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
type="button"
|
||||
class=${this._opened ? "opened" : ""}
|
||||
compact
|
||||
.unknown=${this._unknownValue(
|
||||
this.allowCustomValue,
|
||||
this.value,
|
||||
this.getItems()
|
||||
)}
|
||||
.unknown=${this._unknownValue(this.value, this.getItems())}
|
||||
.unknownItemText=${this.unknownItemText}
|
||||
aria-label=${ifDefined(this.label)}
|
||||
@click=${this.open}
|
||||
@clear=${this._clear}
|
||||
.icon=${this.icon}
|
||||
.image=${this.image}
|
||||
.label=${label}
|
||||
.showLabel=${this.showLabel}
|
||||
.placeholder=${this.placeholder}
|
||||
.helper=${this.helper}
|
||||
.value=${this.value}
|
||||
.valueRenderer=${this.valueRenderer}
|
||||
.required=${this.required}
|
||||
@@ -178,7 +188,6 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
.invalid=${this.invalid}
|
||||
.hideClearIcon=${this.hideClearIcon}
|
||||
>
|
||||
<slot name="start"></slot>
|
||||
</ha-picker-field>`}
|
||||
</slot>
|
||||
</div>
|
||||
@@ -217,7 +226,8 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
</ha-bottom-sheet>`
|
||||
: nothing}
|
||||
</div>
|
||||
${this._renderHelper()}`;
|
||||
${this._renderHelper()}
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderComboBox(dialogMode = false) {
|
||||
@@ -226,7 +236,6 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
}
|
||||
return html`
|
||||
<ha-picker-combo-box
|
||||
id="combo-box"
|
||||
.hass=${this.hass}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
.label=${this.searchLabel}
|
||||
@@ -243,24 +252,13 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
.sectionTitleFunction=${this.sectionTitleFunction}
|
||||
.selectedSection=${this.selectedSection}
|
||||
.searchKeys=${this.searchKeys}
|
||||
.customValueLabel=${this.customValueLabel}
|
||||
></ha-picker-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
private _unknownValue = memoizeOne(
|
||||
(
|
||||
allowCustomValue: boolean,
|
||||
value?: string,
|
||||
items?: (PickerComboBoxItem | string)[]
|
||||
) => {
|
||||
if (
|
||||
allowCustomValue ||
|
||||
value === undefined ||
|
||||
value === null ||
|
||||
value === "" ||
|
||||
!items
|
||||
) {
|
||||
(value?: string, items?: (PickerComboBoxItem | string)[]) => {
|
||||
if (value === undefined || value === null || value === "" || !items) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -286,15 +284,6 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
private _dialogOpened = () => {
|
||||
this._opened = true;
|
||||
requestAnimationFrame(() => {
|
||||
if (this.hass && isIosApp(this.hass)) {
|
||||
this.hass.auth.external!.fireMessage({
|
||||
type: "focus_element",
|
||||
payload: {
|
||||
element_id: "combo-box",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
this._comboBox?.focus();
|
||||
});
|
||||
};
|
||||
@@ -321,7 +310,7 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
this._newValue = value;
|
||||
}
|
||||
|
||||
private _clear(e: CustomEvent) {
|
||||
private _clear(e) {
|
||||
e.stopPropagation();
|
||||
this._setValue(undefined);
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ export class HaIconPicker extends LitElement {
|
||||
<ha-generic-picker
|
||||
.hass=${this.hass}
|
||||
allow-custom-value
|
||||
show-label
|
||||
.getItems=${this._getIconPickerItems}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
@@ -121,7 +122,7 @@ export class HaIconPicker extends LitElement {
|
||||
.invalid=${this.invalid}
|
||||
.rowRenderer=${rowRenderer}
|
||||
.icon=${this._icon}
|
||||
.label=${this.label}
|
||||
.placeholder=${this.label}
|
||||
.value=${this._value}
|
||||
.searchFn=${this._filterIcons}
|
||||
.notFoundLabel=${this.hass?.localize(
|
||||
@@ -130,7 +131,6 @@ export class HaIconPicker extends LitElement {
|
||||
popover-placement="bottom-start"
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
<slot name="start"></slot>
|
||||
</ha-generic-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -231,6 +231,7 @@ export class HaLabelsPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
static styles = css`
|
||||
ha-chip-set {
|
||||
margin-bottom: 8px;
|
||||
background-color: var(--mdc-text-field-fill-color);
|
||||
border-bottom: 1px solid var(--ha-color-border-neutral-normal);
|
||||
border-top-right-radius: var(--ha-border-radius-sm);
|
||||
|
||||
@@ -116,11 +116,6 @@ export class HaLanguagePicker extends LitElement {
|
||||
> `;
|
||||
|
||||
protected render() {
|
||||
const label =
|
||||
this.label ??
|
||||
(this.hass?.localize("ui.components.language-picker.language") ||
|
||||
"Language");
|
||||
|
||||
const value =
|
||||
this.value ??
|
||||
(this.required && !this.disabled ? this._getItems()[0].id : this.value);
|
||||
@@ -134,7 +129,10 @@ export class HaLanguagePicker extends LitElement {
|
||||
.emptyLabel=${this.hass?.localize(
|
||||
"ui.components.language-picker.no_languages"
|
||||
) || "No languages available"}
|
||||
.label=${label}
|
||||
.placeholder=${this.label ??
|
||||
(this.hass?.localize("ui.components.language-picker.language") ||
|
||||
"Language")}
|
||||
show-label
|
||||
.value=${value}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.disabled=${this.disabled}
|
||||
|
||||
@@ -1,17 +1,55 @@
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { titleCase } from "../common/string/title-case";
|
||||
import { fetchConfig } from "../data/lovelace/config/types";
|
||||
import type { LovelaceViewRawConfig } from "../data/lovelace/config/view";
|
||||
import { getPanelIcon, getPanelTitle } from "../data/panel";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import "./ha-generic-picker";
|
||||
import type { HomeAssistant, PanelInfo, ValueChangedEvent } from "../types";
|
||||
import "./ha-combo-box";
|
||||
import type { HaComboBox } from "./ha-combo-box";
|
||||
import "./ha-combo-box-item";
|
||||
import "./ha-icon";
|
||||
import type { PickerComboBoxItem } from "./ha-picker-combo-box";
|
||||
|
||||
interface NavigationItem {
|
||||
path: string;
|
||||
icon: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const DEFAULT_ITEMS: NavigationItem[] = [];
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<NavigationItem> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
<ha-icon .icon=${item.icon} slot="start"></ha-icon>
|
||||
<span slot="headline">${item.title || item.path}</span>
|
||||
${item.title
|
||||
? html`<span slot="supporting-text">${item.path}</span>`
|
||||
: nothing}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
|
||||
const createViewNavigationItem = (
|
||||
prefix: string,
|
||||
view: LovelaceViewRawConfig,
|
||||
index: number
|
||||
) => ({
|
||||
path: `/${prefix}/${view.path ?? index}`,
|
||||
icon: view.icon ?? "mdi:view-compact",
|
||||
title: view.title ?? (view.path ? titleCase(view.path) : `${index}`),
|
||||
});
|
||||
|
||||
const createPanelNavigationItem = (hass: HomeAssistant, panel: PanelInfo) => ({
|
||||
path: `/${panel.url_path}`,
|
||||
icon: getPanelIcon(panel) || "mdi:view-dashboard",
|
||||
title: getPanelTitle(hass, panel) || "",
|
||||
});
|
||||
|
||||
@customElement("ha-navigation-picker")
|
||||
export class HaNavigationPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@@ -23,51 +61,46 @@ export class HaNavigationPicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@state() private _loading = true;
|
||||
@state() private _opened = false;
|
||||
|
||||
protected firstUpdated() {
|
||||
this._loadNavigationItems();
|
||||
}
|
||||
private navigationItemsLoaded = false;
|
||||
|
||||
private _navigationItems: PickerComboBoxItem[] = [];
|
||||
private navigationItems: NavigationItem[] = DEFAULT_ITEMS;
|
||||
|
||||
protected render() {
|
||||
@query("ha-combo-box", true) private comboBox!: HaComboBox;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-generic-picker
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.value=${this._loading ? undefined : this.value}
|
||||
item-value-path="path"
|
||||
item-label-path="path"
|
||||
.value=${this._value}
|
||||
allow-custom-value
|
||||
.placeholder=${this.label}
|
||||
.filteredItems=${this.navigationItems}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this._loading || this.disabled}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.getItems=${this._getItems}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.customValueLabel=${this.hass.localize(
|
||||
"ui.components.navigation-picker.add_custom_path"
|
||||
)}
|
||||
.renderer=${rowRenderer}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
</ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueRenderer = (itemId: string) => {
|
||||
const item = this._navigationItems.find((navItem) => navItem.id === itemId);
|
||||
return html`
|
||||
${item?.icon
|
||||
? html`<ha-icon slot="start" .icon=${item.icon}></ha-icon>`
|
||||
: nothing}
|
||||
<span slot="headline">${item?.primary || itemId}</span>
|
||||
${item?.primary
|
||||
? html`<span slot="supporting-text">${itemId}</span>`
|
||||
: nothing}
|
||||
`;
|
||||
};
|
||||
|
||||
private _getItems = () => this._navigationItems;
|
||||
private async _openedChanged(ev: ValueChangedEvent<boolean>) {
|
||||
this._opened = ev.detail.value;
|
||||
if (this._opened && !this.navigationItemsLoaded) {
|
||||
this._loadNavigationItems();
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadNavigationItems() {
|
||||
this.navigationItemsLoaded = true;
|
||||
|
||||
const panels = Object.entries(this.hass!.panels).map(([id, panel]) => ({
|
||||
id,
|
||||
...panel,
|
||||
@@ -91,47 +124,27 @@ export class HaNavigationPicker extends LitElement {
|
||||
|
||||
const panelViewConfig = new Map(viewConfigs);
|
||||
|
||||
this._navigationItems = [];
|
||||
this.navigationItems = [];
|
||||
|
||||
for (const panel of panels) {
|
||||
const path = `/${panel.url_path}`;
|
||||
const panelTitle = getPanelTitle(this.hass, panel);
|
||||
const primary = panelTitle || path;
|
||||
this._navigationItems.push({
|
||||
id: path,
|
||||
primary,
|
||||
secondary: panelTitle ? path : undefined,
|
||||
icon: getPanelIcon(panel) || "mdi:view-dashboard",
|
||||
sorting_label: [
|
||||
primary.startsWith("/") ? `zzz${primary}` : primary,
|
||||
path,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("_"),
|
||||
});
|
||||
this.navigationItems.push(createPanelNavigationItem(this.hass!, panel));
|
||||
|
||||
const config = panelViewConfig.get(panel.id);
|
||||
|
||||
if (!config || !("views" in config)) continue;
|
||||
|
||||
config.views.forEach((view, index) => {
|
||||
const viewPath = `/${panel.url_path}/${view.path ?? index}`;
|
||||
const viewPrimary =
|
||||
view.title ?? (view.path ? titleCase(view.path) : `${index}`);
|
||||
this._navigationItems.push({
|
||||
id: viewPath,
|
||||
secondary: viewPath,
|
||||
icon: view.icon ?? "mdi:view-compact",
|
||||
primary: viewPrimary,
|
||||
sorting_label: [
|
||||
viewPrimary.startsWith("/") ? `zzz${viewPrimary}` : viewPrimary,
|
||||
viewPath,
|
||||
].join("_"),
|
||||
});
|
||||
});
|
||||
config.views.forEach((view, index) =>
|
||||
this.navigationItems.push(
|
||||
createViewNavigationItem(panel.url_path, view, index)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this._loading = false;
|
||||
this.comboBox.filteredItems = this.navigationItems;
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues) {
|
||||
return !this._opened || changedProps.has("_opened");
|
||||
}
|
||||
|
||||
private _valueChanged(ev: ValueChangedEvent<string>) {
|
||||
@@ -139,18 +152,61 @@ export class HaNavigationPicker extends LitElement {
|
||||
this._setValue(ev.detail.value);
|
||||
}
|
||||
|
||||
private _setValue(value = "") {
|
||||
private _setValue(value: string) {
|
||||
this.value = value;
|
||||
fireEvent(
|
||||
this,
|
||||
"value-changed",
|
||||
{ value: this.value },
|
||||
{ value: this._value },
|
||||
{
|
||||
bubbles: false,
|
||||
composed: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _filterChanged(ev: CustomEvent): void {
|
||||
const filterString = ev.detail.value.toLowerCase();
|
||||
const characterCount = filterString.length;
|
||||
if (characterCount >= 2) {
|
||||
const filteredItems: NavigationItem[] = [];
|
||||
|
||||
this.navigationItems.forEach((item) => {
|
||||
if (
|
||||
item.path.toLowerCase().includes(filterString) ||
|
||||
item.title.toLowerCase().includes(filterString)
|
||||
) {
|
||||
filteredItems.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
if (filteredItems.length > 0) {
|
||||
this.comboBox.filteredItems = filteredItems;
|
||||
} else {
|
||||
this.comboBox.filteredItems = [];
|
||||
}
|
||||
} else {
|
||||
this.comboBox.filteredItems = this.navigationItems;
|
||||
}
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return this.value || "";
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-icon,
|
||||
ha-svg-icon {
|
||||
color: var(--primary-text-color);
|
||||
position: relative;
|
||||
bottom: 0px;
|
||||
}
|
||||
*[slot="prefix"] {
|
||||
margin-right: 8px;
|
||||
margin-inline-end: 8px;
|
||||
margin-inline-start: initial;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { LitVirtualizer } from "@lit-labs/virtualizer";
|
||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||
import { mdiMagnify, mdiMinusBoxOutline, mdiPlus } from "@mdi/js";
|
||||
import { mdiMagnify, mdiMinusBoxOutline } from "@mdi/js";
|
||||
import Fuse from "fuse.js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import {
|
||||
@@ -53,8 +53,7 @@ export interface PickerComboBoxItem {
|
||||
icon_path?: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export const NO_ITEMS_AVAILABLE_ID = "___no_items_available___";
|
||||
const NO_ITEMS_AVAILABLE_ID = "___no_items_available___";
|
||||
|
||||
const DEFAULT_ROW_RENDERER: RenderItemFunction<PickerComboBoxItem> = (
|
||||
item
|
||||
@@ -92,9 +91,6 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
@property({ type: Boolean, attribute: "allow-custom-value" })
|
||||
public allowCustomValue;
|
||||
|
||||
@property({ attribute: "custom-value-label" })
|
||||
public customValueLabel?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public value?: string;
|
||||
@@ -191,15 +187,10 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const searchLabel =
|
||||
this.label ??
|
||||
(this.allowCustomValue
|
||||
? (this.hass?.localize("ui.components.combo-box.search_or_custom") ??
|
||||
"Search | Add custom value")
|
||||
: (this.hass?.localize("ui.common.search") ?? "Search"));
|
||||
|
||||
return html`<ha-textfield
|
||||
.label=${searchLabel}
|
||||
.label=${this.label ??
|
||||
this.hass?.localize("ui.common.search") ??
|
||||
"Search"}
|
||||
@input=${this._filterChanged}
|
||||
></ha-textfield>
|
||||
${this._renderSectionButtons()}
|
||||
@@ -447,23 +438,13 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
);
|
||||
}
|
||||
|
||||
if (this.allowCustomValue && searchString) {
|
||||
filteredItems.push({
|
||||
id: searchString,
|
||||
primary:
|
||||
this.customValueLabel ??
|
||||
this.hass?.localize("ui.components.combo-box.add_custom_item") ??
|
||||
"Add custom item",
|
||||
secondary: `"${searchString}"`,
|
||||
icon_path: mdiPlus,
|
||||
});
|
||||
}
|
||||
|
||||
this._items = filteredItems as PickerComboBoxItem[];
|
||||
}
|
||||
|
||||
this._selectedItemIndex = -1;
|
||||
this._valuePinned = true;
|
||||
if (this._virtualizerElement) {
|
||||
this._virtualizerElement.scrollTo(0, 0);
|
||||
}
|
||||
};
|
||||
|
||||
private _toggleSection(ev: Event) {
|
||||
@@ -658,7 +639,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
typeof item === "string" ? item : item?.id;
|
||||
|
||||
private _getInitialSelectedIndex() {
|
||||
if (!this._virtualizerElement || this._search || !this.value) {
|
||||
if (!this._virtualizerElement || !this.value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,15 +9,13 @@ import {
|
||||
type TemplateResult,
|
||||
} 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 { localizeContext } from "../data/context";
|
||||
import { PickerMixin } from "../mixins/picker-mixin";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-combo-box-item";
|
||||
import type { HaComboBoxItem } from "./ha-combo-box-item";
|
||||
import "./ha-icon";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-icon";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@@ -28,7 +26,32 @@ declare global {
|
||||
export type PickerValueRenderer = (value: string) => TemplateResult<1>;
|
||||
|
||||
@customElement("ha-picker-field")
|
||||
export class HaPickerField extends PickerMixin(LitElement) {
|
||||
export class HaPickerField extends LitElement {
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public icon?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public unknown = false;
|
||||
|
||||
@property({ attribute: "unknown-item-text" }) public unknownItemText?: string;
|
||||
|
||||
@property({ attribute: "hide-clear-icon", type: Boolean })
|
||||
public hideClearIcon = false;
|
||||
|
||||
@property({ attribute: "show-label", type: Boolean })
|
||||
public showLabel = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public valueRenderer?: PickerValueRenderer;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public invalid = false;
|
||||
|
||||
@query("ha-combo-box-item", true) public item!: HaComboBoxItem;
|
||||
@@ -43,48 +66,31 @@ export class HaPickerField extends PickerMixin(LitElement) {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const hasValue = !!this.value;
|
||||
const hasValue = !!this.value?.length;
|
||||
|
||||
const showClearIcon =
|
||||
!!this.value && !this.required && !this.disabled && !this.hideClearIcon;
|
||||
|
||||
const placeholderText = this.placeholder ?? this.label;
|
||||
|
||||
const overlineLabel =
|
||||
this.label && hasValue
|
||||
? html`<span slot="overline"
|
||||
>${this.label}${this.required ? " *" : ""}</span
|
||||
>`
|
||||
this.showLabel && hasValue && this.placeholder
|
||||
? html`<span slot="overline">${this.placeholder}</span>`
|
||||
: nothing;
|
||||
|
||||
const headlineContent = hasValue
|
||||
? this.valueRenderer
|
||||
? this.valueRenderer(this.value ?? "")
|
||||
: html`<span slot="headline">${this.value}</span>`
|
||||
: placeholderText
|
||||
: this.placeholder
|
||||
? html`<span slot="headline" class="placeholder">
|
||||
${placeholderText}${this.required ? " *" : ""}
|
||||
${this.placeholder}
|
||||
</span>`
|
||||
: nothing;
|
||||
|
||||
return html`
|
||||
<ha-combo-box-item
|
||||
aria-label=${ifDefined(this.label || this.placeholder)}
|
||||
.disabled=${this.disabled}
|
||||
type="button"
|
||||
compact
|
||||
>
|
||||
${this.image
|
||||
? html`<img
|
||||
alt=${this.label ?? ""}
|
||||
slot="start"
|
||||
.src=${this.image}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
/>`
|
||||
: this.icon
|
||||
? html`<ha-icon slot="start" .icon=${this.icon}></ha-icon>`
|
||||
: html`<slot name="start"></slot>`}
|
||||
<ha-combo-box-item .disabled=${this.disabled} type="button" compact>
|
||||
${this.icon
|
||||
? html`<ha-icon slot="start" .icon=${this.icon}></ha-icon>`
|
||||
: nothing}
|
||||
${overlineLabel}${headlineContent}
|
||||
${this.unknown
|
||||
? html`<div slot="supporting-text" class="unknown">
|
||||
@@ -111,7 +117,7 @@ export class HaPickerField extends PickerMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _clear(e: CustomEvent) {
|
||||
private _clear(e) {
|
||||
e.stopPropagation();
|
||||
fireEvent(this, "clear");
|
||||
}
|
||||
@@ -194,10 +200,7 @@ export class HaPickerField extends PickerMixin(LitElement) {
|
||||
|
||||
.placeholder {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
:host([invalid]) .placeholder {
|
||||
color: var(--mdc-theme-error, var(--error-color, #b00020));
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.unknown {
|
||||
|
||||
@@ -28,6 +28,7 @@ export class HaAddonSelector extends LitElement {
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
allow-custom-entity
|
||||
></ha-addon-picker>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ export class HaConfigEntrySelector extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.integration=${this.selector.config_entry?.integration}
|
||||
allow-custom-entity
|
||||
></ha-config-entry-picker>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ export class HaDeviceSelector extends LitElement {
|
||||
.placeholder=${this.placeholder}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
allow-custom-entity
|
||||
></ha-device-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -66,14 +66,15 @@ export class HaEntitySelector extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.placeholder=${this.placeholder}
|
||||
.helper=${this.helper}
|
||||
.includeEntities=${this.selector.entity?.include_entities}
|
||||
.excludeEntities=${this.selector.entity?.exclude_entities}
|
||||
.entityFilter=${this._filterEntities}
|
||||
.createDomains=${this._createDomains}
|
||||
.placeholder=${this.placeholder}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>`;
|
||||
}
|
||||
|
||||
@@ -82,13 +83,13 @@ export class HaEntitySelector extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.placeholder=${this.placeholder}
|
||||
.helper=${this.helper}
|
||||
.includeEntities=${this.selector.entity.include_entities}
|
||||
.excludeEntities=${this.selector.entity.exclude_entities}
|
||||
.reorder=${this.selector.entity.reorder ?? false}
|
||||
.entityFilter=${this._filterEntities}
|
||||
.createDomains=${this._createDomains}
|
||||
.placeholder=${this.placeholder}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
></ha-entities-picker>
|
||||
|
||||
@@ -52,7 +52,7 @@ export class HaIconSelector extends LitElement {
|
||||
${!placeholder && stateObj
|
||||
? html`
|
||||
<ha-state-icon
|
||||
slot="start"
|
||||
slot="fallback"
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
></ha-state-icon>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { mdiDragHorizontalVariant } from "@mdi/js";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
@@ -12,8 +11,9 @@ import type { HomeAssistant } from "../../types";
|
||||
import "../chips/ha-chip-set";
|
||||
import "../chips/ha-input-chip";
|
||||
import "../ha-checkbox";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-formfield";
|
||||
import "../ha-generic-picker";
|
||||
import "../ha-input-helper-text";
|
||||
import "../ha-list-item";
|
||||
import "../ha-radio";
|
||||
@@ -40,6 +40,8 @@ export class HaSelectSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
@query("ha-combo-box", true) private comboBox!: HaComboBox;
|
||||
|
||||
private _itemMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
@@ -57,8 +59,15 @@ export class HaSelectSelector extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _filter = "";
|
||||
|
||||
protected render() {
|
||||
const options = this._getOptions(this.selector);
|
||||
const options =
|
||||
this.selector.select?.options?.map((option) =>
|
||||
typeof option === "object"
|
||||
? (option as SelectOption)
|
||||
: ({ value: option, label: option } as SelectOption)
|
||||
) || [];
|
||||
|
||||
const translationKey = this.selector.select?.translation_key;
|
||||
|
||||
@@ -156,6 +165,10 @@ export class HaSelectSelector extends LitElement {
|
||||
const value =
|
||||
!this.value || this.value === "" ? [] : ensureArray(this.value);
|
||||
|
||||
const optionItems = options.filter(
|
||||
(option) => !option.disabled && !value?.includes(option.value)
|
||||
);
|
||||
|
||||
return html`
|
||||
${value?.length
|
||||
? html`
|
||||
@@ -199,33 +212,50 @@ export class HaSelectSelector extends LitElement {
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-generic-picker
|
||||
<ha-combo-box
|
||||
item-value-path="value"
|
||||
item-label-path="label"
|
||||
.hass=${this.hass}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required && !value.length}
|
||||
.value=${""}
|
||||
.addButtonLabel=${this.label}
|
||||
.getItems=${this._getItems(options, value, true)}
|
||||
.items=${optionItems}
|
||||
.allowCustomValue=${this.selector.select.custom_value ?? false}
|
||||
@filter-changed=${this._filterChanged}
|
||||
@value-changed=${this._comboBoxValueChanged}
|
||||
></ha-generic-picker>
|
||||
@opened-changed=${this._openedChanged}
|
||||
></ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
if (this.selector.select?.custom_value) {
|
||||
if (
|
||||
this.value !== undefined &&
|
||||
!Array.isArray(this.value) &&
|
||||
!options.find((option) => option.value === this.value)
|
||||
) {
|
||||
options.unshift({ value: this.value, label: this.value });
|
||||
}
|
||||
|
||||
const optionItems = options.filter((option) => !option.disabled);
|
||||
|
||||
return html`
|
||||
<ha-generic-picker
|
||||
<ha-combo-box
|
||||
item-value-path="value"
|
||||
item-label-path="label"
|
||||
.hass=${this.hass}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.getItems=${this._getItems(options)}
|
||||
.value=${this.value as string | undefined}
|
||||
.items=${optionItems}
|
||||
.value=${this.value}
|
||||
@filter-changed=${this._filterChanged}
|
||||
@value-changed=${this._comboBoxValueChanged}
|
||||
allow-custom-value
|
||||
></ha-generic-picker>
|
||||
@opened-changed=${this._openedChanged}
|
||||
></ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -244,7 +274,7 @@ export class HaSelectSelector extends LitElement {
|
||||
>
|
||||
${options.map(
|
||||
(item: SelectOption) => html`
|
||||
<ha-list-item .value=${item.value} .disabled=${!!item.disabled}
|
||||
<ha-list-item .value=${item.value} .disabled=${item.disabled}
|
||||
>${item.label}</ha-list-item
|
||||
>
|
||||
`
|
||||
@@ -261,30 +291,6 @@ export class HaSelectSelector extends LitElement {
|
||||
: "";
|
||||
}
|
||||
|
||||
private _getOptions = memoizeOne(
|
||||
(selector: SelectSelector) =>
|
||||
selector.select?.options?.map((option) =>
|
||||
typeof option === "object"
|
||||
? (option as SelectOption)
|
||||
: ({ value: option, label: option } as SelectOption)
|
||||
) || []
|
||||
);
|
||||
|
||||
private _getItems = memoizeOne(
|
||||
(options: SelectOption[], value?: string[], multiple = false) => {
|
||||
const filteredOptions = options.filter((option) =>
|
||||
!option.disabled && !multiple ? true : !value?.includes(option.value)
|
||||
);
|
||||
|
||||
return () =>
|
||||
filteredOptions.map((option) => ({
|
||||
id: option.value,
|
||||
primary: option.label,
|
||||
sorting_label: option.label,
|
||||
}));
|
||||
}
|
||||
);
|
||||
|
||||
private get _mode(): "list" | "dropdown" | "box" {
|
||||
return (
|
||||
this.selector.select?.mode ||
|
||||
@@ -349,6 +355,8 @@ export class HaSelectSelector extends LitElement {
|
||||
fireEvent(this, "value-changed", {
|
||||
value,
|
||||
});
|
||||
await this.updateComplete;
|
||||
this._filterChanged();
|
||||
}
|
||||
|
||||
private _comboBoxValueChanged(ev: CustomEvent): void {
|
||||
@@ -366,17 +374,49 @@ export class HaSelectSelector extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentValue = !this.value ? [] : ensureArray(this.value);
|
||||
const currentValue =
|
||||
!this.value || this.value === "" ? [] : ensureArray(this.value);
|
||||
|
||||
if (newValue !== undefined && currentValue.includes(newValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this._filterChanged();
|
||||
this.comboBox.setInputValue("");
|
||||
}, 0);
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: [...currentValue, newValue],
|
||||
});
|
||||
}
|
||||
|
||||
private _openedChanged(ev?: CustomEvent): void {
|
||||
if (ev?.detail.value) {
|
||||
this._filterChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private _filterChanged(ev?: CustomEvent): void {
|
||||
this._filter = ev?.detail.value || "";
|
||||
|
||||
const filteredItems = this.comboBox.items?.filter((item) => {
|
||||
const label = item.label || item.value;
|
||||
return label.toLowerCase().includes(this._filter?.toLowerCase());
|
||||
});
|
||||
|
||||
if (
|
||||
this._filter &&
|
||||
this.selector.select?.custom_value &&
|
||||
filteredItems &&
|
||||
!filteredItems.some((item) => (item.label || item.value) === this._filter)
|
||||
) {
|
||||
filteredItems.unshift({ label: this._filter, value: this._filter });
|
||||
}
|
||||
|
||||
this.comboBox.filteredItems = filteredItems;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
position: relative;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||
import { mdiRoomService } from "@mdi/js";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { html, LitElement, nothing, type TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -56,9 +56,9 @@ class HaServicePicker extends LitElement {
|
||||
getServiceIcons(this.hass);
|
||||
}
|
||||
|
||||
private _rowRenderer: RenderItemFunction<ServiceComboBoxItem> = (
|
||||
private _rowRenderer: ComboBoxLitRenderer<ServiceComboBoxItem> = (
|
||||
item,
|
||||
index
|
||||
{ index }
|
||||
) => html`
|
||||
<ha-combo-box-item type="button" .borderTop=${index !== 0}>
|
||||
<ha-service-icon
|
||||
@@ -135,6 +135,7 @@ class HaServicePicker extends LitElement {
|
||||
<ha-generic-picker
|
||||
.hass=${this.hass}
|
||||
.autofocus=${this.autofocus}
|
||||
allow-custom-value
|
||||
.notFoundLabel=${this.hass.localize(
|
||||
"ui.components.service-picker.no_match"
|
||||
)}
|
||||
|
||||
@@ -235,12 +235,6 @@ export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
|
||||
);
|
||||
max-width: var(--ha-dialog-max-width, var(--safe-width));
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
wa-dialog {
|
||||
--show-duration: 0ms;
|
||||
--hide-duration: 0ms;
|
||||
}
|
||||
}
|
||||
|
||||
:host([width="small"]) wa-dialog {
|
||||
--width: min(var(--ha-dialog-width-sm, 320px), var(--full-width));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
@@ -73,7 +73,7 @@ class HaUserPicker extends LitElement {
|
||||
`;
|
||||
};
|
||||
|
||||
private _rowRenderer: RenderItemFunction<UserComboBoxItem> = (item) => {
|
||||
private _rowRenderer: ComboBoxLitRenderer<UserComboBoxItem> = (item) => {
|
||||
const user = item.user;
|
||||
if (!user) {
|
||||
return html`<ha-combo-box-item type="button" compact>
|
||||
@@ -132,9 +132,9 @@ class HaUserPicker extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.autofocus=${this.autofocus}
|
||||
.label=${this.label}
|
||||
.notFoundLabel=${this._notFoundLabel}
|
||||
.placeholder=${placeholder}
|
||||
.value=${this.value}
|
||||
.notFoundLabel=${this._notFoundLabel}
|
||||
.getItems=${this._getItems}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.rowRenderer=${this._rowRenderer}
|
||||
|
||||
@@ -88,6 +88,7 @@ export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [
|
||||
"select",
|
||||
"text",
|
||||
"update",
|
||||
"event",
|
||||
];
|
||||
|
||||
/** Domains that should have the history hidden in the more info dialog. */
|
||||
|
||||
@@ -137,7 +137,7 @@ export class CloudStepSignin extends LitElement {
|
||||
),
|
||||
});
|
||||
if (totpCode !== null && totpCode !== "") {
|
||||
await doLogin(username, totpCode.trim());
|
||||
await doLogin(username, totpCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,13 +176,6 @@ interface EMOutgoingMessageAddEntityTo extends EMMessage {
|
||||
};
|
||||
}
|
||||
|
||||
interface EMOutgoingMessageFocusElement extends EMMessage {
|
||||
type: "focus_element";
|
||||
payload: {
|
||||
element_id: string;
|
||||
};
|
||||
}
|
||||
|
||||
type EMOutgoingMessageWithoutAnswer =
|
||||
| EMMessageResultError
|
||||
| EMMessageResultSuccess
|
||||
@@ -204,8 +197,7 @@ type EMOutgoingMessageWithoutAnswer =
|
||||
| EMOutgoingMessageThreadStoreInPlatformKeychain
|
||||
| EMOutgoingMessageImprovScan
|
||||
| EMOutgoingMessageImprovConfigureDevice
|
||||
| EMOutgoingMessageAddEntityTo
|
||||
| EMOutgoingMessageFocusElement;
|
||||
| EMOutgoingMessageAddEntityTo;
|
||||
|
||||
export interface EMIncomingMessageRestart {
|
||||
id: number;
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import type { ReactiveElement } from "lit";
|
||||
import { property } from "lit/decorators";
|
||||
import type { Constructor } from "../types";
|
||||
import type { PickerValueRenderer } from "../components/ha-picker-field";
|
||||
|
||||
export const PickerMixin = <T extends Constructor<ReactiveElement>>(
|
||||
superClass: T
|
||||
) => {
|
||||
class PickerFieldClass extends superClass {
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property() public icon?: string;
|
||||
|
||||
@property() public image?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public unknown = false;
|
||||
|
||||
@property({ attribute: "unknown-item-text" })
|
||||
public unknownItemText?: string;
|
||||
|
||||
@property({ attribute: "hide-clear-icon", type: Boolean })
|
||||
public hideClearIcon = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public valueRenderer?: PickerValueRenderer;
|
||||
}
|
||||
return PickerFieldClass;
|
||||
};
|
||||
@@ -5,15 +5,13 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dialog-footer";
|
||||
import "../../../components/ha-combo-box";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-fade-in";
|
||||
import "../../../components/ha-generic-picker";
|
||||
import "../../../components/ha-markdown";
|
||||
import "../../../components/ha-password-field";
|
||||
import type { PickerComboBoxItem } from "../../../components/ha-picker-combo-box";
|
||||
import "../../../components/ha-spinner";
|
||||
import "../../../components/ha-textfield";
|
||||
import "../../../components/ha-wa-dialog";
|
||||
import type {
|
||||
ApplicationCredential,
|
||||
ApplicationCredentialsConfig,
|
||||
@@ -61,10 +59,6 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
|
||||
@state() private _config?: ApplicationCredentialsConfig;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@state() private _invalid = false;
|
||||
|
||||
public showDialog(params: AddApplicationCredentialDialogParams) {
|
||||
this._params = params;
|
||||
this._domain = params.selectedDomain;
|
||||
@@ -75,7 +69,6 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
this._clientSecret = "";
|
||||
this._error = undefined;
|
||||
this._loading = false;
|
||||
this._open = true;
|
||||
this._fetchConfig();
|
||||
}
|
||||
|
||||
@@ -97,16 +90,16 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
? domainToName(this.hass.localize, this._domain!)
|
||||
: "";
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this._abortDialog}
|
||||
.preventScrimClose=${!!this._domain ||
|
||||
!!this._name ||
|
||||
!!this._clientId ||
|
||||
!!this._clientSecret}
|
||||
.headerTitle=${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.caption"
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.caption"
|
||||
)
|
||||
)}
|
||||
>
|
||||
${!this._config
|
||||
@@ -172,23 +165,20 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
: nothing}
|
||||
${this._params.selectedDomain
|
||||
? nothing
|
||||
: html`<ha-generic-picker
|
||||
: html`<ha-combo-box
|
||||
name="domain"
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.domain"
|
||||
)}
|
||||
.value=${this._domain}
|
||||
.invalid=${this._invalid && !this._domain}
|
||||
.getItems=${this._getDomainItems}
|
||||
.items=${this._domains}
|
||||
item-id-path="id"
|
||||
item-value-path="id"
|
||||
item-label-path="name"
|
||||
required
|
||||
.disabled=${!this._domains}
|
||||
.valueRenderer=${this._domainRenderer}
|
||||
@value-changed=${this._handleDomainPicked}
|
||||
.errorMessage=${this.hass.localize(
|
||||
"ui.common.error_required"
|
||||
)}
|
||||
></ha-generic-picker>`}
|
||||
></ha-combo-box>`}
|
||||
${this._description
|
||||
? html`<ha-markdown
|
||||
breaks
|
||||
@@ -202,10 +192,9 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
"ui.panel.config.application_credentials.editor.name"
|
||||
)}
|
||||
.value=${this._name}
|
||||
.invalid=${this._invalid && !this._name}
|
||||
required
|
||||
@input=${this._handleValueChanged}
|
||||
.errorMessage=${this.hass.localize(
|
||||
.validationMessage=${this.hass.localize(
|
||||
"ui.common.error_required"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
@@ -217,10 +206,9 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
"ui.panel.config.application_credentials.editor.client_id"
|
||||
)}
|
||||
.value=${this._clientId}
|
||||
.invalid=${this._invalid && !this._clientId}
|
||||
required
|
||||
@input=${this._handleValueChanged}
|
||||
.errorMessage=${this.hass.localize(
|
||||
.validationMessage=${this.hass.localize(
|
||||
"ui.common.error_required"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
@@ -235,10 +223,9 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
)}
|
||||
name="clientSecret"
|
||||
.value=${this._clientSecret}
|
||||
.invalid=${this._invalid && !this._clientSecret}
|
||||
required
|
||||
@input=${this._handleValueChanged}
|
||||
.errorMessage=${this.hass.localize(
|
||||
.validationMessage=${this.hass.localize(
|
||||
"ui.common.error_required"
|
||||
)}
|
||||
.helper=${this.hass.localize(
|
||||
@@ -248,33 +235,30 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
></ha-password-field>
|
||||
</div>
|
||||
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="secondaryAction"
|
||||
@click=${this._closeDialog}
|
||||
.disabled=${this._loading}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._addApplicationCredential}
|
||||
.loading=${this._loading}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.add"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>`}
|
||||
</ha-wa-dialog>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="secondaryAction"
|
||||
@click=${this._abortDialog}
|
||||
.disabled=${this._loading}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
.disabled=${!this._domain ||
|
||||
!this._clientId ||
|
||||
!this._clientSecret}
|
||||
@click=${this._addApplicationCredential}
|
||||
.loading=${this._loading}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.add"
|
||||
)}
|
||||
</ha-button>`}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _closeDialog() {
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._params = undefined;
|
||||
this._domains = undefined;
|
||||
@@ -319,16 +303,9 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
|
||||
private async _addApplicationCredential(ev) {
|
||||
ev.preventDefault();
|
||||
if (
|
||||
!this._domain ||
|
||||
!this._name ||
|
||||
!this._clientId ||
|
||||
!this._clientSecret
|
||||
) {
|
||||
this._invalid = true;
|
||||
if (!this._domain || !this._clientId || !this._clientSecret) {
|
||||
return;
|
||||
}
|
||||
this._invalid = false;
|
||||
|
||||
this._loading = true;
|
||||
this._error = "";
|
||||
@@ -351,20 +328,6 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private _getDomainItems = (): PickerComboBoxItem[] =>
|
||||
this._domains?.map((domain) => ({
|
||||
id: domain.id,
|
||||
primary: domain.name,
|
||||
sorting_label: domain.name,
|
||||
})) ?? [];
|
||||
|
||||
private _domainRenderer = (domainId: string) => {
|
||||
const domain = this._domains?.find((d) => d.id === domainId);
|
||||
return html`<span slot="headline"
|
||||
>${domain ? domain.name : domainId}</span
|
||||
>`;
|
||||
};
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
@@ -375,12 +338,15 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
padding: var(--ha-space-2) 0;
|
||||
padding: 8px 0;
|
||||
}
|
||||
ha-combo-box {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin-top: var(--ha-space-4);
|
||||
margin-bottom: var(--ha-space-4);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
@@ -389,8 +355,7 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
--mdc-icon-size: 16px;
|
||||
}
|
||||
ha-markdown {
|
||||
margin-top: var(--ha-space-4);
|
||||
margin-bottom: var(--ha-space-4);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-fade-in {
|
||||
display: flex;
|
||||
|
||||
@@ -11,13 +11,12 @@ import "../../../components/ha-alert";
|
||||
import "../../../components/ha-aliases-editor";
|
||||
import "../../../components/ha-area-picker";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dialog-footer";
|
||||
import "../../../components/ha-floor-icon";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-icon-picker";
|
||||
import "../../../components/ha-picture-upload";
|
||||
import "../../../components/ha-settings-row";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-textfield";
|
||||
import "../../../components/ha-wa-dialog";
|
||||
import { updateAreaRegistryEntry } from "../../../data/area_registry";
|
||||
import type {
|
||||
FloorRegistryEntry,
|
||||
@@ -50,8 +49,6 @@ class DialogFloorDetail extends LitElement {
|
||||
|
||||
@state() private _removedAreas = new Set<string>();
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
public showDialog(params: FloorRegistryDetailDialogParams): void {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
@@ -63,14 +60,9 @@ class DialogFloorDetail extends LitElement {
|
||||
this._level = this._params.entry?.level ?? null;
|
||||
this._addedAreas.clear();
|
||||
this._removedAreas.clear();
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this._error = "";
|
||||
this._params = undefined;
|
||||
this._addedAreas.clear();
|
||||
@@ -104,15 +96,18 @@ class DialogFloorDetail extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
const entry = this._params.entry;
|
||||
const nameInvalid = !this._isNameValid();
|
||||
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${entry
|
||||
? this.hass.localize("ui.panel.config.floors.editor.update_floor")
|
||||
: this.hass.localize("ui.panel.config.floors.editor.create_floor")}
|
||||
@closed=${this._dialogClosed}
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
entry
|
||||
? this.hass.localize("ui.panel.config.floors.editor.update_floor")
|
||||
: this.hass.localize("ui.panel.config.floors.editor.create_floor")
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
${this._error
|
||||
@@ -133,7 +128,6 @@ class DialogFloorDetail extends LitElement {
|
||||
: nothing}
|
||||
|
||||
<ha-textfield
|
||||
autofocus
|
||||
.value=${this._name}
|
||||
@input=${this._nameChanged}
|
||||
.label=${this.hass.localize("ui.panel.config.floors.editor.name")}
|
||||
@@ -141,6 +135,7 @@ class DialogFloorDetail extends LitElement {
|
||||
"ui.panel.config.floors.editor.name_required"
|
||||
)}
|
||||
required
|
||||
dialogInitialFocus
|
||||
></ha-textfield>
|
||||
|
||||
<ha-textfield
|
||||
@@ -165,7 +160,7 @@ class DialogFloorDetail extends LitElement {
|
||||
${!this._icon
|
||||
? html`
|
||||
<ha-floor-icon
|
||||
slot="start"
|
||||
slot="fallback"
|
||||
.floor=${{ level: this._level }}
|
||||
></ha-floor-icon>
|
||||
`
|
||||
@@ -235,25 +230,23 @@ class DialogFloorDetail extends LitElement {
|
||||
></ha-aliases-editor>
|
||||
</div>
|
||||
</div>
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="secondaryAction"
|
||||
@click=${this.closeDialog}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateEntry}
|
||||
.disabled=${!!this._submitting}
|
||||
>
|
||||
${entry
|
||||
? this.hass.localize("ui.common.save")
|
||||
: this.hass.localize("ui.common.create")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-wa-dialog>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="secondaryAction"
|
||||
@click=${this.closeDialog}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateEntry}
|
||||
.disabled=${nameInvalid || !!this._submitting}
|
||||
>
|
||||
${entry
|
||||
? this.hass.localize("ui.common.save")
|
||||
: this.hass.localize("ui.common.create")}
|
||||
</ha-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -292,6 +285,10 @@ class DialogFloorDetail extends LitElement {
|
||||
this._addedAreas = new Set(this._addedAreas);
|
||||
}
|
||||
|
||||
private _isNameValid() {
|
||||
return this._name.trim() !== "";
|
||||
}
|
||||
|
||||
private _nameChanged(ev) {
|
||||
this._error = undefined;
|
||||
this._name = ev.target.value;
|
||||
@@ -308,16 +305,7 @@ class DialogFloorDetail extends LitElement {
|
||||
}
|
||||
|
||||
private async _updateEntry() {
|
||||
if (this._name.trim() === "") {
|
||||
this._error = this.hass.localize(
|
||||
"ui.panel.config.floors.editor.name_required"
|
||||
);
|
||||
return;
|
||||
}
|
||||
this._error = undefined;
|
||||
|
||||
this._submitting = true;
|
||||
|
||||
const create = !this._params!.entry;
|
||||
try {
|
||||
const values: FloorRegistryEntryMutableParams = {
|
||||
@@ -356,13 +344,13 @@ class DialogFloorDetail extends LitElement {
|
||||
css`
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin-bottom: var(--ha-space-4);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-floor-icon {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-chip-set {
|
||||
margin-bottom: var(--ha-space-2);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -148,7 +148,7 @@ class DialogAutomationSave extends LitElement implements HassDialog {
|
||||
@value-changed=${this._iconChanged}
|
||||
>
|
||||
<ha-domain-icon
|
||||
slot="start"
|
||||
slot="fallback"
|
||||
domain=${this._params.domain}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
@@ -176,10 +176,8 @@ class DialogAutomationSave extends LitElement implements HassDialog {
|
||||
id="category"
|
||||
.hass=${this.hass}
|
||||
.scope=${this._params.domain}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.category-picker.category"
|
||||
)}
|
||||
.value=${this._entryUpdates.category}
|
||||
show-label
|
||||
@value-changed=${this._registryEntryChanged}
|
||||
></ha-category-picker>`
|
||||
: nothing}
|
||||
@@ -196,6 +194,7 @@ class DialogAutomationSave extends LitElement implements HassDialog {
|
||||
id="area"
|
||||
.hass=${this.hass}
|
||||
.value=${this._entryUpdates.area}
|
||||
show-label
|
||||
@value-changed=${this._registryEntryChanged}
|
||||
></ha-area-picker>`
|
||||
: nothing}
|
||||
|
||||
@@ -40,6 +40,7 @@ export class HaZoneCondition extends LitElement {
|
||||
@value-changed=${this._entityPicked}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
allow-custom-entity
|
||||
.entityFilter=${zoneAndLocationFilter}
|
||||
></ha-entity-picker>
|
||||
<ha-entity-picker
|
||||
@@ -50,6 +51,7 @@ export class HaZoneCondition extends LitElement {
|
||||
@value-changed=${this._zonePicked}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
allow-custom-entity
|
||||
.includeDomains=${includeDomains}
|
||||
></ha-entity-picker>
|
||||
`;
|
||||
|
||||
@@ -43,6 +43,7 @@ export class HaZoneTrigger extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._entityPicked}
|
||||
.hass=${this.hass}
|
||||
allow-custom-entity
|
||||
.entityFilter=${zoneAndLocationFilter}
|
||||
></ha-entity-picker>
|
||||
<ha-entity-picker
|
||||
@@ -53,6 +54,7 @@ export class HaZoneTrigger extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._zonePicked}
|
||||
.hass=${this.hass}
|
||||
allow-custom-entity
|
||||
.includeDomains=${includeDomains}
|
||||
></ha-entity-picker>
|
||||
|
||||
|
||||
@@ -65,9 +65,6 @@ class DialogAssignCategory extends LitElement {
|
||||
<ha-category-picker
|
||||
.hass=${this.hass}
|
||||
.scope=${this._scope}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.category-picker.category"
|
||||
)}
|
||||
.value=${this._category}
|
||||
@value-changed=${this._categoryChanged}
|
||||
></ha-category-picker>
|
||||
|
||||
@@ -39,6 +39,9 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) {
|
||||
@property({ type: Boolean, attribute: "no-add" })
|
||||
public noAdd = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "show-label" })
|
||||
public showLabel = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
@@ -180,6 +183,10 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) {
|
||||
};
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const placeholder =
|
||||
this.placeholder ??
|
||||
this.hass.localize("ui.components.category-picker.category");
|
||||
|
||||
const valueRenderer = this._computeValueRenderer(this._categories);
|
||||
|
||||
return html`
|
||||
@@ -187,12 +194,13 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) {
|
||||
.hass=${this.hass}
|
||||
.autofocus=${this.autofocus}
|
||||
.label=${this.label}
|
||||
.placeholder=${this.placeholder}
|
||||
.value=${this.value}
|
||||
.notFoundLabel=${this._notFoundLabel}
|
||||
.emptyLabel=${this.hass.localize(
|
||||
"ui.components.category-picker.no_categories"
|
||||
)}
|
||||
.placeholder=${placeholder}
|
||||
.showLabel=${this.showLabel}
|
||||
.value=${this.value}
|
||||
.getItems=${this._getItems}
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
.valueRenderer=${valueRenderer}
|
||||
|
||||
@@ -155,7 +155,7 @@ export class CloudLogin extends LitElement {
|
||||
),
|
||||
});
|
||||
if (totpCode !== null && totpCode !== "") {
|
||||
this._login(email, password, checkConnection, totpCode.trim());
|
||||
this._login(email, password, checkConnection, totpCode);
|
||||
return "continue";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._areaId}
|
||||
show-label
|
||||
@value-changed=${this._areaPicked}
|
||||
></ha-area-picker>
|
||||
<ha-labels-picker
|
||||
|
||||
@@ -403,7 +403,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
${!this._icon && !stateObj?.attributes.icon && stateObj
|
||||
? html`
|
||||
<ha-state-icon
|
||||
slot="start"
|
||||
slot="fallback"
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
></ha-state-icon>
|
||||
@@ -778,6 +778,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.value=${this._areaId}
|
||||
.disabled=${this.disabled}
|
||||
show-label
|
||||
@value-changed=${this._areaPicked}
|
||||
></ha-area-picker>`
|
||||
: ""}
|
||||
@@ -1012,6 +1013,7 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
? html`<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._areaId}
|
||||
show-label
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._areaPicked}
|
||||
></ha-area-picker>`
|
||||
@@ -1543,12 +1545,6 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
margin-inline-end: 0;
|
||||
margin-inline-start: initial;
|
||||
}
|
||||
ha-settings-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: var(--ha-space-4);
|
||||
align-items: start;
|
||||
}
|
||||
ha-textfield,
|
||||
ha-icon-picker,
|
||||
ha-select,
|
||||
|
||||
@@ -16,14 +16,13 @@ import "../../../../../components/buttons/ha-progress-button";
|
||||
import type { HaProgressButton } from "../../../../../components/buttons/ha-progress-button";
|
||||
import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-generic-picker";
|
||||
import "../../../../../components/ha-list-item";
|
||||
import type { PickerComboBoxItem } from "../../../../../components/ha-picker-combo-box";
|
||||
import "../../../../../components/ha-select";
|
||||
import "../../../../../components/ha-selector/ha-selector-boolean";
|
||||
import "../../../../../components/ha-settings-row";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import "../../../../../components/ha-combo-box";
|
||||
import type {
|
||||
ZWaveJSNodeCapabilities,
|
||||
ZWaveJSNodeConfigParam,
|
||||
@@ -330,22 +329,19 @@ class ZWaveJSNodeConfig extends LitElement {
|
||||
) {
|
||||
return html`
|
||||
${labelAndDescription}
|
||||
<ha-generic-picker
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.value=${item.value?.toString()}
|
||||
allow-custom-value
|
||||
hide-clear-icon
|
||||
.getItems=${this._getManualEntryItems(item.metadata.states)}
|
||||
.items=${this._getComboBoxOptions(item.metadata.states)}
|
||||
.disabled=${!item.metadata.writeable}
|
||||
.invalid=${result?.status === "error"}
|
||||
.placeholder=${item.metadata.unit}
|
||||
.helper=${`${this.hass.localize("ui.panel.config.zwave_js.node_config.between_min_max", { min: item.metadata.min, max: item.metadata.max })}${defaultLabel ? `, ${defaultLabel}` : ""}`}
|
||||
.valueRenderer=${this._enumeratedPickerValueRenderer(
|
||||
item.metadata.states
|
||||
)}
|
||||
@value-changed=${this._getComboBoxValueChangedCallback(id, item)}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
</ha-combo-box>
|
||||
`;
|
||||
}
|
||||
return html`${labelAndDescription}
|
||||
@@ -367,10 +363,7 @@ class ZWaveJSNodeConfig extends LitElement {
|
||||
</ha-textfield>`;
|
||||
}
|
||||
|
||||
if (
|
||||
item.configuration_value_type === "enumerated" &&
|
||||
Object.keys(item.metadata.states).length < 5
|
||||
) {
|
||||
if (item.configuration_value_type === "enumerated") {
|
||||
return html`
|
||||
${labelAndDescription}
|
||||
<ha-select
|
||||
@@ -392,28 +385,6 @@ class ZWaveJSNodeConfig extends LitElement {
|
||||
</ha-select>
|
||||
`;
|
||||
}
|
||||
if (item.configuration_value_type === "enumerated") {
|
||||
return html`
|
||||
${labelAndDescription}
|
||||
<ha-generic-picker
|
||||
.hass=${this.hass}
|
||||
.disabled=${!item.metadata.writeable}
|
||||
.value=${item.value?.toString()}
|
||||
.key=${id}
|
||||
hide-clear-icon
|
||||
@value-changed=${this._pickerValueChanged}
|
||||
.helper=${defaultLabel}
|
||||
.getItems=${this._getEnumeratedPickerItems(item.metadata.states!)}
|
||||
.valueRenderer=${this._enumeratedPickerValueRenderer(
|
||||
item.metadata.states!
|
||||
)}
|
||||
.property=${item.property}
|
||||
.endpoint=${item.endpoint}
|
||||
.propertyKey=${item.property_key}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`${labelAndDescription}
|
||||
<p>${item.value}</p>`;
|
||||
@@ -458,23 +429,15 @@ class ZWaveJSNodeConfig extends LitElement {
|
||||
}
|
||||
|
||||
private _dropdownSelected(ev) {
|
||||
this._handleEnumeratedPickerValueChanged(ev, ev.target.value);
|
||||
}
|
||||
|
||||
private _pickerValueChanged(ev) {
|
||||
this._handleEnumeratedPickerValueChanged(ev, ev.detail.value);
|
||||
}
|
||||
|
||||
private _handleEnumeratedPickerValueChanged(ev, value: string) {
|
||||
if (ev.target === undefined || this._config![ev.target.key] === undefined) {
|
||||
return;
|
||||
}
|
||||
if (this._config![ev.target.key].value?.toString() === value) {
|
||||
if (this._config![ev.target.key].value?.toString() === ev.target.value) {
|
||||
return;
|
||||
}
|
||||
this._setResult(ev.target.key, undefined);
|
||||
|
||||
this._updateConfigParameter(ev.target, Number(value));
|
||||
this._updateConfigParameter(ev.target, Number(ev.target.value));
|
||||
}
|
||||
|
||||
private _numericInputChanged(ev) {
|
||||
@@ -511,36 +474,11 @@ class ZWaveJSNodeConfig extends LitElement {
|
||||
this._updateConfigParameter(ev.target, value);
|
||||
}
|
||||
|
||||
private _getEnumeratedPickerItems = memoizeOne(
|
||||
(states: Record<string, string>) => {
|
||||
const items: PickerComboBoxItem[] = Object.entries(states).map(
|
||||
([value, label]) => ({
|
||||
id: value,
|
||||
primary: label,
|
||||
sorting_label: `${label}_${value}`,
|
||||
})
|
||||
);
|
||||
return () => items;
|
||||
}
|
||||
);
|
||||
|
||||
private _enumeratedPickerValueRenderer = memoizeOne(
|
||||
(states: Record<string, string>) => (value: string) =>
|
||||
html`<span slot="headline">${states[value] || value}</span>`
|
||||
);
|
||||
|
||||
private _getManualEntryItems = memoizeOne(
|
||||
(states: Record<string, string>) => {
|
||||
const items: PickerComboBoxItem[] = Object.entries(states).map(
|
||||
([value, label]) => ({
|
||||
id: value,
|
||||
primary: `${label}`,
|
||||
secondary: value,
|
||||
sorting_label: `${label}_${value}`,
|
||||
})
|
||||
);
|
||||
return () => items;
|
||||
}
|
||||
private _getComboBoxOptions = memoizeOne((states: Record<string, string>) =>
|
||||
Object.entries(states).map(([value, label]) => ({
|
||||
value,
|
||||
label: `${value} - ${label}`,
|
||||
}))
|
||||
);
|
||||
|
||||
private _getComboBoxValueChangedCallback(
|
||||
|
||||
@@ -5,10 +5,9 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-color-picker";
|
||||
import "../../../components/ha-dialog-footer";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-icon-picker";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/ha-wa-dialog";
|
||||
import "../../../components/ha-textarea";
|
||||
import "../../../components/ha-textfield";
|
||||
import type { LabelRegistryEntryMutableParams } from "../../../data/label/label_registry";
|
||||
@@ -38,8 +37,6 @@ class DialogLabelDetail
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
public showDialog(params: LabelDetailDialogParams): void {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
@@ -54,17 +51,20 @@ class DialogLabelDetail
|
||||
this._color = "";
|
||||
this._description = "";
|
||||
}
|
||||
this._open = true;
|
||||
document.body.addEventListener("keydown", this._handleKeyPress);
|
||||
}
|
||||
|
||||
private _handleKeyPress = (ev: KeyboardEvent) => {
|
||||
if (ev.key === "Escape") {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
public closeDialog() {
|
||||
this._open = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
document.body.removeEventListener("keydown", this._handleKeyPress);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -73,13 +73,17 @@ class DialogLabelDetail
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this._params.entry
|
||||
? this._params.entry.name || this._params.entry.label_id
|
||||
: this.hass!.localize("ui.dialogs.label-detail.new_label")}
|
||||
@closed=${this._dialogClosed}
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this._params.entry
|
||||
? this._params.entry.name || this._params.entry.label_id
|
||||
: this.hass!.localize("ui.dialogs.label-detail.new_label")
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
${this._error
|
||||
@@ -87,7 +91,7 @@ class DialogLabelDetail
|
||||
: ""}
|
||||
<div class="form">
|
||||
<ha-textfield
|
||||
autofocus
|
||||
dialogInitialFocus
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@input=${this._input}
|
||||
@@ -121,32 +125,29 @@ class DialogLabelDetail
|
||||
></ha-textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ha-dialog-footer slot="footer">
|
||||
${this._params.entry && this._params.removeEntry
|
||||
? html`
|
||||
<ha-button
|
||||
slot="secondaryAction"
|
||||
variant="danger"
|
||||
appearance="plain"
|
||||
@click=${this._deleteEntry}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass!.localize("ui.common.delete")}
|
||||
</ha-button>
|
||||
`
|
||||
: nothing}
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateEntry}
|
||||
.disabled=${this._submitting || !this._name}
|
||||
>
|
||||
${this._params.entry
|
||||
? this.hass!.localize("ui.common.update")
|
||||
: this.hass!.localize("ui.common.create")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-wa-dialog>
|
||||
${this._params.entry && this._params.removeEntry
|
||||
? html`
|
||||
<ha-button
|
||||
slot="secondaryAction"
|
||||
variant="danger"
|
||||
appearance="plain"
|
||||
@click=${this._deleteEntry}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass!.localize("ui.common.delete")}
|
||||
</ha-button>
|
||||
`
|
||||
: nothing}
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateEntry}
|
||||
.disabled=${this._submitting || !this._name}
|
||||
>
|
||||
${this._params.entry
|
||||
? this.hass!.localize("ui.common.update")
|
||||
: this.hass!.localize("ui.common.create")}
|
||||
</ha-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -673,12 +673,12 @@ class HaPanelDevAction extends LitElement {
|
||||
haStyle,
|
||||
css`
|
||||
.content {
|
||||
padding: var(--ha-space-4);
|
||||
padding: 16px;
|
||||
max-width: 1200px;
|
||||
margin: auto;
|
||||
}
|
||||
.button-row {
|
||||
padding: var(--ha-space-2) var(--ha-space-4);
|
||||
padding: 8px 16px;
|
||||
border-top: 1px solid var(--divider-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
background: var(--card-background-color);
|
||||
@@ -698,8 +698,8 @@ class HaPanelDevAction extends LitElement {
|
||||
align-items: center;
|
||||
}
|
||||
.switch-mode-container .error {
|
||||
margin-left: var(--ha-space-2);
|
||||
margin-inline-start: var(--ha-space-2);
|
||||
margin-left: 8px;
|
||||
margin-inline-start: 8px;
|
||||
margin-inline-end: initial;
|
||||
}
|
||||
.attributes {
|
||||
@@ -732,7 +732,7 @@ class HaPanelDevAction extends LitElement {
|
||||
}
|
||||
|
||||
.attributes td {
|
||||
padding: var(--ha-space-1);
|
||||
padding: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@@ -748,7 +748,7 @@ class HaPanelDevAction extends LitElement {
|
||||
.response img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin-top: var(--ha-space-6);
|
||||
margin-top: 24px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -240,13 +240,13 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) {
|
||||
haStyle,
|
||||
css`
|
||||
.content {
|
||||
padding: var(--ha-space-7) var(--ha-space-5) var(--ha-space-4);
|
||||
padding: 28px 20px 16px;
|
||||
max-width: 1040px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.description {
|
||||
margin: 0;
|
||||
margin-bottom: var(--ha-space-4);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-textarea {
|
||||
width: 100%;
|
||||
@@ -255,18 +255,18 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) {
|
||||
text-align: right;
|
||||
}
|
||||
.form {
|
||||
margin-bottom: var(--ha-space-4);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.result-toolbar {
|
||||
text-align: center;
|
||||
margin-bottom: var(--ha-space-4);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.result {
|
||||
margin-bottom: var(--ha-space-4);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.sentence {
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
margin-bottom: var(--ha-space-2);
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
@@ -280,7 +280,7 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) {
|
||||
ha-code-editor,
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-top: var(--ha-space-4);
|
||||
margin-top: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "./ha-debug-connection-row";
|
||||
import {
|
||||
getStatisticMetadata,
|
||||
validateStatistics,
|
||||
} from "../../../data/recorder";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { getExtendedEntityRegistryEntry } from "../../../data/entity/entity_registry";
|
||||
|
||||
@customElement("developer-tools-debug")
|
||||
class HaPanelDevDebug extends SubscribeMixin(LitElement) {
|
||||
@@ -22,8 +12,6 @@ class HaPanelDevDebug extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@state() private _entityId?: string;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="content">
|
||||
@@ -37,83 +25,15 @@ class HaPanelDevDebug extends SubscribeMixin(LitElement) {
|
||||
.narrow=${this.narrow}
|
||||
></ha-debug-connection-row>
|
||||
</ha-card>
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.debug.entity_diagnostic.title"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
.helper=${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.debug.entity_diagnostic.description"
|
||||
)}
|
||||
@value-changed=${this._entityPicked}
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-button
|
||||
@click=${this._copyEntityDiagnostic}
|
||||
appearance="filled"
|
||||
.disabled=${!this._entityId}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.debug.entity_diagnostic.copy_to_clipboard"
|
||||
)}</ha-button
|
||||
>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _copyEntityDiagnostic() {
|
||||
const id = this._entityId!;
|
||||
let statistic;
|
||||
if (computeDomain(id) === "sensor") {
|
||||
const [metadata, issues] = await Promise.all([
|
||||
getStatisticMetadata(this.hass, [id]),
|
||||
validateStatistics(this.hass),
|
||||
]);
|
||||
const issue = issues[id];
|
||||
if (metadata || issue) {
|
||||
statistic = {
|
||||
metadata,
|
||||
issue,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const entity = await getExtendedEntityRegistryEntry(this.hass, id);
|
||||
const device = entity?.device_id && this.hass.devices[entity.device_id];
|
||||
|
||||
const data = {
|
||||
state: this.hass.states[id],
|
||||
entity,
|
||||
device,
|
||||
statistic,
|
||||
};
|
||||
const json = JSON.stringify(data, null, 2);
|
||||
await copyToClipboard(json);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
}
|
||||
|
||||
private _entityPicked(ev) {
|
||||
this._entityId = ev.detail.value;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
haStyle,
|
||||
css`
|
||||
ha-card {
|
||||
margin-bottom: var(--ha-space-4);
|
||||
}
|
||||
.card-content {
|
||||
padding: var(--ha-space-2);
|
||||
}
|
||||
.content {
|
||||
padding: var(--ha-space-7) var(--ha-space-5) var(--ha-space-4);
|
||||
padding: 28px 20px 16px;
|
||||
display: block;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -148,7 +148,7 @@ class HaPanelDevEvent extends LitElement {
|
||||
css`
|
||||
.content {
|
||||
gap: var(--ha-space-4);
|
||||
padding: var(--ha-space-4);
|
||||
padding: 16px;
|
||||
max-width: 1200px;
|
||||
margin: auto;
|
||||
}
|
||||
@@ -169,7 +169,7 @@ class HaPanelDevEvent extends LitElement {
|
||||
}
|
||||
|
||||
ha-button {
|
||||
margin-top: var(--ha-space-2);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
ha-textfield {
|
||||
@@ -178,7 +178,7 @@ class HaPanelDevEvent extends LitElement {
|
||||
|
||||
event-subscribe-card {
|
||||
display: block;
|
||||
margin-top: var(--ha-space-4);
|
||||
margin-top: 16px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
|
||||
@@ -160,16 +160,16 @@ class EventSubscribeCard extends LitElement {
|
||||
static styles = css`
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin-bottom: var(--ha-space-4);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.error-message {
|
||||
margin-top: var(--ha-space-2);
|
||||
margin-top: 8px;
|
||||
}
|
||||
.event {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
padding-top: var(--ha-space-2);
|
||||
padding-bottom: var(--ha-space-2);
|
||||
margin: var(--ha-space-4) 0;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.event:last-child {
|
||||
border-bottom: 0;
|
||||
@@ -179,7 +179,7 @@ class EventSubscribeCard extends LitElement {
|
||||
font-family: var(--ha-font-family-code);
|
||||
}
|
||||
ha-card {
|
||||
margin-bottom: var(--ha-space-1);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -176,12 +176,12 @@ class PanelDeveloperTools extends LitElement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: var(--ha-font-size-xl);
|
||||
padding: var(--ha-space-2) var(--ha-space-3);
|
||||
padding: 8px 12px;
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:host([narrow]) .toolbar {
|
||||
padding: var(--ha-space-1);
|
||||
padding: 4px;
|
||||
}
|
||||
.main-title {
|
||||
margin: var(--margin-title);
|
||||
|
||||
@@ -298,7 +298,7 @@ class HaPanelDevStateRenderer extends LitElement {
|
||||
}
|
||||
|
||||
.cell .padded {
|
||||
padding: var(--ha-space-1);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.entities .row .header:nth-child(1),
|
||||
@@ -328,11 +328,11 @@ class HaPanelDevStateRenderer extends LitElement {
|
||||
|
||||
.entities ha-svg-icon {
|
||||
--mdc-icon-size: 20px;
|
||||
padding: var(--ha-space-1);
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
margin-right: var(--ha-space-2);
|
||||
margin-inline-end: var(--ha-space-2);
|
||||
margin-right: 8px;
|
||||
margin-inline-end: 8px;
|
||||
margin-inline-start: initial;
|
||||
}
|
||||
|
||||
|
||||
@@ -144,6 +144,7 @@ class HaPanelDevState extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.value=${this._entityId}
|
||||
@value-changed=${this._entityIdChanged}
|
||||
allow-custom-entity
|
||||
show-entity-id
|
||||
></ha-entity-picker>
|
||||
${this._entityId
|
||||
@@ -507,7 +508,7 @@ class HaPanelDevState extends LitElement {
|
||||
-webkit-user-select: initial;
|
||||
-moz-user-select: initial;
|
||||
display: block;
|
||||
padding: var(--ha-space-4);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
:host search-input {
|
||||
@@ -525,7 +526,7 @@ class HaPanelDevState extends LitElement {
|
||||
}
|
||||
|
||||
.heading ha-formfield {
|
||||
margin-right: var(--ha-space-2);
|
||||
margin-right: 8px;
|
||||
--mdc-typography-body2-font-size: var(--ha-font-size-m);
|
||||
--mdc-typography-body2-font-weight: var(--ha-font-weight-medium);
|
||||
}
|
||||
@@ -534,9 +535,9 @@ class HaPanelDevState extends LitElement {
|
||||
display: block;
|
||||
font-family: var(--ha-font-family-code);
|
||||
color: var(--secondary-text-color);
|
||||
padding: 0 var(--ha-space-2);
|
||||
margin-bottom: var(--ha-space-2);
|
||||
margin-top: var(--ha-space-1);
|
||||
padding: 0 8px;
|
||||
margin-bottom: 8px;
|
||||
margin-top: 4px;
|
||||
font-size: var(--ha-font-size-s);
|
||||
--mdc-icon-size: 14px;
|
||||
--mdc-icon-button-size: 24px;
|
||||
@@ -557,15 +558,15 @@ class HaPanelDevState extends LitElement {
|
||||
}
|
||||
|
||||
.state-input {
|
||||
margin-top: var(--ha-space-4);
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
ha-expansion-panel {
|
||||
margin: 0 var(--ha-space-2) var(--ha-space-4);
|
||||
margin: 0 8px 16px;
|
||||
}
|
||||
|
||||
ha-expansion-panel p {
|
||||
padding: 0 var(--ha-space-2);
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
@@ -574,12 +575,12 @@ class HaPanelDevState extends LitElement {
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 0 var(--ha-space-4);
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
margin: var(--ha-space-2) 0;
|
||||
margin: 8px 0;
|
||||
align-items: center;
|
||||
gap: var(--ha-space-2);
|
||||
}
|
||||
|
||||
@@ -732,7 +732,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
height: 56px;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--ha-space-4);
|
||||
padding: 0 16px;
|
||||
gap: var(--ha-space-4);
|
||||
box-sizing: border-box;
|
||||
background: var(--primary-background-color);
|
||||
@@ -751,7 +751,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--ha-space-4);
|
||||
padding: 0 var(--ha-space-4);
|
||||
padding: 0 16px;
|
||||
overflow-x: scroll;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
@@ -763,7 +763,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--ha-space-2) var(--ha-space-3);
|
||||
padding: 8px 12px;
|
||||
box-sizing: border-box;
|
||||
font-size: var(--ha-font-size-m);
|
||||
--ha-assist-chip-container-color: var(--card-background-color);
|
||||
@@ -776,8 +776,8 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
}
|
||||
|
||||
.selection-controls p {
|
||||
margin-left: var(--ha-space-2);
|
||||
margin-inline-start: var(--ha-space-2);
|
||||
margin-left: 8px;
|
||||
margin-inline-start: 8px;
|
||||
margin-inline-end: initial;
|
||||
}
|
||||
|
||||
|
||||
@@ -513,7 +513,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement {
|
||||
.text-content,
|
||||
ha-selector-datetime,
|
||||
ha-selector-number {
|
||||
margin-bottom: var(--ha-space-5);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
ha-list-item {
|
||||
margin: 0 -24px;
|
||||
@@ -522,7 +522,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement {
|
||||
.table-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--ha-space-5);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.stat-list {
|
||||
min-height: 360px;
|
||||
|
||||
@@ -275,7 +275,7 @@ ${type === "object"
|
||||
|
||||
.content {
|
||||
gap: var(--ha-space-4);
|
||||
padding: var(--ha-space-4);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.content.horizontal {
|
||||
@@ -289,7 +289,7 @@ ${type === "object"
|
||||
}
|
||||
|
||||
ha-card {
|
||||
margin-bottom: var(--ha-space-4);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.edit-pane {
|
||||
@@ -307,14 +307,14 @@ ${type === "object"
|
||||
|
||||
.render-spinner {
|
||||
position: absolute;
|
||||
top: var(--ha-space-2);
|
||||
right: var(--ha-space-2);
|
||||
inset-inline-end: var(--ha-space-2);
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
inset-inline-end: 8px;
|
||||
inset-inline-start: initial;
|
||||
}
|
||||
|
||||
ha-alert {
|
||||
margin-bottom: var(--ha-space-2);
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ ${type === "object"
|
||||
clear: both;
|
||||
white-space: pre-wrap;
|
||||
background-color: var(--secondary-background-color);
|
||||
padding: var(--ha-space-2);
|
||||
padding: 8px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
direction: ltr;
|
||||
|
||||
@@ -251,19 +251,19 @@ export class DeveloperYamlConfig extends LitElement {
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: var(--ha-space-7) var(--ha-space-5) var(--ha-space-4);
|
||||
padding: 28px 20px 16px;
|
||||
max-width: 1040px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
margin-top: var(--ha-space-6);
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: var(--ha-space-1);
|
||||
padding: 4px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -75,6 +75,7 @@ export class DialogEditHome
|
||||
"ui.panel.home.editor.favorite_entities_helper"
|
||||
)}
|
||||
reorder
|
||||
allow-custom-entity
|
||||
@value-changed=${this._favoriteEntitiesChanged}
|
||||
></ha-entities-picker>
|
||||
|
||||
|
||||
@@ -11,7 +11,10 @@ import { findEntities } from "../common/find-entities";
|
||||
import type { LovelaceElement, LovelaceElementConfig } from "../elements/types";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { createStyledHuiElement } from "./picture-elements/create-styled-hui-element";
|
||||
import type { PictureElementsCardConfig } from "./types";
|
||||
import {
|
||||
PREVIEW_CLICK_CALLBACK,
|
||||
type PictureElementsCardConfig,
|
||||
} from "./types";
|
||||
import type { PersonEntity } from "../../../data/person";
|
||||
|
||||
@customElement("hui-picture-elements-card")
|
||||
@@ -166,6 +169,7 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard {
|
||||
.aspectRatio=${this._config.aspect_ratio}
|
||||
.darkModeFilter=${this._config.dark_mode_filter}
|
||||
.darkModeImage=${darkModeImage}
|
||||
@click=${this._handleImageClick}
|
||||
></hui-image>
|
||||
${this._elements}
|
||||
</div>
|
||||
@@ -221,6 +225,19 @@ class HuiPictureElementsCard extends LitElement implements LovelaceCard {
|
||||
curCardEl === elToReplace ? newCardEl : curCardEl
|
||||
);
|
||||
}
|
||||
|
||||
private _handleImageClick(ev: MouseEvent): void {
|
||||
if (!this.preview || !this._config?.[PREVIEW_CLICK_CALLBACK]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = (ev.currentTarget as HTMLElement).getBoundingClientRect();
|
||||
const x = ((ev.clientX - rect.left) / rect.width) * 100;
|
||||
const y = ((ev.clientY - rect.top) / rect.height) * 100;
|
||||
|
||||
// only the edited card has this callback
|
||||
this._config[PREVIEW_CLICK_CALLBACK](x, y);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -483,6 +483,10 @@ export interface PictureCardConfig extends LovelaceCardConfig {
|
||||
alt_text?: string;
|
||||
}
|
||||
|
||||
// Symbol for preview click callback - preserved through spreads, not serialized
|
||||
// This allows the editor to attach a callback that only exists on the edited card's config
|
||||
export const PREVIEW_CLICK_CALLBACK = Symbol("previewClickCallback");
|
||||
|
||||
export interface PictureElementsCardConfig extends LovelaceCardConfig {
|
||||
title?: string;
|
||||
image?: string | MediaSelectorValue;
|
||||
@@ -497,6 +501,7 @@ export interface PictureElementsCardConfig extends LovelaceCardConfig {
|
||||
theme?: string;
|
||||
dark_mode_image?: string | MediaSelectorValue;
|
||||
dark_mode_filter?: string;
|
||||
[PREVIEW_CLICK_CALLBACK]?: (x: number, y: number) => void;
|
||||
}
|
||||
|
||||
export interface PictureEntityCardConfig extends LovelaceCardConfig {
|
||||
|
||||
@@ -167,6 +167,7 @@ export class HuiEntityEditor extends LitElement {
|
||||
.index=${index}
|
||||
.entityFilter=${this.entityFilter}
|
||||
@value-changed=${this._valueChanged}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
`
|
||||
|
||||
@@ -51,6 +51,7 @@ export class HuiGraphFooterEditor
|
||||
return html`
|
||||
<div class="card-config">
|
||||
<ha-entity-picker
|
||||
allow-custom-entity
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.entity"
|
||||
)}
|
||||
|
||||
@@ -78,6 +78,7 @@ export class HuiHeadingBadgesEditor extends LitElement {
|
||||
${isEntityBadge && entityBadge
|
||||
? html`
|
||||
<ha-entity-picker
|
||||
allow-custom-entity
|
||||
hide-clear-icon
|
||||
.hass=${this.hass}
|
||||
.value=${entityBadge.entity ?? ""}
|
||||
@@ -130,6 +131,7 @@ export class HuiHeadingBadgesEditor extends LitElement {
|
||||
@value-changed=${this._entityPicked}
|
||||
.value=${undefined}
|
||||
@click=${preventDefault}
|
||||
allow-custom-entity
|
||||
add-button
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
|
||||
@@ -15,12 +15,16 @@ import {
|
||||
} from "superstruct";
|
||||
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import "../../../../components/ha-icon";
|
||||
import "../../../../components/ha-switch";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { PictureElementsCardConfig } from "../../cards/types";
|
||||
import {
|
||||
PREVIEW_CLICK_CALLBACK,
|
||||
type PictureElementsCardConfig,
|
||||
} from "../../cards/types";
|
||||
import type { LovelaceCardEditor } from "../../types";
|
||||
import "../hui-sub-element-editor";
|
||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
@@ -28,7 +32,6 @@ import type { EditDetailElementEvent, SubElementEditorConfig } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import "../hui-picture-elements-card-row-editor";
|
||||
import type { LovelaceElementConfig } from "../../elements/types";
|
||||
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
|
||||
const genericElementConfigStruct = type({
|
||||
@@ -66,6 +69,44 @@ export class HuiPictureElementsCardEditor
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
private _onPreviewClick = (x: number, y: number): void => {
|
||||
if (this._subElementEditorConfig?.type === "element") {
|
||||
this._handlePositionClick(x, y);
|
||||
}
|
||||
};
|
||||
|
||||
private _handlePositionClick(x: number, y: number): void {
|
||||
if (
|
||||
!this._subElementEditorConfig?.elementConfig ||
|
||||
this._subElementEditorConfig.type !== "element" ||
|
||||
this._subElementEditorConfig.elementConfig.type === "conditional"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const elementConfig = this._subElementEditorConfig
|
||||
.elementConfig as LovelaceElementConfig;
|
||||
const currentPosition = (elementConfig.style as Record<string, string>)
|
||||
?.position;
|
||||
if (currentPosition && currentPosition !== "absolute") {
|
||||
return;
|
||||
}
|
||||
|
||||
const newElement = {
|
||||
...elementConfig,
|
||||
style: {
|
||||
...((elementConfig.style as Record<string, string>) || {}),
|
||||
left: `${Math.round(x)}%`,
|
||||
top: `${Math.round(y)}%`,
|
||||
},
|
||||
};
|
||||
|
||||
const updateEvent = new CustomEvent("config-changed", {
|
||||
detail: { config: newElement },
|
||||
});
|
||||
this._handleSubElementChanged(updateEvent);
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(localize: LocalizeFunc) =>
|
||||
[
|
||||
@@ -138,6 +179,16 @@ export class HuiPictureElementsCardEditor
|
||||
|
||||
if (this._subElementEditorConfig) {
|
||||
return html`
|
||||
${this._subElementEditorConfig.type === "element" &&
|
||||
this._subElementEditorConfig.elementConfig?.type !== "conditional"
|
||||
? html`
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.picture-elements.position_hint"
|
||||
)}
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing}
|
||||
<hui-sub-element-editor
|
||||
.hass=${this.hass}
|
||||
.config=${this._subElementEditorConfig}
|
||||
@@ -181,6 +232,7 @@ export class HuiPictureElementsCardEditor
|
||||
return;
|
||||
}
|
||||
|
||||
// no need to attach the preview click callback here, no element is being edited
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
}
|
||||
|
||||
@@ -191,7 +243,8 @@ export class HuiPictureElementsCardEditor
|
||||
const config = {
|
||||
...this._config,
|
||||
elements: ev.detail.elements as LovelaceElementConfig[],
|
||||
} as LovelaceCardConfig;
|
||||
[PREVIEW_CLICK_CALLBACK]: this._onPreviewClick,
|
||||
} as PictureElementsCardConfig;
|
||||
|
||||
fireEvent(this, "config-changed", { config });
|
||||
|
||||
@@ -232,7 +285,12 @@ export class HuiPictureElementsCardEditor
|
||||
elementConfig: value,
|
||||
};
|
||||
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
fireEvent(this, "config-changed", {
|
||||
config: {
|
||||
...this._config,
|
||||
[PREVIEW_CLICK_CALLBACK]: this._onPreviewClick,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _editDetailElement(ev: HASSDomEvent<EditDetailElementEvent>): void {
|
||||
|
||||
@@ -316,6 +316,7 @@ export class HuiStatisticsGraphCardEditor
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
<ha-statistics-picker
|
||||
allow-custom-entity
|
||||
.hass=${this.hass}
|
||||
.placeholder=${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.card.statistics-graph.pick_statistic"
|
||||
|
||||
@@ -10,7 +10,9 @@ export const getElementStubConfig = async (
|
||||
): Promise<LovelaceElementConfig> => {
|
||||
let elementConfig: LovelaceElementConfig = { type };
|
||||
|
||||
if (type !== "conditional") {
|
||||
if (type === "conditional") {
|
||||
elementConfig = { type, conditions: [], elements: [] };
|
||||
} else {
|
||||
elementConfig.style = { left: "50%", top: "50%" };
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,11 @@ export abstract class HuiElementEditor<
|
||||
}
|
||||
|
||||
public set value(config: T | undefined) {
|
||||
if (this._config && deepEqual(config, this._config)) {
|
||||
// Compare symbols to detect callback changes (e.g., preview click handlers)
|
||||
if (
|
||||
this._config &&
|
||||
deepEqual(config, this._config, { compareSymbols: true })
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this._config = config;
|
||||
|
||||
@@ -80,6 +80,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
|
||||
`
|
||||
: html`
|
||||
<ha-entity-picker
|
||||
allow-custom-entity
|
||||
hide-clear-icon
|
||||
.hass=${this.hass}
|
||||
.value=${(entityConf as EntityConfig).entity}
|
||||
|
||||
@@ -36,6 +36,7 @@ export class HuiHomeDashboardStrategyEditor
|
||||
"ui.panel.lovelace.editor.strategy.home.add_favorite_entity"
|
||||
)}
|
||||
reorder
|
||||
allow-custom-entity
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-entities-picker>
|
||||
|
||||
@@ -141,17 +141,30 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
);
|
||||
const maxCommonControls = Math.max(8, favoriteEntities.length);
|
||||
|
||||
const commonControlsSection = {
|
||||
const commonControlsSectionBase = {
|
||||
strategy: {
|
||||
type: "common-controls",
|
||||
limit: maxCommonControls,
|
||||
include_entities: favoriteEntities,
|
||||
title: hass.localize("ui.panel.lovelace.strategy.home.favorites"),
|
||||
hide_empty: true,
|
||||
} satisfies CommonControlSectionStrategyConfig,
|
||||
column_span: maxColumns,
|
||||
} as LovelaceStrategySectionConfig;
|
||||
|
||||
const commonControlsSectionMobile = {
|
||||
...commonControlsSectionBase,
|
||||
strategy: {
|
||||
...commonControlsSectionBase.strategy,
|
||||
title: hass.localize("ui.panel.lovelace.strategy.home.commonly_used"),
|
||||
},
|
||||
visibility: [smallScreenCondition],
|
||||
} as LovelaceStrategySectionConfig;
|
||||
|
||||
const commonControlsSectionDesktop = {
|
||||
...commonControlsSectionBase,
|
||||
visibility: [largeScreenCondition],
|
||||
} as LovelaceStrategySectionConfig;
|
||||
|
||||
const allEntities = Object.keys(hass.states);
|
||||
|
||||
const mediaPlayerFilter = HOME_SUMMARIES_FILTERS.media_players.map(
|
||||
@@ -296,8 +309,20 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
|
||||
|
||||
const sections = (
|
||||
[
|
||||
{
|
||||
type: "grid",
|
||||
cards: [
|
||||
// Heading to add some spacing on large screens
|
||||
{
|
||||
type: "heading",
|
||||
heading_style: "subtitle",
|
||||
visibility: [largeScreenCondition],
|
||||
},
|
||||
],
|
||||
},
|
||||
mobileSummarySection,
|
||||
commonControlsSection,
|
||||
commonControlsSectionMobile,
|
||||
commonControlsSectionDesktop,
|
||||
...floorsSections,
|
||||
] satisfies (LovelaceSectionRawConfig | undefined)[]
|
||||
).filter(Boolean) as LovelaceSectionRawConfig[];
|
||||
|
||||
@@ -28,9 +28,6 @@ export const getMyRedirects = (): Redirects => ({
|
||||
developer_assist: {
|
||||
redirect: "/developer-tools/assist",
|
||||
},
|
||||
developer_debug: {
|
||||
redirect: "/developer-tools/debug",
|
||||
},
|
||||
developer_states: {
|
||||
redirect: "/developer-tools/state",
|
||||
},
|
||||
|
||||
@@ -339,6 +339,12 @@ export const colorStyles = css`
|
||||
--ha-assist-chip-filled-container-color: rgba(var(--rgb-primary-text-color), 0.15);
|
||||
--ha-assist-chip-active-container-color: rgba(var(--rgb-primary-color), 0.15);
|
||||
--chip-background-color: rgba(var(--rgb-primary-text-color), 0.15);
|
||||
|
||||
/* Vaadin */
|
||||
--material-body-text-color: var(--primary-text-color);
|
||||
--material-background-color: var(--card-background-color);
|
||||
--material-secondary-background-color: var(--secondary-background-color);
|
||||
--material-secondary-text-color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -36,6 +36,12 @@ export const typographyStyles = css`
|
||||
--ha-font-smoothing: antialiased;
|
||||
--ha-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
/* Vaadin typography */
|
||||
--material-h6-font-size: var(--ha-font-size-m);
|
||||
--material-small-font-size: var(--ha-font-size-xs);
|
||||
--material-caption-font-size: var(--ha-font-size-2xs);
|
||||
--material-button-font-size: var(--ha-font-size-xs);
|
||||
|
||||
/* Add font to lists since default does not handle non-latin characters */
|
||||
--md-list-item-label-text-font: var(--ha-font-family-body);
|
||||
--md-list-item-supporting-text-font: var(--ha-font-family-body);
|
||||
|
||||
@@ -672,20 +672,17 @@
|
||||
"device_missing": "No related device"
|
||||
},
|
||||
"add": "Add",
|
||||
"custom_name": "Custom name",
|
||||
"no_match": "No entities found"
|
||||
"custom_name": "Custom name"
|
||||
},
|
||||
"entity-attribute-picker": {
|
||||
"attribute": "Attribute",
|
||||
"show_attributes": "Show attributes"
|
||||
},
|
||||
"entity-state-picker": {
|
||||
"state": "State",
|
||||
"add_custom_state": "Add custom state"
|
||||
"state": "State"
|
||||
},
|
||||
"entity-state-content-picker": {
|
||||
"add": "Add",
|
||||
"custom_state": "Custom state"
|
||||
"add": "Add"
|
||||
}
|
||||
},
|
||||
"target-picker": {
|
||||
@@ -1311,9 +1308,7 @@
|
||||
"combo-box": {
|
||||
"no_match": "No matching items found",
|
||||
"no_items": "No items available",
|
||||
"unknown_item": "Unknown item",
|
||||
"search_or_custom": "Search | Add custom item",
|
||||
"add_custom_item": "Add custom item"
|
||||
"unknown_item": "Unknown item"
|
||||
},
|
||||
"suggest_with_ai": {
|
||||
"label": "Suggest",
|
||||
@@ -1322,9 +1317,6 @@
|
||||
"suggesting_3": "Enchanting…",
|
||||
"done": "Done!",
|
||||
"error": "Fail!"
|
||||
},
|
||||
"navigation-picker": {
|
||||
"add_custom_path": "Add custom path"
|
||||
}
|
||||
},
|
||||
"dialogs": {
|
||||
@@ -7185,7 +7177,7 @@
|
||||
"automations": "Automations",
|
||||
"for_you": "For you",
|
||||
"home": "Home",
|
||||
"favorites": "Favorites"
|
||||
"commonly_used": "Commonly used"
|
||||
},
|
||||
"common_controls": {
|
||||
"not_loaded": "Usage Prediction integration is not loaded.",
|
||||
@@ -8225,6 +8217,7 @@
|
||||
"dark_mode_image": "Dark mode image path",
|
||||
"state_filter": "State filter",
|
||||
"dark_mode_filter": "Dark mode state filter",
|
||||
"position_hint": "Click on the image preview to position this element",
|
||||
"element_types": {
|
||||
"state-badge": "State badge",
|
||||
"state-icon": "State icon",
|
||||
@@ -9141,11 +9134,6 @@
|
||||
"debug_connection": {
|
||||
"title": "Debug connection",
|
||||
"description": "Observe requests to the server and responses from the server in browser console."
|
||||
},
|
||||
"entity_diagnostic": {
|
||||
"title": "Entity diagnostic",
|
||||
"description": "Select an entity to copy diagnostic details.",
|
||||
"copy_to_clipboard": "[%key:ui::panel::config::automation::editor::copy_to_clipboard%]"
|
||||
}
|
||||
},
|
||||
"events": {
|
||||
|
||||
476
yarn.lock
476
yarn.lock
@@ -3595,6 +3595,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@open-wc/dedupe-mixin@npm:^1.3.0":
|
||||
version: 1.4.0
|
||||
resolution: "@open-wc/dedupe-mixin@npm:1.4.0"
|
||||
checksum: 10/808ceddeb8e294ffb905d90e7ad9fc0dae5f38f4fd856615658f27806eb2e7356c643629f36f9ebd9cc170f9d5249f9c6220a8569436f513bd50e5d6f04185cb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@pkgjs/parseargs@npm:^0.11.0":
|
||||
version: 0.11.0
|
||||
resolution: "@pkgjs/parseargs@npm:0.11.0"
|
||||
@@ -3602,6 +3609,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@polymer/polymer@npm:^3.0.0":
|
||||
version: 3.5.2
|
||||
resolution: "@polymer/polymer@npm:3.5.2"
|
||||
dependencies:
|
||||
"@webcomponents/shadycss": "npm:^1.9.1"
|
||||
checksum: 10/fdacd436c64e8e122090480f99fb94396c3f351b99f6919e579197a2880a357bc3e8e7a2922e32a3ed0c0a363f0b096242faee93a23800768ed27784d49664ad
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@reallyland/esm@npm:^0.0.1":
|
||||
version: 0.0.1
|
||||
resolution: "@reallyland/esm@npm:0.0.1"
|
||||
@@ -3877,22 +3893,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/client@npm:1.3.16":
|
||||
version: 1.3.16
|
||||
resolution: "@rsdoctor/client@npm:1.3.16"
|
||||
checksum: 10/1a2516c13407c1030984bd5fc345a9b5600c6f2015df98cf9c6a72dded324da6d86a88ebf844bcdad81ff8f8f516502516b45b9f0e89d82942ee3a4266cda00a
|
||||
"@rsdoctor/client@npm:1.3.15":
|
||||
version: 1.3.15
|
||||
resolution: "@rsdoctor/client@npm:1.3.15"
|
||||
checksum: 10/a1b024b5af2d09763125bad2f71e476fb741ba473daf4eebda37aa403568b70609b411efe9562071472c4b2f2f2ae412d41efab312b64fda800dc618fe25241c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/core@npm:1.3.16":
|
||||
version: 1.3.16
|
||||
resolution: "@rsdoctor/core@npm:1.3.16"
|
||||
"@rsdoctor/core@npm:1.3.15":
|
||||
version: 1.3.15
|
||||
resolution: "@rsdoctor/core@npm:1.3.15"
|
||||
dependencies:
|
||||
"@rsbuild/plugin-check-syntax": "npm:1.5.0"
|
||||
"@rsdoctor/graph": "npm:1.3.16"
|
||||
"@rsdoctor/sdk": "npm:1.3.16"
|
||||
"@rsdoctor/types": "npm:1.3.16"
|
||||
"@rsdoctor/utils": "npm:1.3.16"
|
||||
"@rsdoctor/graph": "npm:1.3.15"
|
||||
"@rsdoctor/sdk": "npm:1.3.15"
|
||||
"@rsdoctor/types": "npm:1.3.15"
|
||||
"@rsdoctor/utils": "npm:1.3.15"
|
||||
browserslist-load-config: "npm:^1.0.1"
|
||||
enhanced-resolve: "npm:5.12.0"
|
||||
es-toolkit: "npm:^1.41.0"
|
||||
@@ -3900,59 +3916,59 @@ __metadata:
|
||||
fs-extra: "npm:^11.1.1"
|
||||
semver: "npm:^7.7.3"
|
||||
source-map: "npm:^0.7.6"
|
||||
checksum: 10/9b48ab07803214f53f1432bb002fcd9e749ec8e2f951632ab1ef500a060d8af3734f864149b2340b8ea97451f375415c4b0116cdc15ea7679d419714d02220a7
|
||||
checksum: 10/9de4c2c3953473cd87d393248df75f1ccf03191b5aa2f45e331d0d300e484208de97616b3d06d1910fdde68678e5e752eadb509abe09c470383e9427eb30faf3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/graph@npm:1.3.16":
|
||||
version: 1.3.16
|
||||
resolution: "@rsdoctor/graph@npm:1.3.16"
|
||||
"@rsdoctor/graph@npm:1.3.15":
|
||||
version: 1.3.15
|
||||
resolution: "@rsdoctor/graph@npm:1.3.15"
|
||||
dependencies:
|
||||
"@rsdoctor/types": "npm:1.3.16"
|
||||
"@rsdoctor/utils": "npm:1.3.16"
|
||||
"@rsdoctor/types": "npm:1.3.15"
|
||||
"@rsdoctor/utils": "npm:1.3.15"
|
||||
es-toolkit: "npm:^1.41.0"
|
||||
path-browserify: "npm:1.0.1"
|
||||
source-map: "npm:^0.7.6"
|
||||
checksum: 10/a66c27dabd0b4e98adb75c7de46c8b015c9b81f9b8fd89560e8766d074e156792cb914bb789a24f0888339c27f1e89c43c3bdd778d20035e059c9798dc0ff970
|
||||
checksum: 10/3bbafc0d03463346c00cd0fd29da01d66d086342c228c41ee2cf9e63e87bc3c8e30e571aff02b679f93bf8ca8726b861c64bfd8b41dde2d035935027b9c1fcba
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/rspack-plugin@npm:1.3.16":
|
||||
version: 1.3.16
|
||||
resolution: "@rsdoctor/rspack-plugin@npm:1.3.16"
|
||||
"@rsdoctor/rspack-plugin@npm:1.3.15":
|
||||
version: 1.3.15
|
||||
resolution: "@rsdoctor/rspack-plugin@npm:1.3.15"
|
||||
dependencies:
|
||||
"@rsdoctor/core": "npm:1.3.16"
|
||||
"@rsdoctor/graph": "npm:1.3.16"
|
||||
"@rsdoctor/sdk": "npm:1.3.16"
|
||||
"@rsdoctor/types": "npm:1.3.16"
|
||||
"@rsdoctor/utils": "npm:1.3.16"
|
||||
"@rsdoctor/core": "npm:1.3.15"
|
||||
"@rsdoctor/graph": "npm:1.3.15"
|
||||
"@rsdoctor/sdk": "npm:1.3.15"
|
||||
"@rsdoctor/types": "npm:1.3.15"
|
||||
"@rsdoctor/utils": "npm:1.3.15"
|
||||
peerDependencies:
|
||||
"@rspack/core": "*"
|
||||
peerDependenciesMeta:
|
||||
"@rspack/core":
|
||||
optional: true
|
||||
checksum: 10/2745dd1299c618fbcfc40a56bf58e5ed15cadc37217149cd017cc623a9d694b907ccab6a99ba23f1b63e9381114ec00fb3932c15f51dd111652a140d78c425ee
|
||||
checksum: 10/c20eac90976f5cf4b0659c4b83a283cd512e79c4af35a5701eb458efa5466084a723c766324686d68b0261aa472b38f23c96605641693d60d48002ae9295ff93
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/sdk@npm:1.3.16":
|
||||
version: 1.3.16
|
||||
resolution: "@rsdoctor/sdk@npm:1.3.16"
|
||||
"@rsdoctor/sdk@npm:1.3.15":
|
||||
version: 1.3.15
|
||||
resolution: "@rsdoctor/sdk@npm:1.3.15"
|
||||
dependencies:
|
||||
"@rsdoctor/client": "npm:1.3.16"
|
||||
"@rsdoctor/graph": "npm:1.3.16"
|
||||
"@rsdoctor/types": "npm:1.3.16"
|
||||
"@rsdoctor/utils": "npm:1.3.16"
|
||||
"@rsdoctor/client": "npm:1.3.15"
|
||||
"@rsdoctor/graph": "npm:1.3.15"
|
||||
"@rsdoctor/types": "npm:1.3.15"
|
||||
"@rsdoctor/utils": "npm:1.3.15"
|
||||
safer-buffer: "npm:2.1.2"
|
||||
socket.io: "npm:4.8.1"
|
||||
tapable: "npm:2.2.3"
|
||||
checksum: 10/dba508539428b085cddda81254458a55e4279e5ebc452c6e6919658067c9ce5d3b8eec4878bdbc7ba5959d7b0615adb7a6fb144e2f9c93d05395cb0d819ee9e7
|
||||
checksum: 10/c1f6a55359df4e8eac83ef8bfa21a80515a05ab7a803bc28c8535c2d6e73510c8764a35adc0aa1605138753bdb221b37c0af04e4367a6f19f4c45b1c52908fa7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/types@npm:1.3.16":
|
||||
version: 1.3.16
|
||||
resolution: "@rsdoctor/types@npm:1.3.16"
|
||||
"@rsdoctor/types@npm:1.3.15":
|
||||
version: 1.3.15
|
||||
resolution: "@rsdoctor/types@npm:1.3.15"
|
||||
dependencies:
|
||||
"@types/connect": "npm:3.4.38"
|
||||
"@types/estree": "npm:1.0.5"
|
||||
@@ -3966,22 +3982,22 @@ __metadata:
|
||||
optional: true
|
||||
webpack:
|
||||
optional: true
|
||||
checksum: 10/ad9488d1a24097e8f51148f768b0ccc9952668a745be8b2f8f26ca566998c7a8af70e7a65f77efe1b0da72049be3572dd33e133651b627c87eaf33c84b502f83
|
||||
checksum: 10/cf97aa38bab865c0fc1c96ce695ed9295095b86d744c2f38fc7ef2d9c50850a32bfe49b08d489665878bf2d0e18a03feea866b092942cac092289fcd540362f5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/utils@npm:1.3.16":
|
||||
version: 1.3.16
|
||||
resolution: "@rsdoctor/utils@npm:1.3.16"
|
||||
"@rsdoctor/utils@npm:1.3.15":
|
||||
version: 1.3.15
|
||||
resolution: "@rsdoctor/utils@npm:1.3.15"
|
||||
dependencies:
|
||||
"@babel/code-frame": "npm:7.26.2"
|
||||
"@rsdoctor/types": "npm:1.3.16"
|
||||
"@rsdoctor/types": "npm:1.3.15"
|
||||
"@types/estree": "npm:1.0.5"
|
||||
acorn: "npm:^8.10.0"
|
||||
acorn-import-attributes: "npm:^1.9.5"
|
||||
acorn-walk: "npm:8.3.4"
|
||||
deep-eql: "npm:4.1.4"
|
||||
envinfo: "npm:7.21.0"
|
||||
envinfo: "npm:7.19.0"
|
||||
fs-extra: "npm:^11.1.1"
|
||||
get-port: "npm:5.1.1"
|
||||
json-stream-stringify: "npm:3.0.1"
|
||||
@@ -3989,7 +4005,7 @@ __metadata:
|
||||
picocolors: "npm:^1.1.1"
|
||||
rslog: "npm:^1.2.11"
|
||||
strip-ansi: "npm:^6.0.1"
|
||||
checksum: 10/81a235130845ad4eaf90afce981ec974ce7f25170015779127a21d06bffe667cc46e44d111b1140a283eef921051151cafd80a898ad916519772f510f3805579
|
||||
checksum: 10/086341603606eb76bb291582d00d6e4741aa065a6cad6bff04ec679fa05db4897bb821bdde3f6bed2961b3798b2fc17ce0d4b827d0955fb307c4b0f800459880
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4439,10 +4455,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/chromecast-caf-receiver@npm:6.0.25":
|
||||
version: 6.0.25
|
||||
resolution: "@types/chromecast-caf-receiver@npm:6.0.25"
|
||||
checksum: 10/b3460609b6a0357949a078de59b89030181eed59b567cdc2935d99627707bfc922429fddfbe3a4deb62359018f14f163e7c81211125d303c3943203ed3d45785
|
||||
"@types/chromecast-caf-receiver@npm:6.0.22":
|
||||
version: 6.0.22
|
||||
resolution: "@types/chromecast-caf-receiver@npm:6.0.22"
|
||||
checksum: 10/6c51cb52527776ddfa187a261b88184c98bdd61c129dd8719cba213894d565cf69073734d6473696ffd60a768f6fb5a3fe9932693f43174fbc5e7af201db8a90
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4929,105 +4945,105 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/eslint-plugin@npm:8.50.0":
|
||||
version: 8.50.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.50.0"
|
||||
"@typescript-eslint/eslint-plugin@npm:8.49.0":
|
||||
version: 8.49.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.49.0"
|
||||
dependencies:
|
||||
"@eslint-community/regexpp": "npm:^4.10.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.50.0"
|
||||
"@typescript-eslint/type-utils": "npm:8.50.0"
|
||||
"@typescript-eslint/utils": "npm:8.50.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.50.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.49.0"
|
||||
"@typescript-eslint/type-utils": "npm:8.49.0"
|
||||
"@typescript-eslint/utils": "npm:8.49.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.49.0"
|
||||
ignore: "npm:^7.0.0"
|
||||
natural-compare: "npm:^1.4.0"
|
||||
ts-api-utils: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
"@typescript-eslint/parser": ^8.50.0
|
||||
"@typescript-eslint/parser": ^8.49.0
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/e35e7857c9e88aa075307c1775ebf009956cd226e8f76df9144799b32120274fd4470408777e60b84bdedc675cf863f108abda5fb6f9be61d06d0316bf427b57
|
||||
checksum: 10/f51c45c7e3fe367a9855742229d1893b3df61aa725a199ee87fa81c7fc80128a0ba6971d39192be023d08262f320688c3483821d139024911cc9e88dbcd58c6b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/parser@npm:8.50.0":
|
||||
version: 8.50.0
|
||||
resolution: "@typescript-eslint/parser@npm:8.50.0"
|
||||
"@typescript-eslint/parser@npm:8.49.0":
|
||||
version: 8.49.0
|
||||
resolution: "@typescript-eslint/parser@npm:8.49.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager": "npm:8.50.0"
|
||||
"@typescript-eslint/types": "npm:8.50.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.50.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.50.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.49.0"
|
||||
"@typescript-eslint/types": "npm:8.49.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.49.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.49.0"
|
||||
debug: "npm:^4.3.4"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/4c3a1011f9122def7b184cdbbc93f2be4167adda787085edb9cee9493957625b0611c0000b8e6d9c6003a0ad2945f2e7db56b31649f03e4c58a59c4d310234b6
|
||||
checksum: 10/b41706ff8b7bd65ca197cd334493c1062cda6cbbef23221b4937a6aed3c04dc74c3f58afb2cbb463f42980bae41289216cb3174bd74ead7a504878277d4ee3a6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/project-service@npm:8.50.0":
|
||||
version: 8.50.0
|
||||
resolution: "@typescript-eslint/project-service@npm:8.50.0"
|
||||
"@typescript-eslint/project-service@npm:8.49.0":
|
||||
version: 8.49.0
|
||||
resolution: "@typescript-eslint/project-service@npm:8.49.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.50.0"
|
||||
"@typescript-eslint/types": "npm:^8.50.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.49.0"
|
||||
"@typescript-eslint/types": "npm:^8.49.0"
|
||||
debug: "npm:^4.3.4"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/d503d270bf62750d96caafa1635297b4be35176361f6f27b30f25ca5b476145d74c914da3ec8f5fc88ee646aa2d809becbe509a81650fd2b2a74179956371329
|
||||
checksum: 10/ce6ed14247b2fbbd108e1accbf050d0585932a14bb9424ef0bc4f1e421a054c4da16caedb3469e3f1bacf0e5d6de78291aa633321ff6a7c79e5767d1c6d4ea51
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/scope-manager@npm:8.50.0":
|
||||
version: 8.50.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.50.0"
|
||||
"@typescript-eslint/scope-manager@npm:8.49.0":
|
||||
version: 8.49.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.49.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.50.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.50.0"
|
||||
checksum: 10/db4e33efacbd4f18a19a6bd9ac1ac9d6857f67a1d076a4d7f8569a4089ae676ab2a85667ae0bec8e21a32ff5398a5358af3ddcb83212c014165ee54aca6b0f13
|
||||
"@typescript-eslint/types": "npm:8.49.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.49.0"
|
||||
checksum: 10/ef13c9f7842efd5141798f4cc02ba46763e1bc0154ba804df143dfdf84ee2ee33de2932bef286c3e5a4806bf142b0327f37061d9c50153f31401c4f5e82086ce
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.50.0, @typescript-eslint/tsconfig-utils@npm:^8.50.0":
|
||||
version: 8.50.0
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.50.0"
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.49.0, @typescript-eslint/tsconfig-utils@npm:^8.49.0":
|
||||
version: 8.49.0
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.49.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/d43caece5c42db7561bcd49c0d3d829abd3ba8faf2d20eb57c60ddd0248afe6df76a5f1f11ec9d6b84153e0bb9e4f92929d6fd83789b520bc64fc51a69b68ba2
|
||||
checksum: 10/296f8b078ecc5f954a6834f7b044ee4786784bae60a6d42037caad34b4602bdb2c2f0a18f36faee47f59c70727ac2abac264a225ab305bc80cfb21cd2ef9f852
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/type-utils@npm:8.50.0":
|
||||
version: 8.50.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.50.0"
|
||||
"@typescript-eslint/type-utils@npm:8.49.0":
|
||||
version: 8.49.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.49.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.50.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.50.0"
|
||||
"@typescript-eslint/utils": "npm:8.50.0"
|
||||
"@typescript-eslint/types": "npm:8.49.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.49.0"
|
||||
"@typescript-eslint/utils": "npm:8.49.0"
|
||||
debug: "npm:^4.3.4"
|
||||
ts-api-utils: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/60ea2500f8dcf1093ec49ffaa3063fb207c482f8fd88b8cde737e10aa67cf2bd49e5052623a6f4b4f7e1569c282b8eadb44ed3f34074ed56c1bd50167d00f61a
|
||||
checksum: 10/d4e88841edd5ddb6fcdb00011e2f67daa792dad01d7e2bd7a7c04fe3f3ffe59680fd707ccad27332003f5c469e89f16961cb1dfbd2e1b214ce3625378397f825
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:8.50.0, @typescript-eslint/types@npm:^8.50.0":
|
||||
version: 8.50.0
|
||||
resolution: "@typescript-eslint/types@npm:8.50.0"
|
||||
checksum: 10/110be118027f64741b6c614fb1f7b8b62325019a68bf9f734842eb7c36d7c03722b2b50d574ee28b554906a015812768c018428de9b387b998b7129115de9f4a
|
||||
"@typescript-eslint/types@npm:8.49.0, @typescript-eslint/types@npm:^8.49.0":
|
||||
version: 8.49.0
|
||||
resolution: "@typescript-eslint/types@npm:8.49.0"
|
||||
checksum: 10/40efafd393d9a3343a9e4bd127c6d5a919f34088227a3d0d6021b603d44f9c0403ad93d8c832959f49b71dfb4603721600363060d3a8f3637ac3fb5d6981ece7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:8.50.0":
|
||||
version: 8.50.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.50.0"
|
||||
"@typescript-eslint/typescript-estree@npm:8.49.0":
|
||||
version: 8.49.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.49.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service": "npm:8.50.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.50.0"
|
||||
"@typescript-eslint/types": "npm:8.50.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.50.0"
|
||||
"@typescript-eslint/project-service": "npm:8.49.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.49.0"
|
||||
"@typescript-eslint/types": "npm:8.49.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.49.0"
|
||||
debug: "npm:^4.3.4"
|
||||
minimatch: "npm:^9.0.4"
|
||||
semver: "npm:^7.6.0"
|
||||
@@ -5035,32 +5051,210 @@ __metadata:
|
||||
ts-api-utils: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/824df4f062f8d3c512bc6eb70b4cc84cd72329f312c8c10628d66771efc74b2d5e2f2096a52438e0decc231fedf81ade9f07fe8cfb3b19325750256ea61c11c0
|
||||
checksum: 10/f84280d8068732d643c101dfb1018db23c1f142b3991e8a5a8e6d6813b79dc00dda96f7f08f256f20cf7efc14655bdc241a1eea406b56eb01156847a91ee621d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/utils@npm:8.50.0":
|
||||
version: 8.50.0
|
||||
resolution: "@typescript-eslint/utils@npm:8.50.0"
|
||||
"@typescript-eslint/utils@npm:8.49.0":
|
||||
version: 8.49.0
|
||||
resolution: "@typescript-eslint/utils@npm:8.49.0"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": "npm:^4.7.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.50.0"
|
||||
"@typescript-eslint/types": "npm:8.50.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.50.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.49.0"
|
||||
"@typescript-eslint/types": "npm:8.49.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.49.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/796899c1b7d7559734d50a64bf1f78a7e10325f66af10749410e037e299c9bce583cc04a3c957819edadbfd5726d643dc44ca3d6b4767d99aebeb0e8ed657d53
|
||||
checksum: 10/3b5fe5184af4d7379498610ed71fa38476f4133b6b4a761b267ee1b103ab422e3082d071fed138d42e5c18b445a29dba496df74a88d0690053c58adc881ffe6e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:8.50.0":
|
||||
version: 8.50.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.50.0"
|
||||
"@typescript-eslint/visitor-keys@npm:8.49.0":
|
||||
version: 8.49.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.49.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.50.0"
|
||||
"@typescript-eslint/types": "npm:8.49.0"
|
||||
eslint-visitor-keys: "npm:^4.2.1"
|
||||
checksum: 10/45703f0899a5627dabb22d7fbc83d1d771e8238bf8ffe712dfaf255140ba248a019829824396420e239737cde897723971d62c48d321aa59e5aae9f186ff87cd
|
||||
checksum: 10/f778c588f49174f21866c59f8d46d2c0cad0d68b7acc87982e279c28d63df9f229fafdc13f36932b45fb8151aaeb1f8f70b1a00c83e7dae3782121ec3e1dac68
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vaadin/a11y-base@npm:~24.9.6":
|
||||
version: 24.9.6
|
||||
resolution: "@vaadin/a11y-base@npm:24.9.6"
|
||||
dependencies:
|
||||
"@open-wc/dedupe-mixin": "npm:^1.3.0"
|
||||
"@polymer/polymer": "npm:^3.0.0"
|
||||
"@vaadin/component-base": "npm:~24.9.6"
|
||||
lit: "npm:^3.0.0"
|
||||
checksum: 10/dc863b46fc68c73c418e686d364858fd8bb3291ccf0b41ae52d13933357a7a1de0eb9f345e05a4abc9c9e363a73dc79326b29d7123d0a88ff075e221ef759d90
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vaadin/combo-box@npm:24.9.6":
|
||||
version: 24.9.6
|
||||
resolution: "@vaadin/combo-box@npm:24.9.6"
|
||||
dependencies:
|
||||
"@open-wc/dedupe-mixin": "npm:^1.3.0"
|
||||
"@polymer/polymer": "npm:^3.0.0"
|
||||
"@vaadin/a11y-base": "npm:~24.9.6"
|
||||
"@vaadin/component-base": "npm:~24.9.6"
|
||||
"@vaadin/field-base": "npm:~24.9.6"
|
||||
"@vaadin/input-container": "npm:~24.9.6"
|
||||
"@vaadin/item": "npm:~24.9.6"
|
||||
"@vaadin/lit-renderer": "npm:~24.9.6"
|
||||
"@vaadin/overlay": "npm:~24.9.6"
|
||||
"@vaadin/vaadin-lumo-styles": "npm:~24.9.6"
|
||||
"@vaadin/vaadin-material-styles": "npm:~24.9.6"
|
||||
"@vaadin/vaadin-themable-mixin": "npm:~24.9.6"
|
||||
lit: "npm:^3.0.0"
|
||||
checksum: 10/40fa537341754a5b257b94cc3d615ea0f1b2593e402fe3876315d088efa7d0954bfbb8a055d35cf1e25067eb147a4613360dd21704b5bab3cc53cbffb23d7534
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vaadin/component-base@npm:~24.9.6":
|
||||
version: 24.9.6
|
||||
resolution: "@vaadin/component-base@npm:24.9.6"
|
||||
dependencies:
|
||||
"@open-wc/dedupe-mixin": "npm:^1.3.0"
|
||||
"@polymer/polymer": "npm:^3.0.0"
|
||||
"@vaadin/vaadin-development-mode-detector": "npm:^2.0.0"
|
||||
"@vaadin/vaadin-usage-statistics": "npm:^2.1.0"
|
||||
lit: "npm:^3.0.0"
|
||||
checksum: 10/87d3a4992cde43d13654eb4941d606c7f7550b6655669ad08803c15528ad9fbf760c61902ae291a9e7522ecf82df3810a44fee4e7741fed571a4907143c7b7c7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vaadin/field-base@npm:~24.9.6":
|
||||
version: 24.9.6
|
||||
resolution: "@vaadin/field-base@npm:24.9.6"
|
||||
dependencies:
|
||||
"@open-wc/dedupe-mixin": "npm:^1.3.0"
|
||||
"@polymer/polymer": "npm:^3.0.0"
|
||||
"@vaadin/a11y-base": "npm:~24.9.6"
|
||||
"@vaadin/component-base": "npm:~24.9.6"
|
||||
lit: "npm:^3.0.0"
|
||||
checksum: 10/8aada69cca51e48f1f589f9549e45cae7244b2fe9b099fe458d52e52261ebfdfb9929d21665dbaf54a221dc16e7ad308a515a7f29be18fd9e0c1af062fe60b7d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vaadin/icon@npm:~24.9.6":
|
||||
version: 24.9.6
|
||||
resolution: "@vaadin/icon@npm:24.9.6"
|
||||
dependencies:
|
||||
"@open-wc/dedupe-mixin": "npm:^1.3.0"
|
||||
"@polymer/polymer": "npm:^3.0.0"
|
||||
"@vaadin/component-base": "npm:~24.9.6"
|
||||
"@vaadin/vaadin-lumo-styles": "npm:~24.9.6"
|
||||
"@vaadin/vaadin-themable-mixin": "npm:~24.9.6"
|
||||
lit: "npm:^3.0.0"
|
||||
checksum: 10/e94c3c27eb0c8bfdacc3935e9dd7f5230656759d21b1a17bd74c47ea803bbe105feea9e43d4ed65d2c4f4408355c981ee1c2bdb99c70b3fe71636f459c0c1802
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vaadin/input-container@npm:~24.9.6":
|
||||
version: 24.9.6
|
||||
resolution: "@vaadin/input-container@npm:24.9.6"
|
||||
dependencies:
|
||||
"@polymer/polymer": "npm:^3.0.0"
|
||||
"@vaadin/component-base": "npm:~24.9.6"
|
||||
"@vaadin/vaadin-lumo-styles": "npm:~24.9.6"
|
||||
"@vaadin/vaadin-material-styles": "npm:~24.9.6"
|
||||
"@vaadin/vaadin-themable-mixin": "npm:~24.9.6"
|
||||
lit: "npm:^3.0.0"
|
||||
checksum: 10/ef32e139af061404f29ed1951635670b41ef4942cd95e992783682f0d9f9af0a3c40c40207845c647560b482297f7136bf3bd05534c22f3275cc79ad7c2aec24
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vaadin/item@npm:~24.9.6":
|
||||
version: 24.9.6
|
||||
resolution: "@vaadin/item@npm:24.9.6"
|
||||
dependencies:
|
||||
"@open-wc/dedupe-mixin": "npm:^1.3.0"
|
||||
"@polymer/polymer": "npm:^3.0.0"
|
||||
"@vaadin/a11y-base": "npm:~24.9.6"
|
||||
"@vaadin/component-base": "npm:~24.9.6"
|
||||
"@vaadin/vaadin-lumo-styles": "npm:~24.9.6"
|
||||
"@vaadin/vaadin-material-styles": "npm:~24.9.6"
|
||||
"@vaadin/vaadin-themable-mixin": "npm:~24.9.6"
|
||||
lit: "npm:^3.0.0"
|
||||
checksum: 10/9721cfe377d0ecc7b5895ac58c71c934108fc5009cbbff9eb56404353071a459e6db0b413f5f11ad56f6aab89ed103bfcf8dad55b1e04697848331ce85a42ab6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vaadin/lit-renderer@npm:~24.9.6":
|
||||
version: 24.9.6
|
||||
resolution: "@vaadin/lit-renderer@npm:24.9.6"
|
||||
dependencies:
|
||||
lit: "npm:^3.0.0"
|
||||
checksum: 10/df0a0dfd9622c31425bba0c8f7d8eb869f71543983627732a46ef7a3619c7101fb7b04bc045a991179dbccfdea20243cbdf8048ad3d2722a364b879cd3f3c23d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vaadin/overlay@npm:~24.9.6":
|
||||
version: 24.9.6
|
||||
resolution: "@vaadin/overlay@npm:24.9.6"
|
||||
dependencies:
|
||||
"@open-wc/dedupe-mixin": "npm:^1.3.0"
|
||||
"@polymer/polymer": "npm:^3.0.0"
|
||||
"@vaadin/a11y-base": "npm:~24.9.6"
|
||||
"@vaadin/component-base": "npm:~24.9.6"
|
||||
"@vaadin/vaadin-lumo-styles": "npm:~24.9.6"
|
||||
"@vaadin/vaadin-material-styles": "npm:~24.9.6"
|
||||
"@vaadin/vaadin-themable-mixin": "npm:~24.9.6"
|
||||
lit: "npm:^3.0.0"
|
||||
checksum: 10/11c3a04e9ddb89b034a5896a54644285cbbc4431fc2219701bd582f01543979b25f0462d78bcaf7cac38ab0218c2b4325cc60042a7aa78b873493fe9e7d570a1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vaadin/vaadin-development-mode-detector@npm:^2.0.0":
|
||||
version: 2.0.7
|
||||
resolution: "@vaadin/vaadin-development-mode-detector@npm:2.0.7"
|
||||
checksum: 10/9b341148d1af8371e0da20dbbec6e5da36fb666001db5dde08e7555065e260a7a68ff62d5a20ba95e8019fa392c7b74aa20031488a836c4aa369aa35c677f3a1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vaadin/vaadin-lumo-styles@npm:~24.9.6":
|
||||
version: 24.9.6
|
||||
resolution: "@vaadin/vaadin-lumo-styles@npm:24.9.6"
|
||||
dependencies:
|
||||
"@polymer/polymer": "npm:^3.0.0"
|
||||
"@vaadin/component-base": "npm:~24.9.6"
|
||||
"@vaadin/icon": "npm:~24.9.6"
|
||||
"@vaadin/vaadin-themable-mixin": "npm:~24.9.6"
|
||||
checksum: 10/f2d89f7109ce62354ba1b69f878699ae4b20154abf9ab8d6ade8a496ac2ea6d878109b1a67a453109b4fe7950808b2e105bbb637672ffebd35d89080fb7f922b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vaadin/vaadin-material-styles@npm:~24.9.6":
|
||||
version: 24.9.6
|
||||
resolution: "@vaadin/vaadin-material-styles@npm:24.9.6"
|
||||
dependencies:
|
||||
"@polymer/polymer": "npm:^3.0.0"
|
||||
"@vaadin/component-base": "npm:~24.9.6"
|
||||
"@vaadin/vaadin-themable-mixin": "npm:~24.9.6"
|
||||
checksum: 10/21a07888adb0545280e4e43dbd4450d4bcb1e4378380ccb744c3d8e48e3ff0e49ee9b9423ffeaf5cd10a470a54d3a1eb6d8af34913b03e8a5bcc52c350cc1564
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vaadin/vaadin-themable-mixin@npm:24.9.6, @vaadin/vaadin-themable-mixin@npm:~24.9.6":
|
||||
version: 24.9.6
|
||||
resolution: "@vaadin/vaadin-themable-mixin@npm:24.9.6"
|
||||
dependencies:
|
||||
"@open-wc/dedupe-mixin": "npm:^1.3.0"
|
||||
lit: "npm:^3.0.0"
|
||||
style-observer: "npm:^0.0.8"
|
||||
checksum: 10/e08dcc82495caa6a6f031085ef1018cb0835c4ee73dc8bd9d67ba2cdb5e33576fd184afddafbec8412c355058a386da868a925cf97fff949b0444ae37dee3afa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vaadin/vaadin-usage-statistics@npm:^2.1.0":
|
||||
version: 2.1.3
|
||||
resolution: "@vaadin/vaadin-usage-statistics@npm:2.1.3"
|
||||
dependencies:
|
||||
"@vaadin/vaadin-development-mode-detector": "npm:^2.0.0"
|
||||
checksum: 10/2210c76ac649b04b5c3f30c4a04ca01e18de7d69996a41b2499588588b7e56909749dc47f43871b28b046f907e7e01f939f2a14933c6198663afab352948b21f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5313,6 +5507,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@webcomponents/shadycss@npm:^1.9.1":
|
||||
version: 1.11.2
|
||||
resolution: "@webcomponents/shadycss@npm:1.11.2"
|
||||
checksum: 10/fa8e1ff9315e45545f7af7b2237386315cfc2895e53a2e489bc74cd33573a8d474fe7c68c7c35604c371d791cbf6a5ea19105ff2654906abd8a299e87c821942
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@webcomponents/webcomponentsjs@npm:2.8.0":
|
||||
version: 2.8.0
|
||||
resolution: "@webcomponents/webcomponentsjs@npm:2.8.0"
|
||||
@@ -7370,12 +7571,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"envinfo@npm:7.21.0":
|
||||
version: 7.21.0
|
||||
resolution: "envinfo@npm:7.21.0"
|
||||
"envinfo@npm:7.19.0":
|
||||
version: 7.19.0
|
||||
resolution: "envinfo@npm:7.19.0"
|
||||
bin:
|
||||
envinfo: dist/cli.js
|
||||
checksum: 10/2469a72802ded4e43c007dcd1c5dd44d8049b7d18276874dcc3f3f14a54bc72806fa35e82760974ca1442d82f5f9df3651048204e72791f81bcdd5f07422a561
|
||||
checksum: 10/133ea6a55e4a3b4fe4c06d3d5f6c97402b39ae4eb5675254d166c6a82a0da42adea92bdc0aceea2d479d1eabdcbbd0a6ab68bb80760181f578adbe83aed5f9b9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9041,7 +9242,7 @@ __metadata:
|
||||
"@octokit/plugin-retry": "npm:8.0.3"
|
||||
"@octokit/rest": "npm:22.0.1"
|
||||
"@replit/codemirror-indentation-markers": "npm:6.5.3"
|
||||
"@rsdoctor/rspack-plugin": "npm:1.3.16"
|
||||
"@rsdoctor/rspack-plugin": "npm:1.3.15"
|
||||
"@rspack/core": "npm:1.6.7"
|
||||
"@rspack/dev-server": "npm:1.1.4"
|
||||
"@swc/helpers": "npm:0.5.17"
|
||||
@@ -9049,7 +9250,7 @@ __metadata:
|
||||
"@tsparticles/engine": "npm:3.9.1"
|
||||
"@tsparticles/preset-links": "npm:3.2.0"
|
||||
"@types/babel__plugin-transform-runtime": "npm:7.9.5"
|
||||
"@types/chromecast-caf-receiver": "npm:6.0.25"
|
||||
"@types/chromecast-caf-receiver": "npm:6.0.22"
|
||||
"@types/chromecast-caf-sender": "npm:1.0.11"
|
||||
"@types/color-name": "npm:2.0.0"
|
||||
"@types/culori": "npm:4.0.1"
|
||||
@@ -9066,6 +9267,8 @@ __metadata:
|
||||
"@types/tar": "npm:6.1.13"
|
||||
"@types/ua-parser-js": "npm:0.7.39"
|
||||
"@types/webspeechapi": "npm:0.0.29"
|
||||
"@vaadin/combo-box": "npm:24.9.6"
|
||||
"@vaadin/vaadin-themable-mixin": "npm:24.9.6"
|
||||
"@vibrant/color": "npm:4.0.0"
|
||||
"@vitest/coverage-v8": "npm:4.0.15"
|
||||
"@vue/web-component-wrapper": "npm:1.3.0"
|
||||
@@ -9149,9 +9352,9 @@ __metadata:
|
||||
tinykeys: "npm:3.0.0"
|
||||
ts-lit-plugin: "npm:2.0.2"
|
||||
typescript: "npm:5.9.3"
|
||||
typescript-eslint: "npm:8.50.0"
|
||||
typescript-eslint: "npm:8.49.0"
|
||||
ua-parser-js: "npm:2.0.7"
|
||||
vite-tsconfig-paths: "npm:6.0.1"
|
||||
vite-tsconfig-paths: "npm:5.1.4"
|
||||
vitest: "npm:4.0.15"
|
||||
vue: "npm:2.7.16"
|
||||
vue2-daterange-picker: "npm:0.6.8"
|
||||
@@ -13548,6 +13751,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"style-observer@npm:^0.0.8":
|
||||
version: 0.0.8
|
||||
resolution: "style-observer@npm:0.0.8"
|
||||
checksum: 10/9c72ee12c61d48f64622a625ebff9bc4df009877e7ed9b26cec08e8159f6270f428aeea120f0e7c5567c8bbaa701846528fb5339dbdb930e84f2a66d382aeeb6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"superstruct@npm:2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "superstruct@npm:2.0.2"
|
||||
@@ -14103,18 +14313,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript-eslint@npm:8.50.0":
|
||||
version: 8.50.0
|
||||
resolution: "typescript-eslint@npm:8.50.0"
|
||||
"typescript-eslint@npm:8.49.0":
|
||||
version: 8.49.0
|
||||
resolution: "typescript-eslint@npm:8.49.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.50.0"
|
||||
"@typescript-eslint/parser": "npm:8.50.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.50.0"
|
||||
"@typescript-eslint/utils": "npm:8.50.0"
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.49.0"
|
||||
"@typescript-eslint/parser": "npm:8.49.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.49.0"
|
||||
"@typescript-eslint/utils": "npm:8.49.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/85708f0a930c8992e8f70e4e143aabf3a2b9bd981a323a7f6e7ea405f2456f6d1af182264f877ff1fa00db68768fe935f668f4346173a2fbd6d828d7113d8653
|
||||
checksum: 10/face6f4043e00ce1e27e63f8e364c1e098c8f3e50111d139da280e412b67d1d758f626087340007960c70df4829b448e632f244193394fd77bbce60a6aee9d5d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -14488,9 +14698,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vite-tsconfig-paths@npm:6.0.1":
|
||||
version: 6.0.1
|
||||
resolution: "vite-tsconfig-paths@npm:6.0.1"
|
||||
"vite-tsconfig-paths@npm:5.1.4":
|
||||
version: 5.1.4
|
||||
resolution: "vite-tsconfig-paths@npm:5.1.4"
|
||||
dependencies:
|
||||
debug: "npm:^4.1.1"
|
||||
globrex: "npm:^0.1.2"
|
||||
@@ -14500,7 +14710,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
vite:
|
||||
optional: true
|
||||
checksum: 10/7ac6d17efee21be805ed62857269d4e41d939774a8cc698e77145b4312edf996a9536e7a3b37e25486ae77d36fc5ae011378058ff290be39da16e52a47e87da7
|
||||
checksum: 10/b409dbd17829f560021a71dba3e473b9c06dcf5fdc9d630b72c1f787145ec478b38caff1be04868971ac8bdcbf0f5af45eeece23dbc9c59c54b901f867740ae0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user