Add hold and double tap action in the UI for every card that supports it. (#24824)

* Add double tap action to button card UI editor

* Add double tap action to light card UI editor

* Add hold action and double tap action to gauge card UI editor

* Add hold action and double tap action to picture glance card UI editor

* Add hold action and double tap action to picture card UI editor

* Add hold action and double tap action to entity card UI editor

* Add hold action and double tap action to elements
This commit is contained in:
Paul Bottein 2025-03-28 13:12:07 +01:00 committed by GitHub
parent f5bb72f067
commit 0152a79bd5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 397 additions and 118 deletions

View File

@ -78,9 +78,6 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
return { return {
type: "button", type: "button",
tap_action: {
action: "toggle",
},
entity: foundEntities[0] || "", entity: foundEntities[0] || "",
}; };
} }
@ -164,6 +161,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
action: getEntityDefaultButtonAction(config.entity), action: getEntityDefaultButtonAction(config.entity),
}, },
hold_action: { action: "more-info" }, hold_action: { action: "more-info" },
double_tap_action: { action: "none" },
show_icon: true, show_icon: true,
show_name: true, show_name: true,
state_color: true, state_color: true,

View File

@ -46,7 +46,10 @@ export class HuiPictureCard extends LitElement implements LovelaceCard {
throw new Error("Image required"); throw new Error("Image required");
} }
this._config = config; this._config = {
tap_action: { action: "more-info" },
...config,
};
} }
protected shouldUpdate(changedProps: PropertyValues): boolean { protected shouldUpdate(changedProps: PropertyValues): boolean {

View File

@ -6,9 +6,11 @@ import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_elemen
import { computeDomain } from "../../../common/entity/compute_domain"; import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import "../../../components/ha-card"; import "../../../components/ha-card";
import type { CameraEntity } from "../../../data/camera";
import type { ImageEntity } from "../../../data/image"; import type { ImageEntity } from "../../../data/image";
import { computeImageUrl } from "../../../data/image"; import { computeImageUrl } from "../../../data/image";
import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler"; import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
import type { PersonEntity } from "../../../data/person";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive"; import { actionHandler } from "../common/directives/action-handler-directive";
import { findEntities } from "../common/find-entities"; import { findEntities } from "../common/find-entities";
@ -19,8 +21,6 @@ import "../components/hui-image";
import { createEntityNotFoundWarning } from "../components/hui-warning"; import { createEntityNotFoundWarning } from "../components/hui-warning";
import type { LovelaceCard, LovelaceCardEditor } from "../types"; import type { LovelaceCard, LovelaceCardEditor } from "../types";
import type { PictureEntityCardConfig } from "./types"; import type { PictureEntityCardConfig } from "./types";
import type { CameraEntity } from "../../../data/camera";
import type { PersonEntity } from "../../../data/person";
export const STUB_IMAGE = export const STUB_IMAGE =
"https://demo.home-assistant.io/stub_config/bedroom.png"; "https://demo.home-assistant.io/stub_config/bedroom.png";
@ -75,7 +75,12 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
throw new Error("No image source configured"); throw new Error("No image source configured");
} }
this._config = { show_name: true, show_state: true, ...config }; this._config = {
show_name: true,
show_state: true,
tap_action: { action: "more-info" },
...config,
};
} }
protected shouldUpdate(changedProps: PropertyValues): boolean { protected shouldUpdate(changedProps: PropertyValues): boolean {

View File

@ -105,7 +105,7 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
}); });
this._config = { this._config = {
hold_action: { action: "more-info" }, tap_action: { action: "more-info" },
...config, ...config,
}; };
} }

View File

@ -1,12 +1,13 @@
import { mdiGestureTap } from "@mdi/js";
import { html, LitElement, nothing } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { any, assert, literal, object, optional, string } from "superstruct"; import { any, assert, literal, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../../components/ha-form/types"; import type { SchemaUnion } from "../../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../../types"; import type { HomeAssistant } from "../../../../../types";
import "../../../../../components/ha-form/ha-form";
import type { LovelacePictureElementEditor } from "../../../types";
import type { IconElementConfig } from "../../../elements/types"; import type { IconElementConfig } from "../../../elements/types";
import type { LovelacePictureElementEditor } from "../../../types";
import { actionConfigStruct } from "../../structs/action-struct"; import { actionConfigStruct } from "../../structs/action-struct";
const iconElementConfigStruct = object({ const iconElementConfigStruct = object({
@ -25,16 +26,35 @@ const SCHEMA = [
{ name: "title", selector: { text: {} } }, { name: "title", selector: { text: {} } },
{ name: "entity", selector: { entity: {} } }, { name: "entity", selector: { entity: {} } },
{ {
name: "tap_action", name: "interactions",
selector: { type: "expandable",
ui_action: {}, flatten: true,
}, iconPath: mdiGestureTap,
}, schema: [
{ {
name: "hold_action", name: "tap_action",
selector: { selector: {
ui_action: {}, ui_action: {
}, default_action: "more-info",
},
},
},
{
name: "",
type: "optional_actions",
flatten: true,
schema: (["hold_action", "double_tap_action"] as const).map(
(action) => ({
name: action,
selector: {
ui_action: {
default_action: "none" as const,
},
},
})
),
},
],
}, },
{ name: "style", selector: { object: {} } }, { name: "style", selector: { object: {} } },
] as const; ] as const;

View File

@ -1,12 +1,13 @@
import { mdiGestureTap } from "@mdi/js";
import { html, LitElement, nothing } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { any, assert, literal, object, optional, string } from "superstruct"; import { any, assert, literal, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../../components/ha-form/types"; import type { SchemaUnion } from "../../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../../types"; import type { HomeAssistant } from "../../../../../types";
import "../../../../../components/ha-form/ha-form";
import type { LovelacePictureElementEditor } from "../../../types";
import type { ImageElementConfig } from "../../../elements/types"; import type { ImageElementConfig } from "../../../elements/types";
import type { LovelacePictureElementEditor } from "../../../types";
import { actionConfigStruct } from "../../structs/action-struct"; import { actionConfigStruct } from "../../structs/action-struct";
const imageElementConfigStruct = object({ const imageElementConfigStruct = object({
@ -30,16 +31,35 @@ const SCHEMA = [
{ name: "entity", selector: { entity: {} } }, { name: "entity", selector: { entity: {} } },
{ name: "title", selector: { text: {} } }, { name: "title", selector: { text: {} } },
{ {
name: "tap_action", name: "interactions",
selector: { type: "expandable",
ui_action: {}, flatten: true,
}, iconPath: mdiGestureTap,
}, schema: [
{ {
name: "hold_action", name: "tap_action",
selector: { selector: {
ui_action: {}, ui_action: {
}, default_action: "more-info",
},
},
},
{
name: "",
type: "optional_actions",
flatten: true,
schema: (["hold_action", "double_tap_action"] as const).map(
(action) => ({
name: action,
selector: {
ui_action: {
default_action: "none" as const,
},
},
})
),
},
],
}, },
{ name: "image", selector: { image: {} } }, { name: "image", selector: { image: {} } },
{ name: "camera_image", selector: { entity: { domain: "camera" } } }, { name: "camera_image", selector: { entity: { domain: "camera" } } },

View File

@ -1,6 +1,7 @@
import { html, LitElement, nothing } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { any, assert, literal, object, optional, string } from "superstruct"; import { any, assert, literal, object, optional, string } from "superstruct";
import { mdiGestureTap } from "@mdi/js";
import { fireEvent } from "../../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import type { SchemaUnion } from "../../../../../components/ha-form/types"; import type { SchemaUnion } from "../../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../../types"; import type { HomeAssistant } from "../../../../../types";
@ -23,16 +24,35 @@ const SCHEMA = [
{ name: "entity", required: true, selector: { entity: {} } }, { name: "entity", required: true, selector: { entity: {} } },
{ name: "title", selector: { text: {} } }, { name: "title", selector: { text: {} } },
{ {
name: "tap_action", name: "interactions",
selector: { type: "expandable",
ui_action: {}, flatten: true,
}, iconPath: mdiGestureTap,
}, schema: [
{ {
name: "hold_action", name: "tap_action",
selector: { selector: {
ui_action: {}, ui_action: {
}, default_action: "more-info",
},
},
},
{
name: "",
type: "optional_actions",
flatten: true,
schema: (["hold_action", "double_tap_action"] as const).map(
(action) => ({
name: action,
selector: {
ui_action: {
default_action: "none" as const,
},
},
})
),
},
],
}, },
{ name: "style", selector: { object: {} } }, { name: "style", selector: { object: {} } },
] as const; ] as const;

View File

@ -1,3 +1,4 @@
import { mdiGestureTap } from "@mdi/js";
import { html, LitElement, nothing } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { import {
@ -10,11 +11,11 @@ import {
string, string,
} from "superstruct"; } from "superstruct";
import { fireEvent } from "../../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../../components/ha-form/types"; import type { SchemaUnion } from "../../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../../types"; import type { HomeAssistant } from "../../../../../types";
import "../../../../../components/ha-form/ha-form";
import type { LovelacePictureElementEditor } from "../../../types";
import type { StateIconElementConfig } from "../../../elements/types"; import type { StateIconElementConfig } from "../../../elements/types";
import type { LovelacePictureElementEditor } from "../../../types";
import { actionConfigStruct } from "../../structs/action-struct"; import { actionConfigStruct } from "../../structs/action-struct";
const stateIconElementConfigStruct = object({ const stateIconElementConfigStruct = object({
@ -35,16 +36,35 @@ const SCHEMA = [
{ name: "title", selector: { text: {} } }, { name: "title", selector: { text: {} } },
{ name: "state_color", default: true, selector: { boolean: {} } }, { name: "state_color", default: true, selector: { boolean: {} } },
{ {
name: "tap_action", name: "interactions",
selector: { type: "expandable",
ui_action: {}, flatten: true,
}, iconPath: mdiGestureTap,
}, schema: [
{ {
name: "hold_action", name: "tap_action",
selector: { selector: {
ui_action: {}, ui_action: {
}, default_action: "more-info",
},
},
},
{
name: "",
type: "optional_actions",
flatten: true,
schema: (["hold_action", "double_tap_action"] as const).map(
(action) => ({
name: action,
selector: {
ui_action: {
default_action: "none" as const,
},
},
})
),
},
],
}, },
{ name: "style", selector: { object: {} } }, { name: "style", selector: { object: {} } },
] as const; ] as const;

View File

@ -1,12 +1,13 @@
import { mdiGestureTap } from "@mdi/js";
import { html, LitElement, nothing } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { any, assert, literal, object, optional, string } from "superstruct"; import { any, assert, literal, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../../components/ha-form/types"; import type { SchemaUnion } from "../../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../../types"; import type { HomeAssistant } from "../../../../../types";
import "../../../../../components/ha-form/ha-form";
import type { LovelacePictureElementEditor } from "../../../types";
import type { StateLabelElementConfig } from "../../../elements/types"; import type { StateLabelElementConfig } from "../../../elements/types";
import type { LovelacePictureElementEditor } from "../../../types";
import { actionConfigStruct } from "../../structs/action-struct"; import { actionConfigStruct } from "../../structs/action-struct";
const stateLabelElementConfigStruct = object({ const stateLabelElementConfigStruct = object({
@ -35,16 +36,35 @@ const SCHEMA = [
{ name: "suffix", selector: { text: {} } }, { name: "suffix", selector: { text: {} } },
{ name: "title", selector: { text: {} } }, { name: "title", selector: { text: {} } },
{ {
name: "tap_action", name: "interactions",
selector: { type: "expandable",
ui_action: {}, flatten: true,
}, iconPath: mdiGestureTap,
}, schema: [
{ {
name: "hold_action", name: "tap_action",
selector: { selector: {
ui_action: {}, ui_action: {
}, default_action: "more-info",
},
},
},
{
name: "",
type: "optional_actions",
flatten: true,
schema: (["hold_action", "double_tap_action"] as const).map(
(action) => ({
name: action,
selector: {
ui_action: {
default_action: "none" as const,
},
},
})
),
},
],
}, },
{ name: "style", selector: { object: {} } }, { name: "style", selector: { object: {} } },
] as const; ] as const;

View File

@ -1,3 +1,4 @@
import { mdiGestureTap } from "@mdi/js";
import type { CSSResultGroup } from "lit"; import type { CSSResultGroup } from "lit";
import { html, LitElement, nothing } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
@ -28,6 +29,7 @@ const cardConfigStruct = assign(
icon_height: optional(string()), icon_height: optional(string()),
tap_action: optional(actionConfigStruct), tap_action: optional(actionConfigStruct),
hold_action: optional(actionConfigStruct), hold_action: optional(actionConfigStruct),
double_tap_action: optional(actionConfigStruct),
theme: optional(string()), theme: optional(string()),
show_state: optional(boolean()), show_state: optional(boolean()),
}) })
@ -86,20 +88,43 @@ export class HuiButtonCardEditor
], ],
}, },
{ {
name: "tap_action", name: "interactions",
selector: { type: "expandable",
ui_action: { flatten: true,
default_action: getEntityDefaultButtonAction(entityId), iconPath: mdiGestureTap,
schema: [
{
name: "tap_action",
selector: {
ui_action: {
default_action: getEntityDefaultButtonAction(entityId),
},
},
}, },
}, {
}, name: "hold_action",
{ selector: {
name: "hold_action", ui_action: {
selector: { default_action: "more-info",
ui_action: { },
default_action: "more-info", },
}, },
}, {
name: "",
type: "optional_actions",
flatten: true,
schema: [
{
name: "double_tap_action",
selector: {
ui_action: {
default_action: "none",
},
},
},
],
},
],
}, },
] as const satisfies readonly HaFormSchema[] ] as const satisfies readonly HaFormSchema[]
); );

View File

@ -1,3 +1,4 @@
import { mdiGestureTap } from "@mdi/js";
import { html, LitElement, nothing } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
@ -16,14 +17,21 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-form/ha-form"; import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { DEFAULT_MAX, DEFAULT_MIN } from "../../cards/hui-gauge-card";
import type { GaugeCardConfig } from "../../cards/types"; import type { GaugeCardConfig } from "../../cards/types";
import type { UiAction } from "../../components/hui-action-editor";
import type { LovelaceCardEditor } from "../../types"; import type { LovelaceCardEditor } from "../../types";
import { actionConfigStruct } from "../structs/action-struct"; import { actionConfigStruct } from "../structs/action-struct";
import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import { DEFAULT_MIN, DEFAULT_MAX } from "../../cards/hui-gauge-card";
import type { UiAction } from "../../components/hui-action-editor";
const TAP_ACTIONS: UiAction[] = ["navigate", "url", "perform-action", "none"]; const TAP_ACTIONS: UiAction[] = [
"more-info",
"navigate",
"url",
"perform-action",
"assist",
"none",
];
const gaugeSegmentStruct = object({ const gaugeSegmentStruct = object({
from: number(), from: number(),
@ -134,13 +142,37 @@ export class HuiGaugeCardEditor
] as const) ] as const)
: []), : []),
{ {
name: "tap_action", name: "interactions",
selector: { type: "expandable",
ui_action: { flatten: true,
actions: TAP_ACTIONS, iconPath: mdiGestureTap,
default_action: "more-info", schema: [
{
name: "tap_action",
selector: {
ui_action: {
actions: TAP_ACTIONS,
default_action: "more-info",
},
},
}, },
}, {
name: "",
type: "optional_actions",
flatten: true,
schema: (["hold_action", "double_tap_action"] as const).map(
(action) => ({
name: action,
selector: {
ui_action: {
actions: TAP_ACTIONS,
default_action: "none" as const,
},
},
})
),
},
],
}, },
] as const ] as const
); );
@ -231,7 +263,13 @@ export class HuiGaugeCardEditor
return this.hass!.localize( return this.hass!.localize(
"ui.panel.lovelace.editor.card.generic.unit" "ui.panel.lovelace.editor.card.generic.unit"
); );
case "interactions":
return this.hass!.localize(
"ui.panel.lovelace.editor.card.generic.interactions"
);
case "tap_action": case "tap_action":
case "hold_action":
case "double_tap_action":
return `${this.hass!.localize( return `${this.hass!.localize(
`ui.panel.lovelace.editor.card.generic.${schema.name}` `ui.panel.lovelace.editor.card.generic.${schema.name}`
)} (${this.hass!.localize( )} (${this.hass!.localize(

View File

@ -2,6 +2,7 @@ import type { CSSResultGroup } from "lit";
import { html, LitElement, nothing } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { assert, assign, object, optional, string } from "superstruct"; import { assert, assign, object, optional, string } from "superstruct";
import { mdiGestureTap } from "@mdi/js";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-form/ha-form"; import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { SchemaUnion } from "../../../../components/ha-form/types";
@ -19,6 +20,7 @@ const cardConfigStruct = assign(
entity: optional(string()), entity: optional(string()),
theme: optional(string()), theme: optional(string()),
icon: optional(string()), icon: optional(string()),
tap_action: optional(actionConfigStruct),
hold_action: optional(actionConfigStruct), hold_action: optional(actionConfigStruct),
double_tap_action: optional(actionConfigStruct), double_tap_action: optional(actionConfigStruct),
}) })
@ -48,12 +50,43 @@ const SCHEMA = [
}, },
{ name: "theme", selector: { theme: {} } }, { name: "theme", selector: { theme: {} } },
{ {
name: "hold_action", name: "interactions",
selector: { ui_action: {} }, type: "expandable",
}, flatten: true,
{ iconPath: mdiGestureTap,
name: "double_tap_action", schema: [
selector: { ui_action: {} }, {
name: "tap_action",
selector: {
ui_action: {
default_action: "toggle",
},
},
},
{
name: "hold_action",
selector: {
ui_action: {
default_action: "more-info",
},
},
},
{
name: "",
type: "optional_actions",
flatten: true,
schema: [
{
name: "double_tap_action",
selector: {
ui_action: {
default_action: "none",
},
},
},
],
},
],
}, },
] as const; ] as const;

View File

@ -1,3 +1,4 @@
import { mdiGestureTap } from "@mdi/js";
import { html, LitElement, nothing } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { assert, assign, object, optional, string } from "superstruct"; import { assert, assign, object, optional, string } from "superstruct";
@ -18,6 +19,7 @@ const cardConfigStruct = assign(
image_entity: optional(string()), image_entity: optional(string()),
tap_action: optional(actionConfigStruct), tap_action: optional(actionConfigStruct),
hold_action: optional(actionConfigStruct), hold_action: optional(actionConfigStruct),
double_tap_action: optional(actionConfigStruct),
theme: optional(string()), theme: optional(string()),
alt_text: optional(string()), alt_text: optional(string()),
}) })
@ -32,12 +34,35 @@ const SCHEMA = [
{ name: "alt_text", selector: { text: {} } }, { name: "alt_text", selector: { text: {} } },
{ name: "theme", selector: { theme: {} } }, { name: "theme", selector: { theme: {} } },
{ {
name: "tap_action", name: "interactions",
selector: { ui_action: {} }, type: "expandable",
}, flatten: true,
{ iconPath: mdiGestureTap,
name: "hold_action", schema: [
selector: { ui_action: {} }, {
name: "tap_action",
selector: {
ui_action: {
default_action: "more-info",
},
},
},
{
name: "",
type: "optional_actions",
flatten: true,
schema: (["hold_action", "double_tap_action"] as const).map(
(action) => ({
name: action,
selector: {
ui_action: {
default_action: "none" as const,
},
},
})
),
},
],
}, },
] as const; ] as const;

View File

@ -1,18 +1,19 @@
import { mdiGestureTap } from "@mdi/js";
import type { CSSResultGroup } from "lit"; import type { CSSResultGroup } from "lit";
import { html, LitElement, nothing } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { assert, assign, boolean, object, optional, string } from "superstruct"; import { assert, assign, boolean, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { computeDomain } from "../../../../common/entity/compute_domain";
import "../../../../components/ha-form/ha-form"; import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { STUB_IMAGE } from "../../cards/hui-picture-entity-card";
import type { PictureEntityCardConfig } from "../../cards/types"; import type { PictureEntityCardConfig } from "../../cards/types";
import type { LovelaceCardEditor } from "../../types"; import type { LovelaceCardEditor } from "../../types";
import { actionConfigStruct } from "../structs/action-struct"; import { actionConfigStruct } from "../structs/action-struct";
import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { computeDomain } from "../../../../common/entity/compute_domain";
import { STUB_IMAGE } from "../../cards/hui-picture-entity-card";
const cardConfigStruct = assign( const cardConfigStruct = assign(
baseLovelaceCardConfig, baseLovelaceCardConfig,
@ -25,6 +26,7 @@ const cardConfigStruct = assign(
aspect_ratio: optional(string()), aspect_ratio: optional(string()),
tap_action: optional(actionConfigStruct), tap_action: optional(actionConfigStruct),
hold_action: optional(actionConfigStruct), hold_action: optional(actionConfigStruct),
double_tap_action: optional(actionConfigStruct),
show_name: optional(boolean()), show_name: optional(boolean()),
show_state: optional(boolean()), show_state: optional(boolean()),
theme: optional(string()), theme: optional(string()),
@ -64,12 +66,35 @@ const SCHEMA = [
}, },
{ name: "theme", selector: { theme: {} } }, { name: "theme", selector: { theme: {} } },
{ {
name: "tap_action", name: "interactions",
selector: { ui_action: {} }, type: "expandable",
}, flatten: true,
{ iconPath: mdiGestureTap,
name: "hold_action", schema: [
selector: { ui_action: {} }, {
name: "tap_action",
selector: {
ui_action: {
default_action: "more-info",
},
},
},
{
name: "",
type: "optional_actions",
flatten: true,
schema: (["hold_action", "double_tap_action"] as const).map(
(action) => ({
name: action,
selector: {
ui_action: {
default_action: "none" as const,
},
},
})
),
},
],
}, },
] as const; ] as const;
@ -132,6 +157,7 @@ export class HuiPictureEntityCardEditor
case "theme": case "theme":
case "tap_action": case "tap_action":
case "hold_action": case "hold_action":
case "double_tap_action":
return `${this.hass!.localize( return `${this.hass!.localize(
`ui.panel.lovelace.editor.card.generic.${schema.name}` `ui.panel.lovelace.editor.card.generic.${schema.name}`
)} (${this.hass!.localize( )} (${this.hass!.localize(

View File

@ -2,6 +2,7 @@ import type { CSSResultGroup } from "lit";
import { html, LitElement, nothing } from "lit"; import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { array, assert, assign, object, optional, string } from "superstruct"; import { array, assert, assign, object, optional, string } from "superstruct";
import { mdiGestureTap } from "@mdi/js";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-form/ha-form"; import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { SchemaUnion } from "../../../../components/ha-form/types";
@ -29,6 +30,7 @@ const cardConfigStruct = assign(
aspect_ratio: optional(string()), aspect_ratio: optional(string()),
tap_action: optional(actionConfigStruct), tap_action: optional(actionConfigStruct),
hold_action: optional(actionConfigStruct), hold_action: optional(actionConfigStruct),
double_tap_action: optional(actionConfigStruct),
entities: array(entitiesConfigStruct), entities: array(entitiesConfigStruct),
theme: optional(string()), theme: optional(string()),
}) })
@ -56,12 +58,35 @@ const SCHEMA = [
{ name: "entity", selector: { entity: {} } }, { name: "entity", selector: { entity: {} } },
{ name: "theme", selector: { theme: {} } }, { name: "theme", selector: { theme: {} } },
{ {
name: "tap_action", name: "interactions",
selector: { ui_action: {} }, type: "expandable",
}, flatten: true,
{ iconPath: mdiGestureTap,
name: "hold_action", schema: [
selector: { ui_action: {} }, {
name: "tap_action",
selector: {
ui_action: {
default_action: "more-info",
},
},
},
{
name: "",
type: "optional_actions",
flatten: true,
schema: (["hold_action", "double_tap_action"] as const).map(
(action) => ({
name: action,
selector: {
ui_action: {
default_action: "none" as const,
},
},
})
),
},
],
}, },
] as const; ] as const;
@ -136,6 +161,7 @@ export class HuiPictureGlanceCardEditor
case "theme": case "theme":
case "tap_action": case "tap_action":
case "hold_action": case "hold_action":
case "double_tap_action":
return `${this.hass!.localize( return `${this.hass!.localize(
`ui.panel.lovelace.editor.card.generic.${schema.name}` `ui.panel.lovelace.editor.card.generic.${schema.name}`
)} (${this.hass!.localize( )} (${this.hass!.localize(

View File

@ -31,7 +31,7 @@ export class HuiIconElement extends LitElement implements LovelaceElement {
throw Error("Icon required"); throw Error("Icon required");
} }
this._config = { hold_action: { action: "more-info" }, ...config }; this._config = { tap_action: { action: "more-info" }, ...config };
} }
protected render() { protected render() {

View File

@ -29,7 +29,7 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
throw Error("Invalid configuration"); throw Error("Invalid configuration");
} }
this._config = { hold_action: { action: "more-info" }, ...config }; this._config = { tap_action: { action: "more-info" }, ...config };
this.classList.toggle( this.classList.toggle(
"clickable", "clickable",

View File

@ -60,7 +60,7 @@ export class HuiStateBadgeElement
throw Error("Entity required"); throw Error("Entity required");
} }
this._config = { hold_action: { action: "more-info" }, ...config }; this._config = { tap_action: { action: "more-info" }, ...config };
} }
protected shouldUpdate(changedProps: PropertyValues): boolean { protected shouldUpdate(changedProps: PropertyValues): boolean {

View File

@ -59,7 +59,7 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement {
this._config = { this._config = {
state_color: true, state_color: true,
hold_action: { action: "more-info" }, tap_action: { action: "more-info" },
...config, ...config,
}; };
} }

View File

@ -56,7 +56,7 @@ class HuiStateLabelElement extends LitElement implements LovelaceElement {
throw Error("Entity required"); throw Error("Entity required");
} }
this._config = { hold_action: { action: "more-info" }, ...config }; this._config = { tap_action: { action: "more-info" }, ...config };
} }
protected shouldUpdate(changedProps: PropertyValues): boolean { protected shouldUpdate(changedProps: PropertyValues): boolean {