diff --git a/gallery/src/pages/lovelace/tile-card.markdown b/gallery/src/pages/lovelace/tile-card.markdown new file mode 100644 index 0000000000..0117153269 --- /dev/null +++ b/gallery/src/pages/lovelace/tile-card.markdown @@ -0,0 +1,3 @@ +--- +title: Tile Card +--- diff --git a/gallery/src/pages/lovelace/tile-card.ts b/gallery/src/pages/lovelace/tile-card.ts new file mode 100644 index 0000000000..00e1e29376 --- /dev/null +++ b/gallery/src/pages/lovelace/tile-card.ts @@ -0,0 +1,173 @@ +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { customElement, query } from "lit/decorators"; +import { CoverEntityFeature } from "../../../../src/data/cover"; +import { LightColorMode } from "../../../../src/data/light"; +import { VacuumEntityFeature } from "../../../../src/data/vacuum"; +import { getEntity } from "../../../../src/fake_data/entity"; +import { provideHass } from "../../../../src/fake_data/provide_hass"; +import "../../components/demo-cards"; + +const ENTITIES = [ + getEntity("switch", "tv_outlet", "on", { + friendly_name: "TV outlet", + device_class: "outlet", + }), + getEntity("light", "bed_light", "on", { + friendly_name: "Bed Light", + supported_color_modes: [LightColorMode.HS], + }), + getEntity("light", "unavailable", "unavailable", { + friendly_name: "Unavailable entity", + }), + getEntity("climate", "thermostat", "heat", { + current_temperature: 73, + min_temp: 45, + max_temp: 95, + temperature: 80, + hvac_modes: ["heat", "cool", "auto", "off"], + friendly_name: "Thermostat", + hvac_action: "heating", + }), + getEntity("person", "paulus", "home", { + friendly_name: "Paulus", + }), + getEntity("vacuum", "first_floor_vacuum", "docked", { + friendly_name: "First floor vacuum", + supported_features: + VacuumEntityFeature.START + + VacuumEntityFeature.STOP + + VacuumEntityFeature.RETURN_HOME, + }), + getEntity("cover", "kitchen_shutter", "open", { + friendly_name: "Kitchen shutter", + device_class: "shutter", + supported_features: + CoverEntityFeature.CLOSE + + CoverEntityFeature.OPEN + + CoverEntityFeature.STOP, + }), + getEntity("cover", "pergola_roof", "open", { + friendly_name: "Pergola Roof", + supported_features: + CoverEntityFeature.CLOSE_TILT + + CoverEntityFeature.OPEN_TILT + + CoverEntityFeature.STOP_TILT, + }), +]; + +const CONFIGS = [ + { + heading: "Basic example", + config: ` +- type: tile + entity: switch.tv_outlet + `, + }, + { + heading: "Vertical example", + config: ` +- type: tile + entity: switch.tv_outlet + vertical: true + `, + }, + { + heading: "Custom color", + config: ` +- type: tile + entity: switch.tv_outlet + color: pink + `, + }, + { + heading: "Unknown entity", + config: ` +- type: tile + entity: light.unknown + `, + }, + { + heading: "Unavailable entity", + config: ` +- type: tile + entity: light.unavailable + `, + }, + { + heading: "Climate", + config: ` +- type: tile + entity: climate.thermostat + `, + }, + { + heading: "Person", + config: ` +- type: tile + entity: person.paulus + `, + }, + { + heading: "Light brightness feature", + config: ` +- type: tile + entity: light.bed_light + features: + - type: "light-brightness" + `, + }, + { + heading: "Vacuum commands feature", + config: ` +- type: tile + entity: vacuum.first_floor_vacuum + features: + - type: "vacuum-commands" + commands: + - start_pause + - stop + - return_home + `, + }, + { + heading: "Cover open close feature", + config: ` +- type: tile + entity: cover.kitchen_shutter + features: + - type: "cover-open-close" + `, + }, + { + heading: "Cover tilt feature", + config: ` +- type: tile + entity: cover.pergola_roof + features: + - type: "cover-tilt" + `, + }, +]; + +@customElement("demo-lovelace-tile-card") +class DemoTile extends LitElement { + @query("#demos") private _demoRoot!: HTMLElement; + + protected render(): TemplateResult { + return html``; + } + + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + const hass = provideHass(this._demoRoot); + hass.updateTranslations(null, "en"); + hass.updateTranslations("lovelace", "en"); + hass.addEntities(ENTITIES); + } +} + +declare global { + interface HTMLElementTagNameMap { + "demo-lovelace-tile-card": DemoTile; + } +} diff --git a/src/components/tile/ha-tile-info.ts b/src/components/tile/ha-tile-info.ts index 5f8d79aa77..5aa224884a 100644 --- a/src/components/tile/ha-tile-info.ts +++ b/src/components/tile/ha-tile-info.ts @@ -25,6 +25,8 @@ export class HaTileInfo extends LitElement { display: flex; flex-direction: column; align-items: flex-start; + justify-content: center; + min-height: 40px; } span { text-overflow: ellipsis; diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index 6422257036..25b4eb071f 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -16,7 +16,6 @@ import memoizeOne from "memoize-one"; import { computeCssColor } from "../../../common/color/compute-color"; import { hsv2rgb, rgb2hex, rgb2hsv } from "../../../common/color/convert-color"; import { DOMAINS_TOGGLE } from "../../../common/const"; -import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeDomain } from "../../../common/entity/compute_domain"; import { computeStateDisplay } from "../../../common/entity/compute_state_display"; import { stateActive } from "../../../common/entity/state_active"; @@ -263,27 +262,29 @@ export class HuiTileCard extends LitElement implements LovelaceCard { const entityId = this._config.entity; const stateObj = entityId ? this.hass.states[entityId] : undefined; - const tileClasses = { vertical: Boolean(this._config.vertical) }; + const contentClasses = { vertical: Boolean(this._config.vertical) }; if (!stateObj) { return html` - - - - - + + + + + + + + - `; @@ -315,66 +316,62 @@ export class HuiTileCard extends LitElement implements LovelaceCard { return html` ${this._shouldRenderRipple ? html`` : null} - + - ${imageUrl - ? html` - - ` - : html` - - `} - ${badge - ? html` - - ` - : null} + @mousedown=${this.handleRippleActivate} + @mouseup=${this.handleRippleDeactivate} + @mouseenter=${this.handleRippleMouseEnter} + @mouseleave=${this.handleRippleMouseLeave} + @touchstart=${this.handleRippleActivate} + @touchend=${this.handleRippleDeactivate} + @touchcancel=${this.handleRippleDeactivate} + > + + + ${imageUrl + ? html` + + ` + : html` + + `} + ${badge + ? html` + + ` + : null} + + - ${supportedFeatures?.length ? html` @@ -424,16 +421,15 @@ export class HuiTileCard extends LitElement implements LovelaceCard { --tile-color: var(--state-inactive-color); -webkit-tap-highlight-color: transparent; } - ha-card:has(.tile:focus-visible) { + ha-card:has(.background:focus-visible) { border-color: var(--tile-color); box-shadow: 0 0 0 1px var(--tile-color); } ha-card { --mdc-ripple-color: var(--tile-color); height: 100%; - overflow: hidden; - // For safari overflow hidden z-index: 0; + overflow: hidden; } ha-card.active { --tile-color: var(--state-icon-color); @@ -444,7 +440,14 @@ export class HuiTileCard extends LitElement implements LovelaceCard { [role="button"]:focus { outline: none; } - .tile { + .background { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + } + .content { display: flex; flex-direction: row; align-items: center; @@ -473,6 +476,10 @@ export class HuiTileCard extends LitElement implements LovelaceCard { } .icon-container .icon { --tile-icon-color: var(--tile-color); + user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + -moz-user-select: none; } .icon-container .badge { position: absolute; @@ -488,9 +495,9 @@ export class HuiTileCard extends LitElement implements LovelaceCard { padding: 12px; flex: 1; min-width: 0; - min-height: 40px; transition: background-color 180ms ease-in-out; box-sizing: border-box; + pointer-events: none; } `; }