diff --git a/src/common/ensure-array.ts b/src/common/array/ensure-array.ts similarity index 100% rename from src/common/ensure-array.ts rename to src/common/array/ensure-array.ts diff --git a/src/common/array/literal-includes.ts b/src/common/array/literal-includes.ts new file mode 100644 index 0000000000..c73648586a --- /dev/null +++ b/src/common/array/literal-includes.ts @@ -0,0 +1,5 @@ +// Creates a type predicate function for determining if an array literal includes a given value +export const arrayLiteralIncludes = + (array: T) => + (searchElement: unknown, fromIndex?: number): searchElement is T[number] => + array.includes(searchElement as T[number], fromIndex); diff --git a/src/common/translations/localize.ts b/src/common/translations/localize.ts index de307f90f7..eec0503178 100644 --- a/src/common/translations/localize.ts +++ b/src/common/translations/localize.ts @@ -30,7 +30,6 @@ export type LocalizeKeys = | `ui.panel.config.dashboard.${string}` | `ui.panel.config.devices.${string}` | `ui.panel.config.energy.${string}` - | `ui.panel.config.helpers.${string}` | `ui.panel.config.info.${string}` | `ui.panel.config.logs.${string}` | `ui.panel.config.lovelace.${string}` diff --git a/src/components/entity/ha-statistic-picker.ts b/src/components/entity/ha-statistic-picker.ts index 533f6c2efc..a19ee9e011 100644 --- a/src/components/entity/ha-statistic-picker.ts +++ b/src/components/entity/ha-statistic-picker.ts @@ -3,7 +3,7 @@ import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit"; import { customElement, property, query, state } from "lit/decorators"; import memoizeOne from "memoize-one"; -import { ensureArray } from "../../common/ensure-array"; +import { ensureArray } from "../../common/array/ensure-array"; import { fireEvent } from "../../common/dom/fire_event"; import { stringCompare } from "../../common/string/compare"; import { diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts index 3ad3baf159..d5dd2c8630 100644 --- a/src/components/ha-target-picker.ts +++ b/src/components/ha-target-picker.ts @@ -18,7 +18,7 @@ import { css, CSSResultGroup, html, LitElement, unsafeCSS } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../common/dom/fire_event"; -import { ensureArray } from "../common/ensure-array"; +import { ensureArray } from "../common/array/ensure-array"; import { computeDomain } from "../common/entity/compute_domain"; import { computeStateName } from "../common/entity/compute_state_name"; import { diff --git a/src/components/trace/hat-script-graph.ts b/src/components/trace/hat-script-graph.ts index 5473d4cbb5..4698396819 100644 --- a/src/components/trace/hat-script-graph.ts +++ b/src/components/trace/hat-script-graph.ts @@ -25,7 +25,7 @@ import { import { css, html, LitElement, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; -import { ensureArray } from "../../common/ensure-array"; +import { ensureArray } from "../../common/array/ensure-array"; import { Condition, Trigger } from "../../data/automation"; import { Action, diff --git a/src/data/automation_i18n.ts b/src/data/automation_i18n.ts index 78cd7ea8e8..564d5bf95d 100644 --- a/src/data/automation_i18n.ts +++ b/src/data/automation_i18n.ts @@ -1,6 +1,6 @@ import { formatDuration } from "../common/datetime/format_duration"; import secondsToDuration from "../common/datetime/seconds_to_duration"; -import { ensureArray } from "../common/ensure-array"; +import { ensureArray } from "../common/array/ensure-array"; import { computeStateName } from "../common/entity/compute_state_name"; import type { HomeAssistant } from "../types"; import { Condition, Trigger } from "./automation"; diff --git a/src/data/script.ts b/src/data/script.ts index 52a3f10f29..ed0a2c8e6a 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -15,6 +15,7 @@ import { Describe, boolean, } from "superstruct"; +import { arrayLiteralIncludes } from "../common/array/literal-includes"; import { navigate } from "../common/navigate"; import { HomeAssistant } from "../types"; import { @@ -28,11 +29,7 @@ import { BlueprintInput } from "./blueprint"; export const MODES = ["single", "restart", "queued", "parallel"] as const; export const MODES_MAX = ["queued", "parallel"] as const; - -export const isMaxMode = ( - mode: typeof MODES[number] -): mode is typeof MODES_MAX[number] => - MODES_MAX.includes(mode as typeof MODES_MAX[number]); +export const isMaxMode = arrayLiteralIncludes(MODES_MAX); export const baseActionStruct = object({ alias: optional(string()), diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts index 163f13e2e0..1d99a9c2d5 100644 --- a/src/data/script_i18n.ts +++ b/src/data/script_i18n.ts @@ -1,6 +1,6 @@ import { formatDuration } from "../common/datetime/format_duration"; import secondsToDuration from "../common/datetime/seconds_to_duration"; -import { ensureArray } from "../common/ensure-array"; +import { ensureArray } from "../common/array/ensure-array"; import { computeStateName } from "../common/entity/compute_state_name"; import { isTemplate } from "../common/string/has-template"; import { HomeAssistant } from "../types"; diff --git a/src/panels/config/automation/action/types/ha-automation-action-choose.ts b/src/panels/config/automation/action/types/ha-automation-action-choose.ts index b9b15a384d..bbc9b5a039 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-choose.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-choose.ts @@ -2,7 +2,7 @@ import { mdiDelete, mdiPlus } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import { ensureArray } from "../../../../../common/ensure-array"; +import { ensureArray } from "../../../../../common/array/ensure-array"; import "../../../../../components/ha-icon-button"; import { Condition } from "../../../../../data/automation"; import { Action, ChooseAction } from "../../../../../data/script"; diff --git a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts index ceabac3ce7..89fdb53585 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts @@ -10,7 +10,7 @@ import { ActionElement, handleChangeEvent } from "../ha-automation-action-row"; import "../../../../../components/ha-duration-input"; import { createDurationData } from "../../../../../common/datetime/create_duration_data"; import { TimeChangedEvent } from "../../../../../components/ha-base-time-input"; -import { ensureArray } from "../../../../../common/ensure-array"; +import { ensureArray } from "../../../../../common/array/ensure-array"; @customElement("ha-automation-action-wait_for_trigger") export class HaWaitForTriggerAction diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts b/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts index 8c5e54f645..3eb72d48d2 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts @@ -3,7 +3,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import { ensureArray } from "../../../../../common/ensure-array"; +import { ensureArray } from "../../../../../common/array/ensure-array"; import "../../../../../components/ha-select"; import type { AutomationConfig, diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts index abce03227e..b17d3bfd71 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts @@ -11,7 +11,7 @@ import { union, } from "superstruct"; import memoizeOne from "memoize-one"; -import { ensureArray } from "../../../../../common/ensure-array"; +import { ensureArray } from "../../../../../common/array/ensure-array"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { hasTemplate } from "../../../../../common/string/has-template"; import { StateTrigger } from "../../../../../data/automation"; diff --git a/src/panels/config/helpers/const.ts b/src/panels/config/helpers/const.ts index 87b03eff10..82992ca6b4 100644 --- a/src/panels/config/helpers/const.ts +++ b/src/panels/config/helpers/const.ts @@ -1,3 +1,4 @@ +import { arrayLiteralIncludes } from "../../../common/array/literal-includes"; import type { Counter } from "../../../data/counter"; import type { InputBoolean } from "../../../data/input_boolean"; import type { InputButton } from "../../../data/input_button"; @@ -18,7 +19,10 @@ export const HELPER_DOMAINS = [ "counter", "timer", "schedule", -]; +] as const; + +export type HelperDomain = typeof HELPER_DOMAINS[number]; +export const isHelperDomain = arrayLiteralIncludes(HELPER_DOMAINS); export type Helper = | InputBoolean diff --git a/src/panels/config/helpers/dialog-helper-detail.ts b/src/panels/config/helpers/dialog-helper-detail.ts index 672f40a26e..ab0e7c5d64 100644 --- a/src/panels/config/helpers/dialog-helper-detail.ts +++ b/src/panels/config/helpers/dialog-helper-detail.ts @@ -25,7 +25,7 @@ import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-c import { haStyleDialog } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { brandsUrl } from "../../../util/brands-url"; -import { Helper } from "./const"; +import { Helper, HelperDomain } from "./const"; import "./forms/ha-counter-form"; import "./forms/ha-input_boolean-form"; import "./forms/ha-input_button-form"; @@ -37,7 +37,18 @@ import "./forms/ha-schedule-form"; import "./forms/ha-timer-form"; import type { ShowDialogHelperDetailParams } from "./show-dialog-helper-detail"; -const HELPERS = { +type HelperCreators = { + [domain in HelperDomain]: ( + hass: HomeAssistant, + // Not properly typed because there is currently a mismatch for this._item between: + // 1. Type passed to form should be Helper + // 2. Type received by creator should be MutableParams version + // The two are not compatible. + params: any + ) => Promise; +}; + +const HELPERS: HelperCreators = { input_boolean: createInputBoolean, input_button: createInputButton, input_text: createInputText, @@ -57,7 +68,7 @@ export class DialogHelperDetail extends LitElement { @state() private _opened = false; - @state() private _domain?: string; + @state() private _domain?: HelperDomain; @state() private _error?: string; @@ -127,7 +138,7 @@ export class DialogHelperDetail extends LitElement { } else { const items: [string, string][] = []; - for (const helper of Object.keys(HELPERS)) { + for (const helper of Object.keys(HELPERS) as (keyof typeof HELPERS)[]) { items.push([ helper, this.hass.localize(`ui.panel.config.helpers.types.${helper}`) || diff --git a/src/panels/config/helpers/ha-config-helpers.ts b/src/panels/config/helpers/ha-config-helpers.ts index 4861cffa72..8e8c8268fe 100644 --- a/src/panels/config/helpers/ha-config-helpers.ts +++ b/src/panels/config/helpers/ha-config-helpers.ts @@ -35,7 +35,7 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { HomeAssistant, Route } from "../../../types"; import { configSections } from "../ha-panel-config"; import "../integrations/ha-integration-overflow-menu"; -import { HELPER_DOMAINS } from "./const"; +import { HelperDomain, isHelperDomain } from "./const"; import { showHelperDetailDialog } from "./show-dialog-helper-detail"; // This groups items by a key but only returns last entry per key. @@ -118,7 +118,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { sortable: true, width: "25%", filterable: true, - template: (type, row) => + template: (type: HelperDomain, row) => row.configEntry ? domainToName(localize, type) : html` @@ -243,7 +243,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { if (!domain) { return; } - if (HELPER_DOMAINS.includes(domain)) { + if (isHelperDomain(domain)) { showHelperDetailDialog(this, { domain, }); @@ -330,7 +330,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { const newStates = Object.values(this.hass!.states).filter( (entity) => extraEntities.has(entity.entity_id) || - HELPER_DOMAINS.includes(computeStateDomain(entity)) + isHelperDomain(computeStateDomain(entity)) ); if ( diff --git a/src/panels/config/helpers/show-dialog-helper-detail.ts b/src/panels/config/helpers/show-dialog-helper-detail.ts index bbee0bc619..da1942c108 100644 --- a/src/panels/config/helpers/show-dialog-helper-detail.ts +++ b/src/panels/config/helpers/show-dialog-helper-detail.ts @@ -1,10 +1,11 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { DataEntryFlowDialogParams } from "../../../dialogs/config-flow/show-dialog-data-entry-flow"; +import { HelperDomain } from "./const"; export const loadHelperDetailDialog = () => import("./dialog-helper-detail"); export interface ShowDialogHelperDetailParams { - domain?: string; + domain?: HelperDomain; // Only used for config entries dialogClosedCallback?: DataEntryFlowDialogParams["dialogClosedCallback"]; } diff --git a/src/panels/config/integrations/ha-config-integrations.ts b/src/panels/config/integrations/ha-config-integrations.ts index 0ea91c848b..3ba289ae99 100644 --- a/src/panels/config/integrations/ha-config-integrations.ts +++ b/src/panels/config/integrations/ha-config-integrations.ts @@ -70,7 +70,7 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant, Route } from "../../../types"; import { configSections } from "../ha-panel-config"; -import { HELPER_DOMAINS } from "../helpers/const"; +import { isHelperDomain } from "../helpers/const"; import "./ha-config-flow-card"; import "./ha-ignored-config-entry-card"; import "./ha-integration-card"; @@ -785,7 +785,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { } // If not an integration or supported brand, try helper else show alert - if (HELPER_DOMAINS.includes(domain)) { + if (isHelperDomain(domain)) { navigate(`/config/helpers/add?domain=${domain}`, { replace: true, }); diff --git a/src/panels/history/ha-panel-history.ts b/src/panels/history/ha-panel-history.ts index 95573534bd..8f07895d24 100644 --- a/src/panels/history/ha-panel-history.ts +++ b/src/panels/history/ha-panel-history.ts @@ -18,7 +18,7 @@ import { css, html, LitElement, PropertyValues } from "lit"; import { property, state } from "lit/decorators"; import { firstWeekdayIndex } from "../../common/datetime/first_weekday"; import { LocalStorage } from "../../common/decorators/local-storage"; -import { ensureArray } from "../../common/ensure-array"; +import { ensureArray } from "../../common/array/ensure-array"; import { navigate } from "../../common/navigate"; import { createSearchParam, diff --git a/src/panels/logbook/ha-logbook.ts b/src/panels/logbook/ha-logbook.ts index 025d8369ce..bfeaf28c67 100644 --- a/src/panels/logbook/ha-logbook.ts +++ b/src/panels/logbook/ha-logbook.ts @@ -1,7 +1,7 @@ import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; -import { ensureArray } from "../../common/ensure-array"; +import { ensureArray } from "../../common/array/ensure-array"; import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { throttle } from "../../common/util/throttle"; import "../../components/ha-circular-progress"; diff --git a/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts index 0f911d8826..a491318179 100644 --- a/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts @@ -20,7 +20,7 @@ import { union, } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { ensureArray } from "../../../../common/ensure-array"; +import { ensureArray } from "../../../../common/array/ensure-array"; import type { LocalizeFunc } from "../../../../common/translations/localize"; import { deepEqual } from "../../../../common/util/deep-equal"; import {