1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-04-02 08:33:31 +01:00

Light favorite color card feature (#29995)

* Light favorite color card feature

* rewrite resizeObserver

* code review

* Apply suggestions from code review

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>

* Apply suggestion

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>

* Apply suggestion from @MindFreeze

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
This commit is contained in:
karwosts
2026-03-11 05:59:01 -07:00
committed by GitHub
parent 0cf3e34956
commit 9d3aafe219
6 changed files with 272 additions and 0 deletions

View File

@@ -29,6 +29,8 @@ class MoreInfoViewLightColorPicker extends LitElement {
@property({ attribute: false }) color!: LightColor;
@property({ type: Boolean, reflect: true }) wide = false;
@query("ha-outlined-icon-button", true)
private _button?: HaOutlinedIconButton;
@@ -108,6 +110,23 @@ class MoreInfoViewLightColorPicker extends LitElement {
--md-ripple-pressed-opacity: 0;
border-radius: var(--ha-border-radius-pill);
}
:host([wide]) ha-outlined-icon-button {
width: 100%;
border-radius: var(--ha-favorite-color-button-border-radius);
--_container-shape: var(--ha-favorite-color-button-border-radius);
--_container-shape-start-start: var(
--ha-favorite-color-button-border-radius
);
--_container-shape-start-end: var(
--ha-favorite-color-button-border-radius
);
--_container-shape-end-start: var(
--ha-favorite-color-button-border-radius
);
--_container-shape-end-end: var(
--ha-favorite-color-button-border-radius
);
}
:host([disabled]) {
pointer-events: none;
}

View File

@@ -0,0 +1,240 @@
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state, query } from "lit/decorators";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import { computeDomain } from "../../../common/entity/compute_domain";
import { UNAVAILABLE } from "../../../data/entity/entity";
import {
computeDefaultFavoriteColors,
type LightEntity,
type LightColor,
lightSupportsFavoriteColors,
} from "../../../data/light";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
LightColorFavoritesCardFeatureConfig,
LovelaceCardFeatureContext,
} from "./types";
import {
type EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../../data/entity/entity_registry";
import "../../../dialogs/more-info/components/lights/ha-favorite-color-button";
import { actionHandler } from "../common/directives/action-handler-directive";
import { debounce } from "../../../common/util/debounce";
export const supportsLightColorFavoritesCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
) => {
const stateObj = context.entity_id
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
const domain = computeDomain(stateObj.entity_id);
return domain === "light" && lightSupportsFavoriteColors(stateObj);
};
@customElement("hui-light-color-favorites-card-feature")
class HuiLightColorFavoritesCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state() private _config?: LightColorFavoritesCardFeatureConfig;
@state() private _entry?: EntityRegistryEntry | null;
@state() private _favoriteColors: LightColor[] = [];
@state() private _maxVisible = 0;
@query(".container") private _container!: HTMLDivElement;
private _resizeObserver?: ResizeObserver;
private _unsubEntityRegistry?: UnsubscribeFunc;
public connectedCallback() {
super.connectedCallback();
this._subscribeEntityEntry();
this.updateComplete.then(() => this._attachObserver());
}
public disconnectedCallback() {
super.disconnectedCallback();
this._unsubscribeEntityRegistry();
this._resizeObserver?.disconnect();
}
private _unsubscribeEntityRegistry() {
if (this._unsubEntityRegistry) {
this._unsubEntityRegistry();
this._unsubEntityRegistry = undefined;
}
}
private _subscribeEntityEntry() {
if (this.hass && this.context?.entity_id) {
const id = this.context.entity_id;
try {
this._unsubEntityRegistry = subscribeEntityRegistry(
this.hass!.connection,
(entries) => {
const entry = entries.find((e) => e.entity_id === id);
if (entry) {
this._entry = entry;
}
}
);
} catch (_e) {
this._entry = null;
}
}
}
private _measure() {
const w = this._container.clientWidth;
const pillMin = 32 + 8;
this._maxVisible = Math.floor(w / pillMin);
}
private _attachObserver(): void {
if (!this._resizeObserver) {
this._resizeObserver = new ResizeObserver(
debounce(() => this._measure(), 250, false)
);
}
this._resizeObserver.observe(this._container);
}
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id] as LightEntity | undefined;
}
protected updated(changedProps: PropertyValues): void {
if (changedProps.has("context")) {
this._unsubscribeEntityRegistry();
this._subscribeEntityEntry();
}
if (changedProps.has("_entry") || changedProps.has("_maxVisible")) {
if (this._entry) {
if (this._entry.options?.light?.favorite_colors) {
this._favoriteColors =
this._entry.options.light.favorite_colors.slice(
0,
this._maxVisible
);
} else if (this._stateObj) {
this._favoriteColors = computeDefaultFavoriteColors(
this._stateObj
).slice(0, this._maxVisible);
}
}
}
}
static getStubConfig(): LightColorFavoritesCardFeatureConfig {
return {
type: "light-color-favorites",
};
}
public setConfig(config: LightColorFavoritesCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsLightColorFavoritesCardFeature(this.hass, this.context)
) {
return nothing;
}
return html`
<div class="container">
${this._favoriteColors.map(
(color, index) => html`
<div class="color">
<ha-favorite-color-button
wide
.label=${this.hass!.localize(
`ui.dialogs.more_info_control.light.favorite_color.set`,
{ number: index }
)}
.disabled=${this._stateObj!.state === UNAVAILABLE}
.color=${color}
.index=${index}
.actionHandler=${actionHandler({
disabled: this._stateObj!.state === UNAVAILABLE,
})}
@action=${this._handleColorAction}
>
</ha-favorite-color-button>
</div>
`
)}
</div>
`;
}
private _handleColorAction(ev: CustomEvent) {
ev.stopPropagation();
const index = (ev.target! as any).index!;
const favorite = this._favoriteColors[index];
this.hass!.callService("light", "turn_on", {
entity_id: this._stateObj!.entity_id,
...favorite,
});
}
static get styles() {
return [
cardFeatureStyles,
css`
.container {
position: relative;
display: flex;
user-select: none;
flex-wrap: nowrap;
align-items: center;
gap: 8px;
}
.color {
position: relative;
display: block;
flex: 1 1 32px;
min-width: 32px;
height: 40px;
}
ha-favorite-color-button {
--ha-favorite-color-button-border-radius: var(--ha-border-radius-md);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-light-color-favorites-card-feature": HuiLightColorFavoritesCardFeature;
}
}

View File

@@ -44,6 +44,10 @@ export interface LightColorTempCardFeatureConfig {
type: "light-color-temp";
}
export interface LightColorFavoritesCardFeatureConfig {
type: "light-color-favorites";
}
export interface LockCommandsCardFeatureConfig {
type: "lock-commands";
}
@@ -271,6 +275,7 @@ export type LovelaceCardFeatureConfig =
| LawnMowerCommandsCardFeatureConfig
| LightBrightnessCardFeatureConfig
| LightColorTempCardFeatureConfig
| LightColorFavoritesCardFeatureConfig
| LockCommandsCardFeatureConfig
| LockOpenDoorCardFeatureConfig
| MediaPlayerPlaybackCardFeatureConfig

View File

@@ -22,6 +22,7 @@ import "../card-features/hui-humidifier-toggle-card-feature";
import "../card-features/hui-lawn-mower-commands-card-feature";
import "../card-features/hui-light-brightness-card-feature";
import "../card-features/hui-light-color-temp-card-feature";
import "../card-features/hui-light-color-favorites-card-feature";
import "../card-features/hui-lock-commands-card-feature";
import "../card-features/hui-lock-open-door-card-feature";
import "../card-features/hui-media-player-playback-card-feature";
@@ -74,6 +75,7 @@ const TYPES = new Set<LovelaceCardFeatureConfig["type"]>([
"lawn-mower-commands",
"light-brightness",
"light-color-temp",
"light-color-favorites",
"lock-commands",
"lock-open-door",
"media-player-playback",

View File

@@ -71,6 +71,7 @@ import type {
LovelaceCardFeatureContext,
} from "../../card-features/types";
import { getCardFeatureElementClass } from "../../create-element/create-card-feature-element";
import { supportsLightColorFavoritesCardFeature } from "../../card-features/hui-light-color-favorites-card-feature";
export type FeatureType = LovelaceCardFeatureConfig["type"];
@@ -106,6 +107,7 @@ const UI_FEATURE_TYPES = [
"lawn-mower-commands",
"light-brightness",
"light-color-temp",
"light-color-favorites",
"lock-commands",
"lock-open-door",
"media-player-playback",
@@ -182,6 +184,7 @@ const SUPPORTS_FEATURE_TYPES: Record<
"lawn-mower-commands": supportsLawnMowerCommandCardFeature,
"light-brightness": supportsLightBrightnessCardFeature,
"light-color-temp": supportsLightColorTempCardFeature,
"light-color-favorites": supportsLightColorFavoritesCardFeature,
"lock-commands": supportsLockCommandsCardFeature,
"lock-open-door": supportsLockOpenDoorCardFeature,
"media-player-playback": supportsMediaPlayerPlaybackCardFeature,

View File

@@ -9414,6 +9414,9 @@
"light-brightness": {
"label": "Light brightness"
},
"light-color-favorites": {
"label": "Light color favorites"
},
"light-color-temp": {
"label": "Light color temperature"
},