1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-04-17 23:54:28 +01:00

Markdown card: add support of actions (#28951)

* add actions

* add actions

* add actions to MarkdownCardConfig

* add "actions_warning" for Markdown

* Add a "warning" label

* process a default handler if none action is defined

* check for config=undefined

* fix attributes

* prettier

* Update src/translations/en.json

Co-authored-by: Norbert Rittel <norbert@rittel.de>

* add ripple

* Fix interactive and add missing import for ripple

* Fix translation

---------

Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
This commit is contained in:
ildar170975
2026-03-18 20:22:28 +03:00
committed by GitHub
parent c697735e46
commit 15245af52d
4 changed files with 132 additions and 1 deletions

View File

@@ -3,18 +3,24 @@ import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import hash from "object-hash";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-alert";
import "../../../components/ha-card";
import "../../../components/ha-markdown";
import "../../../components/ha-ripple";
import type { RenderTemplateResult } from "../../../data/ws-templates";
import { subscribeRenderTemplate } from "../../../data/ws-templates";
import type { HomeAssistant } from "../../../types";
import { CacheManager } from "../../../util/cache-manager";
import type { LovelaceCard, LovelaceCardEditor } from "../types";
import type { MarkdownCardConfig } from "./types";
import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
import { actionHandler } from "../common/directives/action-handler-directive";
import { handleAction } from "../common/handle-action";
import { hasAction } from "../common/has-action";
const templateCache = new CacheManager<RenderTemplateResult>(1000);
@@ -56,6 +62,14 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
: this._config.card_size;
}
private get _interactive() {
return (
hasAction(this._config?.tap_action) ||
hasAction(this._config?.hold_action) ||
hasAction(this._config?.double_tap_action)
);
}
public setConfig(config: MarkdownCardConfig): void {
if (!config.content) {
throw new Error("Content required");
@@ -104,7 +118,13 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
if (!this._config) {
return nothing;
}
const safeHandleAction = this._interactive ? this._handleAction : undefined;
const safeActionHandler = this._interactive
? actionHandler({
hasHold: hasAction(this._config.hold_action),
hasDoubleClick: hasAction(this._config.double_tap_action),
})
: undefined;
return html`
${this._error
? html`
@@ -122,8 +142,22 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
class=${classMap({
"with-header": !!this._config.title,
"text-only": this._config.text_only ?? false,
action: this._interactive,
})}
tabindex=${ifDefined(this._interactive ? "0" : undefined)}
role=${ifDefined(this._interactive ? "button" : undefined)}
@action=${safeHandleAction}
.actionHandler=${safeActionHandler}
>
${this._interactive
? html`<div
class="background"
@focus=${this._handleFocus}
@blur=${this._handleBlur}
>
<ha-ripple></ha-ripple>
</div>`
: nothing}
<ha-markdown
cache
breaks
@@ -228,11 +262,37 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
this._errorLevel = undefined;
}
private _handleAction(ev: ActionHandlerEvent) {
handleAction(this, this.hass!, this._config!, ev.detail.action!);
}
private _handleFocus(ev: FocusEvent) {
if ((ev.target as HTMLElement).matches(":focus-visible")) {
this.setAttribute("focused", "");
}
}
private _handleBlur() {
this.removeAttribute("focused");
}
static styles = css`
:host {
-webkit-tap-highlight-color: transparent;
--ha-ripple-hover-opacity: 0.04;
--ha-ripple-pressed-opacity: 0.12;
}
ha-card {
height: 100%;
overflow-y: auto;
}
.background {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
ha-alert {
margin-bottom: 8px;
}
@@ -252,6 +312,9 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
.text-only ha-markdown {
padding: 2px 4px;
}
.action {
cursor: pointer;
}
`;
}

View File

@@ -426,6 +426,9 @@ export interface MarkdownCardConfig extends LovelaceCardConfig {
entity_ids?: string | string[];
theme?: string;
show_empty?: boolean;
tap_action?: ActionConfig;
hold_action?: ActionConfig;
double_tap_action?: ActionConfig;
}
export interface ClockCardConfig extends LovelaceCardConfig {

View File

@@ -1,3 +1,4 @@
import { mdiGestureTap } from "@mdi/js";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
@@ -12,7 +13,14 @@ import type {
import type { HomeAssistant } from "../../../../types";
import type { MarkdownCardConfig } from "../../cards/types";
import type { LovelaceCardEditor } from "../../types";
import { actionConfigStruct } from "../structs/action-struct";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import {
type UiAction,
supportedActions,
} from "../../components/hui-action-editor";
const TAP_ACTIONS: UiAction[] = ["navigate", "url", "perform-action", "none"];
const cardConfigStruct = assign(
baseLovelaceCardConfig,
@@ -20,6 +28,11 @@ const cardConfigStruct = assign(
text_only: optional(boolean()),
title: optional(string()),
content: string(),
tap_action: optional(supportedActions(actionConfigStruct, TAP_ACTIONS)),
hold_action: optional(supportedActions(actionConfigStruct, TAP_ACTIONS)),
double_tap_action: optional(
supportedActions(actionConfigStruct, TAP_ACTIONS)
),
})
);
@@ -68,6 +81,43 @@ export class HuiMarkdownCardEditor
required: true,
selector: { template: { preview: false } },
},
{
name: "interactions",
type: "expandable",
flatten: true,
iconPath: mdiGestureTap,
schema: [
{
name: "actions_warning",
type: "constant",
},
{
name: "tap_action",
selector: {
ui_action: {
actions: TAP_ACTIONS,
default_action: "none",
},
},
},
{
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 satisfies HaFormSchema[]
);
@@ -119,6 +169,20 @@ export class HuiMarkdownCardEditor
return this.hass!.localize(
`ui.panel.lovelace.editor.card.markdown.${schema.name}`
);
case "interactions":
return this.hass!.localize(
"ui.panel.lovelace.editor.card.generic.interactions"
);
case "actions_warning":
return this.hass!.localize(
"ui.panel.lovelace.editor.card.markdown.actions_warning"
);
case "tap_action":
case "hold_action":
case "double_tap_action":
return `${this.hass!.localize(
`ui.panel.lovelace.editor.card.generic.${schema.name}`
)} (${this.hass!.localize("ui.panel.lovelace.editor.card.config.optional")})`;
default:
return this.hass!.localize(
`ui.panel.lovelace.editor.card.generic.${schema.name}`

View File

@@ -9313,6 +9313,7 @@
"card": "Card",
"text-only": "Text only"
},
"actions_warning": "Adding an action will disable links in the content.",
"description": "This card is used to render Markdown."
},
"clock": {