custom-card-helpers 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.github/dependabot.yml +13 -0
  2. package/.github/workflows/claude-code-review.yml +44 -0
  3. package/.github/workflows/claude.yml +50 -0
  4. package/.github/workflows/release.yml +4 -0
  5. package/.github/workflows/test.yml +1 -0
  6. package/dist/compute-name.d.ts +7 -0
  7. package/dist/compute-state-display.d.ts +1 -2
  8. package/dist/datetime/duration_to_seconds.d.ts +1 -1
  9. package/dist/datetime/seconds_to_duration.d.ts +1 -1
  10. package/dist/datetime/select_unit.d.ts +8 -0
  11. package/dist/get-lovelace.d.ts +5 -1
  12. package/dist/index.js +259 -23
  13. package/dist/index.js.map +1 -1
  14. package/dist/index.m.js +254 -23
  15. package/dist/index.m.js.map +1 -1
  16. package/dist/index.umd.js +263 -25
  17. package/dist/index.umd.js.map +1 -1
  18. package/dist/scroll-to-target.d.ts +1 -1
  19. package/dist/translations/localize.d.ts +1 -2
  20. package/dist/turn-on-off-entity.d.ts +2 -0
  21. package/dist/types.d.ts +156 -4
  22. package/package.json +7 -8
  23. package/src/compute-name.ts +7 -0
  24. package/src/compute-state-display.ts +2 -3
  25. package/src/cover_icon.ts +74 -6
  26. package/src/datetime/duration_to_seconds.ts +1 -1
  27. package/src/datetime/relative_time.ts +1 -2
  28. package/src/datetime/seconds_to_duration.ts +1 -1
  29. package/src/datetime/select_unit.ts +94 -0
  30. package/src/datetime/timer_time_remaining.ts +1 -1
  31. package/src/get-lovelace.ts +22 -16
  32. package/src/handle-action.ts +19 -2
  33. package/src/handle-click.ts +1 -1
  34. package/src/scroll-to-target.ts +1 -1
  35. package/src/state_icon.ts +1 -1
  36. package/src/translations/localize.ts +1 -3
  37. package/src/turn-on-off-entity.ts +10 -0
  38. package/src/types.ts +203 -3
  39. package/test/datetime.test.ts +2 -2
  40. package/test/icons.test.ts +51 -0
  41. package/test/service-actions.test.ts +19 -1
@@ -1,5 +1,4 @@
1
- import { Resources } from "../types";
2
- export type LocalizeFunc = (key: string, ...args: any[]) => string;
1
+ import { LocalizeFunc, Resources } from "../types";
3
2
  interface FormatType {
4
3
  [format: string]: any;
5
4
  }
@@ -1,2 +1,4 @@
1
1
  import { HomeAssistant } from "./types";
2
2
  export declare const turnOnOffEntity: (hass: HomeAssistant, entityId: string, turnOn?: boolean) => Promise<void>;
3
+ export declare const turnOnEntity: (hass: HomeAssistant, entityId: string) => Promise<void>;
4
+ export declare const turnOffEntity: (hass: HomeAssistant, entityId: string) => Promise<void>;
package/dist/types.d.ts CHANGED
@@ -1,5 +1,34 @@
1
- import { HassEntities, HassConfig, Auth, Connection, MessageBase, HassServices, HassServiceTarget } from "home-assistant-js-websocket";
1
+ import { HassEntities, HassEntity, HassConfig, Auth, Connection, MessageBase, HassServices, HassServiceTarget } from "home-assistant-js-websocket";
2
2
  import { HapticType } from "./haptic";
3
+ export interface DeviceRegistryEntry {
4
+ id: string;
5
+ name: string | null;
6
+ name_by_user: string | null;
7
+ area_id: string | null;
8
+ /** Set when this device is a sub-device of another. @since Home Assistant 2026.4 */
9
+ parent_device_id?: string | null;
10
+ }
11
+ export interface AreaRegistryEntry {
12
+ area_id: string;
13
+ name: string;
14
+ floor_id: string | null;
15
+ }
16
+ export interface FloorRegistryEntry {
17
+ floor_id: string;
18
+ name: string;
19
+ level: number | null;
20
+ }
21
+ /** One part of a composed entity name, as accepted by `formatEntityName`. */
22
+ export type EntityNameItem = {
23
+ type: "entity" | "device" | "area" | "floor";
24
+ } | {
25
+ type: "text";
26
+ text: string;
27
+ };
28
+ export interface EntityNameOptions {
29
+ /** Joins the resolved parts. Defaults to a single space. */
30
+ separator?: string;
31
+ }
3
32
  import { HASSDomEvent } from "./fire-event";
4
33
  export interface ToggleMenuActionConfig extends BaseActionConfig {
5
34
  action: "toggle-menu";
@@ -10,7 +39,7 @@ export interface ToggleActionConfig extends BaseActionConfig {
10
39
  export interface CallServiceActionConfig extends BaseActionConfig {
11
40
  action: "call-service";
12
41
  service: string;
13
- service_data?: {
42
+ data?: {
14
43
  entity_id?: string | [string];
15
44
  [key: string]: any;
16
45
  };
@@ -36,6 +65,14 @@ export interface NoActionConfig extends BaseActionConfig {
36
65
  export interface CustomActionConfig extends BaseActionConfig {
37
66
  action: "fire-dom-event";
38
67
  }
68
+ export interface TurnOnActionConfig extends BaseActionConfig {
69
+ action: "turn_on";
70
+ entity?: string;
71
+ }
72
+ export interface TurnOffActionConfig extends BaseActionConfig {
73
+ action: "turn_off";
74
+ entity?: string;
75
+ }
39
76
  /**
40
77
  * `repeat` and `haptic` are specifically for use in custom cards like the Button-Card
41
78
  */
@@ -52,7 +89,15 @@ export interface ConfirmationRestrictionConfig {
52
89
  export interface RestrictionConfig {
53
90
  user: string;
54
91
  }
55
- export type ActionConfig = ToggleActionConfig | CallServiceActionConfig | NavigateActionConfig | UrlActionConfig | MoreInfoActionConfig | NoActionConfig | CustomActionConfig | ToggleMenuActionConfig;
92
+ export type ActionConfig = ToggleActionConfig | CallServiceActionConfig | NavigateActionConfig | UrlActionConfig | MoreInfoActionConfig | NoActionConfig | CustomActionConfig | ToggleMenuActionConfig | TurnOnActionConfig | TurnOffActionConfig;
93
+ export interface HuiRootElement extends HTMLElement {
94
+ lovelace: {
95
+ config: LovelaceConfig;
96
+ current_view: number;
97
+ [key: string]: any;
98
+ };
99
+ ___curView: number;
100
+ }
56
101
  export interface Window {
57
102
  customPanelJS: string;
58
103
  ShadyCSS: {
@@ -147,6 +192,19 @@ export interface ServiceCallRequest {
147
192
  serviceData?: Record<string, any>;
148
193
  target?: HassServiceTarget;
149
194
  }
195
+ export interface EntityRegistryDisplayEntry {
196
+ entity_id: string;
197
+ name?: string;
198
+ icon?: string;
199
+ device_id?: string;
200
+ area_id?: string;
201
+ labels: string[];
202
+ hidden?: boolean;
203
+ entity_category?: "config" | "diagnostic";
204
+ translation_key?: string;
205
+ platform?: string;
206
+ display_precision?: number;
207
+ }
150
208
  export interface HomeAssistant {
151
209
  auth: Auth;
152
210
  connection: Connection;
@@ -169,6 +227,29 @@ export interface HomeAssistant {
169
227
  [lang: string]: Translation;
170
228
  };
171
229
  };
230
+ /** Entity registry keyed by entity_id. */
231
+ entities?: Record<string, EntityRegistryDisplayEntry>;
232
+ devices?: Record<string, DeviceRegistryEntry>;
233
+ areas?: Record<string, AreaRegistryEntry>;
234
+ floors?: Record<string, FloorRegistryEntry>;
235
+ /** @since Home Assistant 2024.4 */
236
+ formatEntityState?: (stateObj: HassEntity, state?: string) => string;
237
+ /** @since Home Assistant 2024.4 */
238
+ formatEntityAttributeName?: (stateObj: HassEntity, attribute: string) => string;
239
+ /** @since Home Assistant 2024.4 */
240
+ formatEntityAttributeValue?: (stateObj: HassEntity, attribute: string, value?: unknown) => string;
241
+ /**
242
+ * Formats an entity's display name from its registry context.
243
+ *
244
+ * Accepts a plain string (returned as-is), a single name item, an array of
245
+ * items joined with the separator, or `undefined` for the default name.
246
+ *
247
+ * @since Home Assistant 2026.4 in this shape. The helper exists from 2025.10,
248
+ * but took bare type strings then, and only accepted `EntityNameItem`s
249
+ * between 2025.11 and 2026.3 - so feature detection is not sufficient, check
250
+ * `hass.config.version` before calling with a card's `name` option.
251
+ */
252
+ formatEntityName?: (stateObj: HassEntity, name?: string | EntityNameItem | EntityNameItem[], options?: EntityNameOptions) => string;
172
253
  dockedSidebar: boolean;
173
254
  moreInfoEntityId: string;
174
255
  user: CurrentUser;
@@ -181,6 +262,7 @@ export interface HomeAssistant {
181
262
  }) => Promise<Response>;
182
263
  sendWS: (msg: MessageBase) => Promise<void>;
183
264
  callWS: <T>(msg: MessageBase) => Promise<T>;
265
+ hassUrl: (path?: string) => string;
184
266
  }
185
267
  export declare enum NumberFormat {
186
268
  language = "language",
@@ -196,10 +278,34 @@ export declare enum TimeFormat {
196
278
  am_pm = "12",
197
279
  twenty_four = "24"
198
280
  }
281
+ export declare enum TimeZone {
282
+ local = "local",
283
+ server = "server"
284
+ }
285
+ export declare enum DateFormat {
286
+ language = "language",
287
+ system = "system",
288
+ DMY = "DMY",
289
+ MDY = "MDY",
290
+ YMD = "YMD"
291
+ }
292
+ export declare enum FirstWeekday {
293
+ language = "language",
294
+ monday = "monday",
295
+ tuesday = "tuesday",
296
+ wednesday = "wednesday",
297
+ thursday = "thursday",
298
+ friday = "friday",
299
+ saturday = "saturday",
300
+ sunday = "sunday"
301
+ }
199
302
  export interface FrontendLocaleData {
200
303
  language: string;
201
304
  number_format: NumberFormat;
202
305
  time_format: TimeFormat;
306
+ date_format: DateFormat;
307
+ first_weekday: FirstWeekday;
308
+ time_zone: TimeZone;
203
309
  }
204
310
  export interface LovelaceCardConfig {
205
311
  index?: number;
@@ -219,6 +325,52 @@ export interface LovelaceCardEditor extends HTMLElement {
219
325
  lovelace?: LovelaceConfig;
220
326
  setConfig(config: LovelaceCardConfig): void;
221
327
  }
328
+ export interface LovelaceCardFeatureConfig {
329
+ type: string;
330
+ [key: string]: any;
331
+ }
332
+ export type LovelaceCardFeaturePosition = "bottom" | "inline";
333
+ export interface LovelaceCardFeatureContext {
334
+ entity_id?: string;
335
+ area_id?: string;
336
+ }
337
+ export interface LovelaceCardFeature extends HTMLElement {
338
+ hass?: HomeAssistant;
339
+ /** @deprecated Use `context` instead */
340
+ stateObj?: HassEntity;
341
+ context?: LovelaceCardFeatureContext;
342
+ setConfig(config: LovelaceCardFeatureConfig): void;
343
+ color?: string;
344
+ position?: LovelaceCardFeaturePosition;
345
+ }
346
+ export interface LovelaceCardFeatureEditor extends HTMLElement {
347
+ hass?: HomeAssistant;
348
+ lovelace?: LovelaceConfig;
349
+ context?: LovelaceCardFeatureContext;
350
+ setConfig(config: LovelaceCardFeatureConfig): void;
351
+ }
352
+ export interface HaFormSchema {
353
+ name?: string;
354
+ type?: string;
355
+ [key: string]: any;
356
+ }
357
+ export interface LovelaceCardFeatureConstructor {
358
+ new (...args: any[]): LovelaceCardFeature;
359
+ getStubConfig?: (hass: HomeAssistant, context?: LovelaceCardFeatureContext) => LovelaceCardFeatureConfig;
360
+ getConfigElement?: () => LovelaceCardFeatureEditor;
361
+ getConfigForm?: () => {
362
+ schema: HaFormSchema[];
363
+ assertConfig?: (config: LovelaceCardConfig) => void;
364
+ };
365
+ }
366
+ export interface CustomCardFeatureEntry {
367
+ type: string;
368
+ name?: string;
369
+ configurable?: boolean;
370
+ isSupported?: (hass: HomeAssistant, context: LovelaceCardFeatureContext) => boolean;
371
+ /** @deprecated Use `isSupported` instead */
372
+ supported?: (stateObj: HassEntity) => boolean;
373
+ }
222
374
  export interface LovelaceConfig {
223
375
  title?: string;
224
376
  views: LovelaceViewConfig[];
@@ -261,7 +413,7 @@ export interface EntitiesCardEntityConfig extends EntityConfig {
261
413
  secondary_info?: "entity-id" | "last-changed" | "last-triggered" | "last-updated" | "position" | "tilt-position" | "brightness";
262
414
  action_name?: string;
263
415
  service?: string;
264
- service_data?: Record<string, unknown>;
416
+ data?: Record<string, unknown>;
265
417
  url?: string;
266
418
  tap_action?: ActionConfig;
267
419
  hold_action?: ActionConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "custom-card-helpers",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Set of helpful functions and types for Custom Card creators",
5
5
  "main": "dist/index.js",
6
6
  "umd:main": "dist/index.umd.js",
@@ -37,9 +37,8 @@
37
37
  },
38
38
  "homepage": "https://github.com/custom-cards/custom-card-helpers#readme",
39
39
  "dependencies": {
40
- "@formatjs/intl-utils": "^3.8.4",
41
40
  "home-assistant-js-websocket": "^9.6.0",
42
- "intl-messageformat": "^11.1.2",
41
+ "intl-messageformat": "^12.1.5",
43
42
  "superstruct": "^2.0.2"
44
43
  },
45
44
  "peerDependencies": {
@@ -47,12 +46,12 @@
47
46
  },
48
47
  "devDependencies": {
49
48
  "@rollup/plugin-typescript": "^12.1.4",
50
- "jsdom": "^26.1.0",
49
+ "jsdom": "^31.0.0",
51
50
  "lit": "^3.3.1",
52
- "rollup": "^4.58.0",
51
+ "rollup": "^4.60.0",
53
52
  "tslib": "^2.8.1",
54
- "typedoc": "^0.28.17",
53
+ "typedoc": "^0.28.20",
55
54
  "typescript": "^5.9.3",
56
- "vitest": "^3.2.4"
55
+ "vitest": "^4.1.0"
57
56
  }
58
- }
57
+ }
@@ -1,4 +1,11 @@
1
1
  import { HassEntity } from "home-assistant-js-websocket";
2
2
 
3
+ /**
4
+ * @deprecated Reads `friendly_name`, which Home Assistant is moving away from -
5
+ * it now always includes the device name, so this duplicates context the
6
+ * surrounding UI already shows. Prefer `hass.formatEntityName(stateObj, name)`
7
+ * on HA 2026.4 and later, which resolves the name from the entity's registry
8
+ * context and lets users choose which parts to include.
9
+ */
3
10
  export const computeName = (stateObj: HassEntity): string =>
4
11
  stateObj.attributes.friendly_name || stateObj.entity_id;
@@ -2,9 +2,8 @@ import { HassEntity } from "home-assistant-js-websocket";
2
2
  import { formatDateTime } from "./datetime/format_date_time";
3
3
  import { formatDate } from "./datetime/format_date";
4
4
  import { formatTime } from "./datetime/format_time";
5
- import { LocalizeFunc } from "./translations/localize";
6
5
  import { computeStateDomain } from "./compute-state-domain";
7
- import { FrontendLocaleData } from "./types";
6
+ import { FrontendLocaleData, LocalizeFunc } from "./types";
8
7
  import { formatNumber, isNumericState } from "./format-number";
9
8
 
10
9
  export const computeStateDisplay = (
@@ -126,4 +125,4 @@ export const computeStateDisplay = (
126
125
  // We don't know! Return the raw state.
127
126
  compareState
128
127
  );
129
- };
128
+ };
package/src/cover_icon.ts CHANGED
@@ -1,21 +1,89 @@
1
1
  /** Return an icon representing a cover state. */
2
2
  import { HassEntity } from "home-assistant-js-websocket";
3
- import { domainIcon } from "./domain_icons";
4
3
 
5
4
  export const coverIcon = (state: HassEntity): string => {
6
5
  const open = state.state !== "closed";
7
6
  switch (state.attributes.device_class) {
8
7
  case "garage":
9
- return open ? "mdi:garage-open" : "mdi:garage";
8
+ switch (state.state) {
9
+ case "opening":
10
+ return "mdi:arrow-up-box";
11
+ case "closing":
12
+ return "mdi:arrow-down-box";
13
+ case "closed":
14
+ return "mdi:garage";
15
+ default:
16
+ return "mdi:garage-open";
17
+ }
18
+ case "gate":
19
+ switch (state.state) {
20
+ case "opening":
21
+ case "closing":
22
+ return "mdi:gate-arrow-right";
23
+ case "closed":
24
+ return "mdi:gate";
25
+ default:
26
+ return "mdi:gate-open";
27
+ }
10
28
  case "door":
11
29
  return open ? "mdi:door-open" : "mdi:door-closed";
30
+ case "damper":
31
+ return open ? "mdi:circle" : "mdi:circle-slice-8";
12
32
  case "shutter":
13
- return open ? "mdi:window-shutter-open" : "mdi:window-shutter";
33
+ switch (state.state) {
34
+ case "opening":
35
+ return "mdi:arrow-up-box";
36
+ case "closing":
37
+ return "mdi:arrow-down-box";
38
+ case "closed":
39
+ return "mdi:window-shutter";
40
+ default:
41
+ return "mdi:window-shutter-open";
42
+ }
43
+ case "curtain":
44
+ switch (state.state) {
45
+ case "opening":
46
+ return "mdi:arrow-split-vertical";
47
+ case "closing":
48
+ return "mdi:arrow-collapse-horizontal";
49
+ case "closed":
50
+ return "mdi:curtains-closed";
51
+ default:
52
+ return "mdi:curtains";
53
+ }
14
54
  case "blind":
15
- return open ? "mdi:blinds-open" : "mdi:blinds";
55
+ case "shade":
56
+ switch (state.state) {
57
+ case "opening":
58
+ return "mdi:arrow-up-box";
59
+ case "closing":
60
+ return "mdi:arrow-down-box";
61
+ case "closed":
62
+ return "mdi:blinds";
63
+ default:
64
+ return "mdi:blinds-open";
65
+ }
16
66
  case "window":
17
- return open ? "mdi:window-open" : "mdi:window-closed";
67
+ switch (state.state) {
68
+ case "opening":
69
+ return "mdi:arrow-up-box";
70
+ case "closing":
71
+ return "mdi:arrow-down-box";
72
+ case "closed":
73
+ return "mdi:window-closed";
74
+ default:
75
+ return "mdi:window-open";
76
+ }
18
77
  default:
19
- return domainIcon("cover", state.state);
78
+ switch (state.state) {
79
+ case "opening":
80
+ return "mdi:arrow-up-box";
81
+ case "closing":
82
+ return "mdi:arrow-down-box";
83
+ case "closed":
84
+ return "mdi:window-closed";
85
+ default:
86
+ return "mdi:window-open";
87
+ }
20
88
  }
21
89
  };
@@ -3,7 +3,7 @@
3
3
  * @param duration hh:mm:ss formated duration
4
4
  * @returns duration in seconds
5
5
  */
6
- export default function durationToSeconds(duration: string): number {
6
+ export function durationToSeconds(duration: string): number {
7
7
  const parts = duration.split(":").map(Number);
8
8
  return parts[0] * 3600 + parts[1] * 60 + parts[2];
9
9
  }
@@ -1,12 +1,11 @@
1
1
  //REF: https://github.com/home-assistant/frontend/blob/dev/src/common/datetime/relative_time.ts
2
2
 
3
- import { selectUnit } from "@formatjs/intl-utils";
4
3
  import { FrontendLocaleData } from "../types";
4
+ import { selectUnit } from "./select_unit";
5
5
 
6
6
  const formatRelTimeMem =
7
7
  (locale: FrontendLocaleData) =>
8
8
  new Intl.RelativeTimeFormat(locale.language, { numeric: "auto" });
9
-
10
9
  /**
11
10
  * Calculate a string representing a date object as relative time from now.
12
11
  *
@@ -2,7 +2,7 @@
2
2
 
3
3
  const leftPad = (num: number) => (num < 10 ? `0${num}` : num);
4
4
 
5
- export default function secondsToDuration(d: number) {
5
+ export function secondsToDuration(d: number) {
6
6
  const h = Math.floor(d / 3600);
7
7
  const m = Math.floor((d % 3600) / 60);
8
8
  const s = Math.floor((d % 3600) % 60);
@@ -0,0 +1,94 @@
1
+ /*
2
+ * selectUnit() taken from @formatjs/intl-units 3.8.4.
3
+ *
4
+ */
5
+
6
+ export type Unit =
7
+ | 'second'
8
+ | 'minute'
9
+ | 'hour'
10
+ | 'day'
11
+ | 'week'
12
+ | 'month'
13
+ | 'quarter'
14
+ | 'year';
15
+
16
+ const MS_PER_SECOND = 1e3;
17
+ const SECS_PER_MIN = 60;
18
+ const SECS_PER_HOUR = SECS_PER_MIN * 60;
19
+ const SECS_PER_DAY = SECS_PER_HOUR * 24;
20
+ const SECS_PER_WEEK = SECS_PER_DAY * 7;
21
+
22
+ export function selectUnit(
23
+ from: Date | number,
24
+ to: Date | number = Date.now(),
25
+ thresholds: Partial<Thresholds> = {}
26
+ ): {value: number; unit: Unit} {
27
+ const resolvedThresholds: Thresholds = {
28
+ ...DEFAULT_THRESHOLDS,
29
+ ...(thresholds || {}),
30
+ };
31
+ const secs = (+from - +to) / MS_PER_SECOND;
32
+ if (Math.abs(secs) < resolvedThresholds.second) {
33
+ return {
34
+ value: Math.round(secs),
35
+ unit: 'second',
36
+ };
37
+ }
38
+ const mins = secs / SECS_PER_MIN;
39
+ if (Math.abs(mins) < resolvedThresholds.minute) {
40
+ return {
41
+ value: Math.round(mins),
42
+ unit: 'minute',
43
+ };
44
+ }
45
+ const hours = secs / SECS_PER_HOUR;
46
+ if (Math.abs(hours) < resolvedThresholds.hour) {
47
+ return {
48
+ value: Math.round(hours),
49
+ unit: 'hour',
50
+ };
51
+ }
52
+
53
+ const days = secs / SECS_PER_DAY;
54
+ if (Math.abs(days) < resolvedThresholds.day) {
55
+ return {
56
+ value: Math.round(days),
57
+ unit: 'day',
58
+ };
59
+ }
60
+ const fromDate = new Date(from);
61
+ const toDate = new Date(to);
62
+
63
+ const years = fromDate.getFullYear() - toDate.getFullYear();
64
+ if (Math.round(Math.abs(years)) > 0) {
65
+ return {
66
+ value: Math.round(years),
67
+ unit: 'year',
68
+ };
69
+ }
70
+
71
+ const months = years * 12 + fromDate.getMonth() - toDate.getMonth();
72
+ if (Math.round(Math.abs(months)) > 0) {
73
+ return {
74
+ value: Math.round(months),
75
+ unit: 'month',
76
+ };
77
+ }
78
+ const weeks = secs / SECS_PER_WEEK;
79
+
80
+ return {
81
+ value: Math.round(weeks),
82
+ unit: 'week',
83
+ };
84
+ }
85
+
86
+ type Thresholds = Record<'second' | 'minute' | 'hour' | 'day', number>;
87
+
88
+ export const DEFAULT_THRESHOLDS: Thresholds = {
89
+ second: 45, // seconds to minute
90
+ minute: 45, // minutes to hour
91
+ hour: 22, // hour to day
92
+ day: 5, // day to week
93
+ };
94
+
@@ -1,5 +1,5 @@
1
1
  import { HassEntity } from "home-assistant-js-websocket";
2
- import durationToSeconds from "../datetime/duration_to_seconds";
2
+ import { durationToSeconds } from "../datetime/duration_to_seconds";
3
3
 
4
4
  export function timerTimeRemaining(stateObj: HassEntity) {
5
5
  let timeRemaining = durationToSeconds(stateObj.attributes.remaining);
@@ -1,17 +1,23 @@
1
+ import { HuiRootElement } from "./types";
2
+
1
3
  export const getLovelace = () => {
2
- let root: any = document.querySelector('home-assistant');
3
- root = root && root.shadowRoot;
4
- root = root && root.querySelector('home-assistant-main');
5
- root = root && root.shadowRoot;
6
- root = root && root.querySelector('app-drawer-layout partial-panel-resolver');
7
- root = root && root.shadowRoot || root;
8
- root = root && root.querySelector('ha-panel-lovelace');
9
- root = root && root.shadowRoot;
10
- root = root && root.querySelector('hui-root');
11
- if (root) {
12
- const ll = root.lovelace;
13
- ll.current_view = root.___curView;
14
- return ll;
15
- }
16
- return null;
17
- }
4
+ const root = document
5
+ .querySelector("home-assistant")
6
+ ?.shadowRoot?.querySelector("home-assistant-main")?.shadowRoot;
7
+
8
+ const resolver =
9
+ root?.querySelector("ha-drawer partial-panel-resolver") ||
10
+ root?.querySelector("app-drawer-layout partial-panel-resolver");
11
+
12
+ const huiRoot = (resolver?.shadowRoot || resolver)
13
+ ?.querySelector<HuiRootElement>("ha-panel-lovelace")
14
+ ?.shadowRoot?.querySelector<HuiRootElement>("hui-root");
15
+
16
+ if (huiRoot) {
17
+ const ll = huiRoot.lovelace;
18
+ ll.current_view = huiRoot.___curView;
19
+ return ll;
20
+ }
21
+
22
+ return null;
23
+ };
@@ -1,8 +1,9 @@
1
- import { HomeAssistant, ActionConfig } from "./types";
1
+ import { HomeAssistant, ActionConfig, TurnOnActionConfig, TurnOffActionConfig } from "./types";
2
2
  import { forwardHaptic } from "./haptic";
3
3
  import { fireEvent } from "./fire-event";
4
4
  import { navigate } from "./navigate";
5
5
  import { toggleEntity } from "./toggle-entity";
6
+ import { turnOnOffEntity } from "./turn-on-off-entity";
6
7
 
7
8
  export const handleActionConfig = (
8
9
  node: HTMLElement,
@@ -65,13 +66,29 @@ export const handleActionConfig = (
65
66
  forwardHaptic("success");
66
67
  }
67
68
  break;
69
+ case "turn_on":
70
+ turnOnOffEntity(
71
+ hass,
72
+ (actionConfig as TurnOnActionConfig).entity || config.entity!,
73
+ true
74
+ );
75
+ forwardHaptic("success");
76
+ break;
77
+ case "turn_off":
78
+ turnOnOffEntity(
79
+ hass,
80
+ (actionConfig as TurnOffActionConfig).entity || config.entity!,
81
+ false
82
+ );
83
+ forwardHaptic("success");
84
+ break;
68
85
  case "call-service": {
69
86
  if (!actionConfig.service) {
70
87
  forwardHaptic("failure");
71
88
  return;
72
89
  }
73
90
  const [domain, service] = actionConfig.service.split(".", 2);
74
- hass.callService(domain, service, actionConfig.service_data, actionConfig.target);
91
+ hass.callService(domain, service, actionConfig.data, actionConfig.target);
75
92
  forwardHaptic("success");
76
93
  break;
77
94
  }
@@ -84,7 +84,7 @@ export const handleClick = (
84
84
  return;
85
85
  }
86
86
  const [domain, service] = actionConfig.service.split(".", 2);
87
- const serviceData = { ...actionConfig.service_data };
87
+ const serviceData = { ...actionConfig.data };
88
88
  if (serviceData.entity_id === "entity") {
89
89
  serviceData.entity_id = config.entity;
90
90
  }
@@ -7,7 +7,7 @@
7
7
  * @param {number} top The coordinate to scroll to, along the y-axis.
8
8
  * @param {boolean} smooth true if the scroll position should be smoothly adjusted.
9
9
  */
10
- export default function scrollToTarget(element, target) {
10
+ export function scrollToTarget(element, target) {
11
11
  // the scroll event will trigger _updateScrollState directly,
12
12
  // However, _updateScrollState relies on the previous `scrollTop` to update the states.
13
13
  // Calling _updateScrollState will ensure that the states are synced correctly.
package/src/state_icon.ts CHANGED
@@ -8,7 +8,7 @@ import { inputDateTimeIcon } from "./input_datetime_icon";
8
8
  import { domainIcon } from "./domain_icons";
9
9
 
10
10
  const domainIcons = {
11
- binary_sensor: binarySensorIcon,
11
+ binary_sensor: (stateObj: HassEntity) => binarySensorIcon(stateObj.state, stateObj),
12
12
  cover: coverIcon,
13
13
  sensor: sensorIcon,
14
14
  input_datetime: inputDateTimeIcon,
@@ -1,9 +1,7 @@
1
1
  //REF: https://github.com/home-assistant/frontend/blob/dev/src/common/translations/localize.ts
2
2
 
3
3
  import IntlMessageFormat from "intl-messageformat";
4
- import { Resources } from "../types";
5
-
6
- export type LocalizeFunc = (key: string, ...args: any[]) => string;
4
+ import { LocalizeFunc, Resources } from "../types";
7
5
 
8
6
  interface FormatType {
9
7
  [format: string]: any;