Compare commits

..

4 Commits

Author SHA1 Message Date
Petar Petrov 94bc9d8567 Update src/panels/config/network/ha-config-http-form.ts
Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2026-05-12 08:39:01 +03:00
Petar Petrov 18268c67dc Surface fetch errors and validate before saving in HTTP config form 2026-05-11 14:50:20 +03:00
Petar Petrov 75a2331c87 Add HTTP server settings to the network panel 2026-05-11 14:06:52 +03:00
Petar Petrov 40fa954542 Fix focus loss in ha-input-multi when items change 2026-05-11 14:06:27 +03:00
248 changed files with 1434 additions and 1670 deletions
-2
View File
@@ -1,4 +1,3 @@
/* global process */
// Tasks to generate entry HTML
import {
@@ -26,7 +25,6 @@ const SAFARI_TO_MACOS = {
16: [11, 0, 0],
17: [12, 0, 0],
18: [13, 0, 0],
26: [26, 0, 0],
};
const getCommonTemplateVars = () => {
+4 -4
View File
@@ -137,11 +137,11 @@
"@bundle-stats/plugin-webpack-filter": "4.22.1",
"@eslint/js": "10.0.1",
"@html-eslint/eslint-plugin": "0.60.0",
"@lokalise/node-api": "16.0.0",
"@lokalise/node-api": "15.7.1",
"@octokit/auth-oauth-device": "8.0.3",
"@octokit/plugin-retry": "8.1.0",
"@octokit/rest": "22.0.1",
"@rsdoctor/rspack-plugin": "1.5.10",
"@rsdoctor/rspack-plugin": "1.5.9",
"@rspack/core": "2.0.2",
"@rspack/dev-server": "2.0.1",
"@types/babel__plugin-transform-runtime": "7.9.5",
@@ -186,7 +186,7 @@
"husky": "9.1.7",
"jsdom": "29.1.1",
"jszip": "3.10.1",
"lint-staged": "17.0.3",
"lint-staged": "17.0.2",
"lit-analyzer": "2.0.3",
"lodash.merge": "4.6.2",
"lodash.template": "4.18.1",
@@ -197,7 +197,7 @@
"serve": "14.2.6",
"sinon": "22.0.0",
"tar": "7.5.15",
"terser-webpack-plugin": "5.6.0",
"terser-webpack-plugin": "5.5.0",
"ts-lit-plugin": "2.0.2",
"typescript": "6.0.3",
"typescript-eslint": "8.59.2",
-38
View File
@@ -1,17 +1,3 @@
import {
mdiBattery,
mdiBattery10,
mdiBattery20,
mdiBattery30,
mdiBattery40,
mdiBattery50,
mdiBattery60,
mdiBattery70,
mdiBattery80,
mdiBattery90,
mdiBatteryAlertVariantOutline,
mdiBatteryUnknown,
} from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
const BATTERY_ICONS = {
@@ -26,18 +12,6 @@ const BATTERY_ICONS = {
90: "mdi:battery-90",
100: "mdi:battery",
};
const BATTERY_ICON_PATHS = {
10: mdiBattery10,
20: mdiBattery20,
30: mdiBattery30,
40: mdiBattery40,
50: mdiBattery50,
60: mdiBattery60,
70: mdiBattery70,
80: mdiBattery80,
90: mdiBattery90,
100: mdiBattery,
};
const BATTERY_CHARGING_ICONS = {
10: "mdi:battery-charging-10",
20: "mdi:battery-charging-20",
@@ -83,15 +57,3 @@ export const batteryLevelIcon = (
}
return BATTERY_ICONS[batteryRound];
};
export const batteryLevelIconPath = (batteryLevel: number | string): string => {
const batteryValue = Number(batteryLevel);
if (isNaN(batteryValue)) {
return mdiBatteryUnknown;
}
if (batteryValue <= 5) {
return mdiBatteryAlertVariantOutline;
}
const batteryRound = Math.round(batteryValue / 10) * 10;
return BATTERY_ICON_PATHS[batteryRound];
};
@@ -137,10 +137,7 @@ export const computeEntityPickerDisplay = (
hass.floors
);
const isRTL = computeRTL(
hass.language,
hass.translationMetadata.translations
);
const isRTL = computeRTL(hass);
const primary = entityName || deviceName || stateObj.entity_id;
const secondary =
+6 -10
View File
@@ -1,20 +1,16 @@
import type { LitElement } from "lit";
import type { HomeAssistant, Translation } from "../../types";
import type { HomeAssistant } from "../../types";
export function computeRTL(
language = "en",
translations: Record<string, Translation>
) {
if (translations[language]) {
return translations[language].isRTL || false;
export function computeRTL(hass: HomeAssistant) {
const lang = hass.language || "en";
if (hass.translationMetadata.translations[lang]) {
return hass.translationMetadata.translations[lang].isRTL || false;
}
return false;
}
export function computeRTLDirection(hass: HomeAssistant) {
return emitRTLDirection(
computeRTL(hass.language, hass.translationMetadata.translations)
);
return emitRTLDirection(computeRTL(hass));
}
export function emitRTLDirection(rtl: boolean) {
@@ -293,10 +293,7 @@ export class StateHistoryChartLine extends LitElement {
(changedProps.has("hass") &&
this._hasEntityStatesChanged(changedProps.get("hass")))
) {
const rtl = computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
);
const rtl = computeRTL(this.hass);
let minYAxis: number | ((values: { min: number }) => number) | undefined =
this.minYAxis;
let maxYAxis: number | ((values: { max: number }) => number) | undefined =
@@ -144,10 +144,7 @@ export class StateHistoryChartTimeline extends LitElement {
"ui.components.history_charts.duration"
)}: ${millisecondsToDuration(durationInMs)}`;
const markerLocalized = !computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
)
const markerLocalized = !computeRTL(this.hass)
? marker
: `<span style="direction: rtl;display:inline-block;margin-right:4px;margin-inline-end:4px;border-radius:10px;width:10px;height:10px;background-color:${color};"></span>`;
@@ -170,12 +167,11 @@ export class StateHistoryChartTimeline extends LitElement {
public willUpdate(changedProps: PropertyValues) {
if (
this.isConnected &&
(changedProps.has("startTime") ||
changedProps.has("endTime") ||
changedProps.has("data") ||
this._chartTime <
new Date(this.endTime.getTime() - MIN_TIME_BETWEEN_UPDATES))
changedProps.has("startTime") ||
changedProps.has("endTime") ||
changedProps.has("data") ||
this._chartTime <
new Date(this.endTime.getTime() - MIN_TIME_BETWEEN_UPDATES)
) {
// If the line is more than 5 minutes old, re-gen it
// so the X axis grows even if there is no new data
@@ -202,10 +198,7 @@ export class StateHistoryChartTimeline extends LitElement {
? Math.max(this.paddingYAxis, this._yWidth)
: 0;
const labelMargin = 5;
const rtl = computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
);
const rtl = computeRTL(this.hass);
this._chartOptions = {
xAxis: {
type: "time",
+31 -108
View File
@@ -13,9 +13,7 @@ import { isComponentLoaded } from "../../common/config/is_component_loaded";
import type { HASSDomEvent } from "../../common/dom/fire_event";
import { fireEvent } from "../../common/dom/fire_event";
import { formatDate } from "../../common/datetime/format_date";
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
import { formatTimeWithSeconds } from "../../common/datetime/format_time";
import {
formatNumber,
getNumberFormatOptions,
@@ -243,8 +241,6 @@ export class StatisticsChart extends LitElement {
private _renderTooltip = (params: any) => {
const rendered: Record<string, boolean> = {};
const chartIsBar = this.chartType.startsWith("bar");
const period = this.period;
const unit = this.unit
? `${blankBeforeUnit(this.unit, this.hass.locale)}${this.unit}`
: "";
@@ -256,67 +252,8 @@ export class StatisticsChart extends LitElement {
const statisticId = this._statisticIds[param.seriesIndex];
const stateObj = this.hass.states[statisticId];
const entry = this.hass.entities[statisticId];
let rawValue: string;
let rawTime: string;
if (chartIsBar) {
// For bar charts value is always second value.
rawValue = String(param.value[1]);
// Time value is third value (un-shifted date) if given, otherwise first value
let startTime: Date;
let endTime: Date | undefined;
if (param.value[2]) {
startTime = new Date(param.value[2]);
if (param.value[3]) {
endTime = new Date(param.value[3]);
}
} else {
startTime = new Date(param.value[0]);
}
if (
period === "year" ||
period === "month" ||
period === "week" ||
period === "day"
) {
// For year/month/day periods, show only the date
rawTime =
formatDate(startTime, this.hass.locale, this.hass.config) +
(endTime && period !== "day"
? ` ${formatDate(
endTime,
this.hass.locale,
this.hass.config
)}`
: "") +
"<br>";
} else {
// For other time periods, include time in render, and optionally show range
// if we have an end time.
rawTime =
formatDateTimeWithSeconds(
startTime,
this.hass.locale,
this.hass.config
) +
(endTime
? ` ${formatTimeWithSeconds(
endTime,
this.hass.locale,
this.hass.config
)}`
: "") +
"<br>";
}
} else {
// For lines max series can have 3 values, as the second value is the max-min to form a band
rawValue = String(param.value[2] ?? param.value[1]);
// Time value is always first value
rawTime = `${formatDateTimeWithSeconds(
new Date(param.value[0]),
this.hass.locale,
this.hass.config
)} <br>`;
}
// max series can have 3 values, as the second value is the max-min to form a band
const rawValue = String(param.value[2] ?? param.value[1]);
const options = getNumberFormatOptions(stateObj, entry) ?? {
maximumFractionDigits: 2,
@@ -328,7 +265,14 @@ export class StatisticsChart extends LitElement {
options
)}${unit}`;
const time = index === 0 ? rawTime : "";
const time =
index === 0
? formatDateTimeWithSeconds(
new Date(param.value[0]),
this.hass.locale,
this.hass.config
) + "<br>"
: "";
return `${time}${param.marker} ${param.seriesName}: ${value}`;
})
.filter(Boolean)
@@ -424,12 +368,7 @@ export class StatisticsChart extends LitElement {
nameTextStyle: {
align: "left",
},
position: computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
)
? "right"
: "left",
position: computeRTL(this.hass) ? "right" : "left",
scale:
this.chartType.startsWith("line") ||
this.logarithmicScale ||
@@ -567,53 +506,33 @@ export class StatisticsChart extends LitElement {
const statDataSets: (LineSeriesOption | BarSeriesOption)[] = [];
const statLegendData: typeof legendData = [];
// Place bars at centre of their specified time range if this is a bar chart
// and the period is 5minute or hour.
const centerBars =
chartType === "bar" &&
(this.period === "5minute" || this.period === "hour");
const pushData = (
start: Date, // Data point start time
end: Date, // Data point end time
limit: Date, // Limit for end time (e.g. now)
start: Date,
end: Date,
dataValues: (number | null)[][]
) => {
if (!dataValues.length) return;
// Limit for time range is lesser of overall limit and data point end
limit = end.getTime() < limit.getTime() ? end : limit;
if (start.getTime() > limit.getTime()) {
if (start > end) {
// Drop data points that are after the requested endTime. This could happen if
// endTime is "now" and client time is not in sync with server time.
return;
}
statDataSets.forEach((d, i) => {
if (chartType === "line") {
if (
prevEndTime &&
prevValues &&
prevEndTime.getTime() !== start.getTime()
) {
// if the end of the previous data doesn't match the start of the current data,
// we have to draw a gap so add a value at the end time, and then an empty value.
d.data!.push([prevEndTime, ...prevValues[i]!]);
d.data!.push([prevEndTime, null]);
}
d.data!.push([start, ...dataValues[i]!]);
} else {
let time = start;
if (centerBars) {
// If centering bars, set the time to the midpoint between start and end instead
// of the start time.
time = new Date((start.getTime() + end.getTime()) / 2);
}
// Data value should always be a scalar for bar charts. Pass in
// real start time as extra value to allow formatting tooltip.
d.data!.push([time, dataValues[i][0]!, start, end]);
if (
chartType === "line" &&
prevEndTime &&
prevValues &&
prevEndTime.getTime() !== start.getTime()
) {
// if the end of the previous data doesn't match the start of the current data,
// we have to draw a gap so add a value at the end time, and then an empty value.
d.data!.push([prevEndTime, ...prevValues[i]!]);
d.data!.push([prevEndTime, null]);
}
d.data!.push([start, ...dataValues[i]!]);
});
prevValues = dataValues;
prevEndTime = limit;
prevEndTime = end;
};
let color = colors[statistic_id];
@@ -773,7 +692,11 @@ export class StatisticsChart extends LitElement {
dataValues.push(val);
});
if (!this._hiddenStats.has(statistic_id)) {
pushData(startDate, endDate, endTime, dataValues);
pushData(
startDate,
endDate.getTime() < endTime.getTime() ? endDate : endTime,
dataValues
);
}
});
@@ -127,6 +127,7 @@ export class DialogDataTableSettings extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${localize("ui.components.data-table.settings.header")}
@closed=${this._dialogClosed}
+3 -11
View File
@@ -22,14 +22,6 @@ const isOn = (stateObj?: HassEntity) =>
!STATES_OFF.includes(stateObj.state) &&
!isUnavailableState(stateObj.state);
/**
* @element ha-entity-toggle
*
* @cssprop --ha-entity-toggle-switch-width - Width of the switch track. Defaults to `38px`.
* @cssprop --ha-entity-toggle-switch-size - Height of the switch track. Defaults to `20px`.
* @cssprop --ha-entity-toggle-switch-thumb-size - Size of the switch thumb. Defaults to `14px`.
*/
@customElement("ha-entity-toggle")
export class HaEntityToggle extends LitElement {
// hass is not a property so that we only re-render on stateObj changes
@@ -173,9 +165,9 @@ export class HaEntityToggle extends LitElement {
white-space: nowrap;
}
ha-switch {
--ha-switch-width: var(--ha-entity-toggle-switch-width, 38px);
--ha-switch-size: var(--ha-entity-toggle-switch-size, 20px);
--ha-switch-thumb-size: var(--ha-entity-toggle-switch-thumb-size, 14px);
--ha-switch-width: 38px;
--ha-switch-size: 20px;
--ha-switch-thumb-size: 14px;
}
ha-icon-button {
--ha-icon-button-size: 40px;
@@ -130,6 +130,7 @@ export class HaStateLabelBadge extends LitElement {
? html`<ha-state-icon
.icon=${this.icon}
.stateObj=${entityState}
.hass=${this.hass}
></ha-state-icon>`
: ""}
${value && !image && !showIcon
+2 -8
View File
@@ -210,10 +210,7 @@ export class HaStatisticPicker extends LitElement {
});
}
const isRTL = computeRTL(
hass.language,
hass.translationMetadata.translations
);
const isRTL = computeRTL(hass);
const output: StatisticComboBoxItem[] = [];
@@ -356,10 +353,7 @@ export class HaStatisticPicker extends LitElement {
this.hass.floors
);
const isRTL = computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
);
const isRTL = computeRTL(this.hass);
const primary = entityName || deviceName || statisticId;
const secondary = [areaName, entityName ? deviceName : undefined]
+1
View File
@@ -98,6 +98,7 @@ export class StateBadge extends LitElement {
const domain = stateObj ? computeStateDomain(stateObj) : undefined;
return html`<ha-state-icon
.hass=${this.hass}
style=${styleMap(this._iconStyle)}
data-domain=${ifDefined(domain)}
data-state=${ifDefined(stateObj?.state)}
+5
View File
@@ -4,6 +4,7 @@ import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { listenMediaQuery } from "../common/dom/media_query";
import { internationalizationContext } from "../data/context";
import type { HomeAssistant } from "../types";
import "./ha-bottom-sheet";
import "./ha-dialog-header";
import "./ha-icon-button";
@@ -81,6 +82,8 @@ export const ADAPTIVE_DIALOG_MEDIA_QUERY =
*/
@customElement("ha-adaptive-dialog")
export class HaAdaptiveDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: "aria-labelledby" })
public ariaLabelledBy?: string;
@@ -199,6 +202,7 @@ export class HaAdaptiveDialog extends LitElement {
.ariaLabelledBy=${this._defaultAriaLabelledBy}
.ariaDescribedBy=${this.ariaDescribedBy}
.flexContent=${this.flexContent}
.hass=${this.hass}
.open=${this.open}
.preventScrimClose=${this.preventScrimClose}
>
@@ -217,6 +221,7 @@ export class HaAdaptiveDialog extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this.open}
.type=${this.type}
.width=${this.width}
+2 -4
View File
@@ -184,10 +184,7 @@ export class HaAreaControlsPicker extends LitElement {
const allEntityIds = Object.values(controlEntities).flat();
const uniqueEntityIds = Array.from(new Set(allEntityIds));
const isRTL = computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
);
const isRTL = computeRTL(this.hass);
uniqueEntityIds.forEach((entityId) => {
if (isSelected(entityId)) {
@@ -264,6 +261,7 @@ export class HaAreaControlsPicker extends LitElement {
${item.type === "entity" && item.stateObj
? html`<ha-state-icon
slot="start"
.hass=${this.hass}
.stateObj=${item.stateObj}
></ha-state-icon>`
: item.domain
+5 -7
View File
@@ -7,6 +7,7 @@ import { fireEvent } from "../common/dom/fire_event";
import { SwipeGestureRecognizer } from "../common/util/swipe-gesture-recognizer";
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
import { haStyleScrollbar } from "../resources/styles";
import type { HomeAssistant } from "../types";
export const BOTTOM_SHEET_ANIMATION_DURATION_MS = 300;
@@ -46,6 +47,8 @@ const SWIPE_LOCKED_CLASSES = new Set(["volume-slider-container", "forecast"]);
*/
@customElement("ha-bottom-sheet")
export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: "aria-labelledby" })
public ariaLabelledBy?: string;
@@ -64,11 +67,6 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
@state() private _sliderInteractionActive = false;
// disabled till iOS app fix the "focus_element" implementation
// @state()
// @consume({ context: configContext, subscribe: true })
// private _hassConfig?: ContextType<typeof configContext>;
@query("#drawer") private _drawer!: HTMLElement;
@query("#body") private _bodyElement!: HTMLDivElement;
@@ -92,13 +90,13 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
requestAnimationFrame(() => {
// disabled till iOS app fix the "focus_element" implementation
// if (this._hassConfig?.auth.external && isIosApp(this._hassConfig.auth.external)) {
// if (this.hass && isIosApp(this.hass.auth.external)) {
// const element = this.renderRoot.querySelector("[autofocus]");
// if (element !== null) {
// if (!element.id) {
// element.id = "ha-bottom-sheet-autofocus";
// }
// this._hassConfig.auth.external.fireMessage({
// this.hass.auth.external?.fireMessage({
// type: "focus_element",
// payload: {
// element_id: element.id,
+1 -6
View File
@@ -39,12 +39,7 @@ export class HaEntitiesDisplayEditor extends LitElement {
const items: DisplayItem[] = entities.map((entity) => ({
value: entity.entity_id,
label: computeStateName(entity),
icon: entityIcon(
this.hass.entities,
this.hass.config,
this.hass.connection,
entity
),
icon: entityIcon(this.hass, entity),
}));
const value: DisplayValue = {
+5 -1
View File
@@ -122,7 +122,11 @@ export class HaFilterEntities extends LitElement {
.selected=${this.value?.includes(entity.entity_id) ?? false}
graphic="icon"
>
<ha-state-icon slot="graphic" .stateObj=${entity}></ha-state-icon>
<ha-state-icon
slot="graphic"
.hass=${this.hass}
.stateObj=${entity}
></ha-state-icon>
${computeStateName(entity)}
</ha-check-list-item>`;
+1 -4
View File
@@ -137,10 +137,7 @@ export class HaFilterFloorAreas extends LitElement {
.selected=${this.value?.areas?.includes(area.area_id) || false}
.type=${"areas"}
class=${classMap({
rtl: computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
),
rtl: computeRTL(this.hass),
floor: hasFloor,
})}
>
+5
View File
@@ -166,6 +166,7 @@ export class HaRelatedItems extends LitElement {
graphic="icon"
>
<ha-state-icon
.hass=${this.hass}
.stateObj=${entity}
slot="graphic"
></ha-state-icon>
@@ -321,6 +322,7 @@ export class HaRelatedItems extends LitElement {
graphic="icon"
>
<ha-state-icon
.hass=${this.hass}
.stateObj=${group}
slot="graphic"
></ha-state-icon>
@@ -345,6 +347,7 @@ export class HaRelatedItems extends LitElement {
graphic="icon"
>
<ha-state-icon
.hass=${this.hass}
.stateObj=${scene}
slot="graphic"
></ha-state-icon>
@@ -397,6 +400,7 @@ export class HaRelatedItems extends LitElement {
graphic="icon"
>
<ha-state-icon
.hass=${this.hass}
.stateObj=${automation}
slot="graphic"
></ha-state-icon>
@@ -448,6 +452,7 @@ export class HaRelatedItems extends LitElement {
graphic="icon"
>
<ha-state-icon
.hass=${this.hass}
.stateObj=${script}
slot="graphic"
></ha-state-icon>
+1 -6
View File
@@ -63,12 +63,7 @@ export class HaSelectBox extends LitElement {
const selected = option.value === this.value;
const isDark = this.hass?.themes.darkMode || false;
const isRTL = this.hass
? computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
)
: false;
const isRTL = this.hass ? computeRTL(this.hass) : false;
const imageSrc =
typeof option.image === "object"
+6 -10
View File
@@ -36,15 +36,7 @@ export class HaIconSelector extends LitElement {
const placeholder =
this.selector.icon?.placeholder ||
stateObj?.attributes.icon ||
(stateObj &&
until(
entityIcon(
this.hass.entities,
this.hass.config,
this.hass.connection,
stateObj
)
));
(stateObj && until(entityIcon(this.hass, stateObj)));
return html`
<ha-icon-picker
@@ -59,7 +51,11 @@ export class HaIconSelector extends LitElement {
>
${!placeholder && stateObj
? html`
<ha-state-icon slot="start" .stateObj=${stateObj}></ha-state-icon>
<ha-state-icon
slot="start"
.hass=${this.hass}
.stateObj=${stateObj}
></ha-state-icon>
`
: nothing}
</ha-icon-picker>
+16 -11
View File
@@ -523,10 +523,7 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
}
private _renderUserItem(selectedPanel: string) {
const isRTL = computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
);
const isRTL = computeRTL(this.hass);
const isSelected = selectedPanel === "profile";
return html`
@@ -564,9 +561,9 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
id="sidebar-external-config"
>
<ha-svg-icon slot="start" .path=${mdiCellphoneCog}></ha-svg-icon>
<span class="item-text" slot="headline">
${this.hass.localize("ui.sidebar.external_app_configuration")}
</span>
<span class="item-text" slot="headline"
>${this.hass.localize("ui.sidebar.external_app_configuration")}</span
>
</ha-list-item-button>
${!this.alwaysExpand
? this._renderToolTip(
@@ -743,7 +740,6 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
border-radius: var(--ha-border-radius-sm);
--ha-row-item-min-height: var(--ha-space-10);
--ha-row-item-padding-block: 0;
--ha-row-item-padding-inline: var(--ha-space-3);
width: var(--ha-space-12);
position: relative;
transition: width var(--ha-animation-duration-normal) ease;
@@ -844,12 +840,21 @@ class HaSidebar extends SubscribeMixin(ScrollableFadeMixin(LitElement)) {
}
ha-user-badge {
width: 40px;
height: 40px;
width: var(--ha-space-10);
height: var(--ha-space-10);
}
ha-list-item-button.user {
--ha-row-item-padding-inline: var(--ha-space-1) 0;
--ha-row-item-padding-inline: var(--ha-space-2) var(--ha-space-3);
}
ha-list-item-button.user.rtl {
--ha-row-item-padding-inline: var(--ha-space-4) var(--ha-space-3);
}
ha-user-badge {
flex-shrink: 0;
margin-right: calc(var(--ha-space-2) * -1);
}
.spacer {
+13 -32
View File
@@ -1,46 +1,31 @@
import { consume, type ContextType } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement, property } from "lit/decorators";
import { until } from "lit/directives/until";
import { computeStateDomain } from "../common/entity/compute_state_domain";
import {
configContext,
connectionContext,
entitiesContext,
} from "../data/context";
import {
DEFAULT_DOMAIN_ICON,
entityIcon,
FALLBACK_DOMAIN_ICONS,
} from "../data/icons";
import type { HomeAssistant } from "../types";
import "./ha-icon";
import "./ha-svg-icon";
@customElement("ha-state-icon")
export class HaStateIcon extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public stateObj?: HassEntity;
@property({ attribute: false }) public stateValue?: string;
@property() public icon?: string;
@state()
@consume({ context: configContext, subscribe: true })
protected _config?: ContextType<typeof configContext>;
@state()
@consume({ context: connectionContext, subscribe: true })
protected _connection?: ContextType<typeof connectionContext>;
@state()
@consume({ context: entitiesContext, subscribe: true })
protected _entities?: ContextType<typeof entitiesContext>;
protected render() {
const overrideIcon =
this.icon ||
(this.stateObj && this._entities?.[this.stateObj.entity_id]?.icon) ||
(this.stateObj && this.hass?.entities[this.stateObj.entity_id]?.icon) ||
this.stateObj?.attributes.icon;
if (overrideIcon) {
return html`<ha-icon .icon=${overrideIcon}></ha-icon>`;
@@ -48,21 +33,17 @@ export class HaStateIcon extends LitElement {
if (!this.stateObj) {
return nothing;
}
if (!this._config || !this._connection || !this._entities) {
if (!this.hass) {
return this._renderFallback();
}
const icon = entityIcon(
this._entities,
this._config.config,
this._connection.connection,
this.stateObj,
this.stateValue
).then((icn) => {
if (icn) {
return html`<ha-icon .icon=${icn}></ha-icon>`;
const icon = entityIcon(this.hass, this.stateObj, this.stateValue).then(
(icn) => {
if (icn) {
return html`<ha-icon .icon=${icn}></ha-icon>`;
}
return this._renderFallback();
}
return this._renderFallback();
});
);
return html`${until(icon)}`;
}
+1 -4
View File
@@ -1136,10 +1136,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
let rtl = false;
let showEntityId = false;
if (type === "area" || type === "floor") {
rtl = computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
);
rtl = computeRTL(this.hass);
hasFloor =
type === "area" && !!(item as FloorComboBoxItem).area?.floor_id;
}
+1 -1
View File
@@ -78,7 +78,7 @@ class HaInputMulti extends LitElement {
<div class="items">
${repeat(
this._items,
(item, index) => `${item}-${index}`,
(_item, index) => index,
(item, index) => {
const indexSuffix = `${this.itemIndex ? ` ${index + 1}` : ""}`;
return html`
+9 -30
View File
@@ -1,7 +1,7 @@
import { HasSlotController } from "@home-assistant/webawesome/dist/internal/slot";
import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement, property } from "lit/decorators";
/**
* @element ha-row-item
@@ -46,34 +46,13 @@ export class HaRowItem extends LitElement {
protected readonly _slotController = new HasSlotController(
this,
"start",
"end",
"headline",
"supporting-text",
"content"
);
@state() private _hasStart = false;
@state() private _hasEnd = false;
private _onSlotChange(name: "start" | "end") {
return (ev: Event) => {
const slot = ev.target as HTMLSlotElement;
const hasContent = slot
.assignedNodes({ flatten: true })
.some(
(node) =>
node.nodeType === Node.ELEMENT_NODE ||
(node.nodeType === Node.TEXT_NODE &&
(node as Text).textContent?.trim() !== "")
);
if (name === "start") {
this._hasStart = hasContent;
} else {
this._hasEnd = hasContent;
}
};
}
protected render(): TemplateResult {
return this._renderBase(this._renderInner());
}
@@ -86,16 +65,16 @@ export class HaRowItem extends LitElement {
const hasContent = this._slotController.test("content");
return html`
<div part="start" class="start" ?hidden=${!this._hasStart}>
<slot name="start" @slotchange=${this._onSlotChange("start")}></slot>
<div part="start" class="start">
<slot name="start"></slot>
</div>
<div part="content" class="content">
${hasContent
? html`<slot name="content"></slot>`
: this._renderDefaultContent()}
</div>
<div part="end" class="end" ?hidden=${!this._hasEnd}>
<slot name="end" @slotchange=${this._onSlotChange("end")}></slot>
<div part="end" class="end">
<slot name="end"></slot>
</div>
`;
}
@@ -163,8 +142,8 @@ export class HaRowItem extends LitElement {
align-items: center;
flex: 0 0 auto;
}
.start[hidden],
.end[hidden] {
:host(:not(:has([slot="start"]))) .start,
:host(:not(:has([slot="end"]))) .end {
display: none;
}
.headline {
+1
View File
@@ -37,6 +37,7 @@ class HaEntityMarker extends LitElement {
></div>`
: this.showIcon && this.entityId
? html`<ha-state-icon
.hass=${this.hass}
.stateObj=${this.hass?.states[this.entityId]}
></ha-state-icon>`
: !this.entityUnit
@@ -76,7 +76,12 @@ class DialogJoinMediaPlayers extends LitElement {
const entityId = this._entityId;
return html`
<ha-dialog .open=${this._open} flexcontent @closed=${this._dialogClosed}>
<ha-dialog
.hass=${this.hass}
.open=${this._open}
flexcontent
@closed=${this._dialogClosed}
>
<ha-dialog-header show-border slot="header">
<ha-icon-button
.label=${this.hass.localize("ui.common.close")}
@@ -100,6 +100,7 @@ class DialogMediaManage extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
?prevent-scrim-close=${this._uploading || this._deleting}
@closed=${this._dialogClosed}
@@ -77,6 +77,7 @@ class DialogMediaPlayerBrowse extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
width="large"
flexcontent
@@ -59,10 +59,7 @@ class HaMediaPlayerToggle extends LitElement {
icon = mdiSpeakerPause;
}
const isRTL = computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
);
const isRTL = computeRTL(this.hass);
const { primary, secondary } = this._computeDisplayData(
this.entityId,
@@ -1,31 +1,15 @@
import type { HassEntity } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { ensureArray } from "../../../common/array/ensure-array";
import { fireEvent } from "../../../common/dom/fire_event";
import type { DeviceRegistryEntry } from "../../../data/device/device_registry";
import { getDeviceIntegrationLookup } from "../../../data/device/device_registry";
import type { HaEntityPickerEntityFilterFunc } from "../../../data/entity/entity";
import type { EntitySources } from "../../../data/entity/entity_sources";
import { fetchEntitySourcesWithCache } from "../../../data/entity/entity_sources";
import type { TargetSelector } from "../../../data/selector";
import {
filterSelectorDevices,
filterSelectorEntities,
} from "../../../data/selector";
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
import type { HomeAssistant } from "../../../types";
import type { HaDevicePickerDeviceFilterFunc } from "../../device/ha-device-picker";
import "../../ha-adaptive-dialog";
import "../../ha-dialog";
import "../../ha-dialog-header";
import "../../ha-icon-button";
import "../../ha-icon-next";
import "../../ha-md-list";
import "../../ha-md-list-item";
import "../../ha-svg-icon";
import "../../list/ha-list-base";
import "../ha-target-picker-item-row";
import type { TargetDetailsDialogParams } from "./show-dialog-target-details";
@@ -37,12 +21,6 @@ class DialogTargetDetails extends LitElement implements HassDialog {
@state() private _opened = false;
@state() private _entitySources?: EntitySources;
@state() private _entitySourcesLoaded = false;
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
public showDialog(params: TargetDetailsDialogParams): void {
this._params = params;
this._opened = true;
@@ -56,72 +34,6 @@ class DialogTargetDetails extends LitElement implements HassDialog {
private _dialogClosed() {
fireEvent(this, "dialog-closed", { dialog: this.localName });
this._params = undefined;
this._entitySources = undefined;
this._entitySourcesLoaded = false;
}
private _hasIntegration(selector: TargetSelector) {
return (
(selector.target?.entity &&
ensureArray(selector.target.entity).some((e) => e.integration)) ||
(selector.target?.device &&
ensureArray(selector.target.device).some((d) => d.integration))
);
}
protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
if (!changedProperties.has("_params")) {
return;
}
if (
this._params?.selector &&
this._hasIntegration(this._params.selector) &&
!this._entitySourcesLoaded
) {
this._loadEntitySources();
}
}
private async _loadEntitySources(): Promise<void> {
try {
this._entitySources = await fetchEntitySourcesWithCache(this.hass);
} catch (err) {
// eslint-disable-next-line no-console
console.error("Failed to load entity sources for target details", err);
} finally {
this._entitySourcesLoaded = true;
}
}
private _filterEntities = (entity: HassEntity): boolean => {
const target = this._selectorTarget();
if (!target?.entity) {
return true;
}
return ensureArray(target.entity).some((e) =>
filterSelectorEntities(e, entity, this._entitySources)
);
};
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
const target = this._selectorTarget();
if (!target?.device) {
return true;
}
const deviceIntegrations = this._entitySources
? this._deviceIntegrationLookup(
this._entitySources,
Object.values(this.hass.entities)
)
: undefined;
return ensureArray(target.device).some((d) =>
filterSelectorDevices(d, device, deviceIntegrations)
);
};
private _selectorTarget() {
return this._params?.selector?.target || null;
}
protected render() {
@@ -129,86 +41,33 @@ class DialogTargetDetails extends LitElement implements HassDialog {
return nothing;
}
let deviceFilter: HaDevicePickerDeviceFilterFunc | undefined;
let entityFilter: HaEntityPickerEntityFilterFunc | undefined;
let includeDomains: string[] | undefined;
let includeDeviceClasses: string[] | undefined;
let primaryEntitiesOnly: boolean | undefined;
if (this._params.selector) {
deviceFilter = this._filterDevices;
entityFilter = this._filterEntities;
primaryEntitiesOnly = this._params.selector.target?.primary_entities_only;
} else {
deviceFilter = this._params.deviceFilter;
entityFilter = this._params.entityFilter;
includeDomains = this._params.includeDomains;
includeDeviceClasses = this._params.includeDeviceClasses;
primaryEntitiesOnly = this._params.primaryEntitiesOnly;
}
const waitingForSources =
this._params.selector &&
this._hasIntegration(this._params.selector) &&
!this._entitySourcesLoaded;
return html`
<ha-adaptive-dialog
<ha-dialog
.hass=${this.hass}
.open=${this._opened}
header-title=${this.hass.localize(
"ui.components.target-picker.target_details"
)}
header-subtitle=${`${this.hass.localize(
`ui.components.target-picker.type.${this._params.type}`
)}:
${this._params.title}`}
@closed=${this._dialogClosed}
>
<div class="type-wrapper">
<div class="type-label">
${this.hass.localize(
`ui.components.target-picker.type.${this._params.type}`
)}
</div>
<ha-list-base
.ariaLabel=${`${this.hass.localize(`ui.components.target-picker.type.${this._params.type}`)}: ${this._params.title}`}
wrap-focus
>
${waitingForSources
? nothing
: html`
<ha-target-picker-item-row
.hass=${this.hass}
.type=${this._params.type}
.itemId=${this._params.itemId}
.deviceFilter=${deviceFilter}
.entityFilter=${entityFilter}
.includeDomains=${includeDomains}
.includeDeviceClasses=${includeDeviceClasses}
.primaryEntitiesOnly=${primaryEntitiesOnly}
expand
></ha-target-picker-item-row>
`}
</ha-list-base>
</div>
</ha-adaptive-dialog>
<ha-target-picker-item-row
.hass=${this.hass}
.type=${this._params.type}
.itemId=${this._params.itemId}
.deviceFilter=${this._params.deviceFilter}
.entityFilter=${this._params.entityFilter}
.includeDomains=${this._params.includeDomains}
.includeDeviceClasses=${this._params.includeDeviceClasses}
.primaryEntitiesOnly=${this._params.primaryEntitiesOnly}
expand
></ha-target-picker-item-row>
</ha-dialog>
`;
}
static styles = css`
.type-wrapper {
display: flex;
flex-direction: column;
border-radius: var(--ha-border-radius-xl);
border: var(--ha-border-width-sm) solid
var(--ha-color-border-neutral-normal);
overflow: hidden;
}
.type-label {
background-color: var(--ha-color-surface-low);
padding: var(--ha-space-1) var(--ha-space-3);
font-weight: var(--ha-font-weight-bold);
display: flex;
align-items: center;
height: 20px;
}
`;
}
declare global {
@@ -1,14 +1,14 @@
import { fireEvent } from "../../../common/dom/fire_event";
import type { HaEntityPickerEntityFilterFunc } from "../../../data/entity/entity";
import type { TargetSelector } from "../../../data/selector";
import type { TargetType } from "../../../data/target";
import type { HaDevicePickerDeviceFilterFunc } from "../../device/ha-device-picker";
export type NewBackupType = "automatic" | "manual";
export interface TargetDetailsDialogParams {
title: string;
type: TargetType;
itemId: string;
selector?: TargetSelector;
deviceFilter?: HaDevicePickerDeviceFilterFunc;
entityFilter?: HaEntityPickerEntityFilterFunc;
includeDomains?: string[];
@@ -5,7 +5,7 @@ import type { TargetType, TargetTypeFloorless } from "../../data/target";
import type { HomeAssistant } from "../../types";
import type { HaDevicePickerDeviceFilterFunc } from "../device/ha-device-picker";
import "../ha-expansion-panel";
import "../list/ha-list-base";
import "../ha-md-list";
import "./ha-target-picker-item-row";
@customElement("ha-target-picker-item-group")
@@ -66,25 +66,23 @@ export class HaTargetPickerItemGroup extends LitElement {
}
)}
</div>
<ha-list-base>
${Object.entries(this.items).map(([type, items]) =>
items
? items.map(
(item) =>
html`<ha-target-picker-item-row
.hass=${this.hass}
.type=${type as TargetTypeFloorless}
.itemId=${item}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDomains=${this.includeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
.primaryEntitiesOnly=${this.primaryEntitiesOnly}
></ha-target-picker-item-row>`
)
: nothing
)}
</ha-list-base>
${Object.entries(this.items).map(([type, items]) =>
items
? items.map(
(item) =>
html`<ha-target-picker-item-row
.hass=${this.hass}
.type=${type as TargetTypeFloorless}
.itemId=${item}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDomains=${this.includeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
.primaryEntitiesOnly=${this.primaryEntitiesOnly}
></ha-target-picker-item-row>`
)
: nothing
)}
</ha-expansion-panel>`;
}
@@ -98,7 +96,7 @@ export class HaTargetPickerItemGroup extends LitElement {
--expansion-panel-content-padding: 0;
}
ha-expansion-panel::part(summary) {
background-color: var(--ha-color-surface-low);
background-color: var(--ha-color-fill-neutral-quiet-resting);
padding: var(--ha-space-1) var(--ha-space-2);
font-weight: var(--ha-font-weight-bold);
color: var(--secondary-text-color);
@@ -106,6 +104,9 @@ export class HaTargetPickerItemGroup extends LitElement {
justify-content: space-between;
min-height: unset;
}
ha-md-list {
padding: 0;
}
`;
}
@@ -1,24 +1,14 @@
import { consume } from "@lit/context";
import {
mdiChevronLeft,
mdiChevronRight,
mdiClose,
mdiDevices,
mdiHome,
mdiLabel,
mdiMinusBox,
mdiTextureBox,
} from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import {
css,
html,
LitElement,
nothing,
type PropertyValues,
type TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { css, html, LitElement, nothing, type PropertyValues } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
@@ -48,17 +38,18 @@ import {
type ExtractFromTargetResultReferenced,
type TargetType,
} from "../../data/target";
import { showMoreInfoDialog } from "../../dialogs/more-info/show-ha-more-info-dialog";
import { buttonLinkStyle } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import { brandsUrl } from "../../util/brands-url";
import type { HaDevicePickerDeviceFilterFunc } from "../device/ha-device-picker";
import { floorDefaultIconPath } from "../ha-floor-icon";
import "../ha-icon-button";
import "../ha-md-list";
import type { HaMdList } from "../ha-md-list";
import "../ha-md-list-item";
import type { HaMdListItem } from "../ha-md-list-item";
import "../ha-state-icon";
import "../ha-svg-icon";
import "../item/ha-list-item-base";
import "../item/ha-list-item-button";
import { showTargetDetailsDialog } from "./dialog/show-dialog-target-details";
@customElement("ha-target-picker-item-row")
@@ -74,9 +65,6 @@ export class HaTargetPickerItemRow extends LitElement {
@property({ type: Boolean, attribute: "sub-entry", reflect: true })
public subEntry = false;
@property({ attribute: false })
public subLevel = 0;
@property({ type: Boolean, attribute: "hide-context" })
public hideContext = false;
@@ -118,6 +106,12 @@ export class HaTargetPickerItemRow extends LitElement {
@consume({ context: labelsContext, subscribe: true })
_labelRegistry!: LabelRegistryEntry[];
@query("ha-md-list-item") public item?: HaMdListItem;
@query("ha-md-list") public list?: HaMdList;
@query("ha-target-picker-item-row") public itemRow?: HaTargetPickerItemRow;
protected willUpdate(changedProps: PropertyValues<this>) {
if (!this.subEntry && changedProps.has("itemId")) {
this._updateItemData();
@@ -143,130 +137,101 @@ export class HaTargetPickerItemRow extends LitElement {
const replaceable = !this.subEntry && !this.expand;
const content = html`
<div class="icon" slot="start">
${iconPath
? html`<ha-icon .icon=${iconPath}></ha-icon>`
: this._iconImg
? html`<img
alt=${this._domainName || ""}
crossorigin="anonymous"
referrerpolicy="no-referrer"
src=${this._iconImg}
/>`
: fallbackIconPath
? html`<ha-svg-icon .path=${fallbackIconPath}></ha-svg-icon>`
: this.type === "entity"
? html`
<ha-state-icon
.stateObj=${stateObject ||
({
entity_id: this.itemId,
attributes: {},
} as HassEntity)}
>
</ha-state-icon>
`
: nothing}
</div>
return html`
<ha-md-list-item
type=${replaceable ? "button" : "text"}
class=${classMap({
error: notFound,
replaceable,
})}
@click=${replaceable ? this._replaceItem : undefined}
>
<div class="icon" slot="start">
${this.subEntry
? html`
<div class="horizontal-line-wrapper">
<div class="horizontal-line"></div>
</div>
`
: nothing}
${iconPath
? html`<ha-icon .icon=${iconPath}></ha-icon>`
: this._iconImg
? html`<img
alt=${this._domainName || ""}
crossorigin="anonymous"
referrerpolicy="no-referrer"
src=${this._iconImg}
/>`
: fallbackIconPath
? html`<ha-svg-icon .path=${fallbackIconPath}></ha-svg-icon>`
: this.type === "entity"
? html`
<ha-state-icon
.hass=${this.hass}
.stateObj=${stateObject ||
({
entity_id: this.itemId,
attributes: {},
} as HassEntity)}
>
</ha-state-icon>
`
: nothing}
</div>
<div slot="headline">${name}</div>
${notFound || (context && !this.hideContext)
? html`<span slot="supporting-text"
>${notFound
? this.hass.localize(
`ui.components.target-picker.${this.type}_not_found`
)
: context}</span
>`
: nothing}
${stateObject && this.subEntry
? html`<span slot="supporting-text" class="state"
>${this.hass.formatEntityState(stateObject)}</span
>`
: nothing}
${!this.subEntry && entries && showEntities
? html`
<div slot="end" class="summary">
${showEntities &&
!this.expand &&
entries?.referenced_entities.length
? html`<button class="main link" @click=${this._openDetails}>
${this.hass.localize(
"ui.components.target-picker.entities_count",
{
count: entries?.referenced_entities.length,
}
)}
</button>`
: showEntities
? html`<span class="main">
<div slot="headline">${name}</div>
${notFound || (context && !this.hideContext)
? html`<span slot="supporting-text"
>${notFound
? this.hass.localize(
`ui.components.target-picker.${this.type}_not_found`
)
: context}</span
>`
: nothing}
${this._domainName && this.subEntry
? html`<span slot="supporting-text" class="domain"
>${this._domainName}</span
>`
: nothing}
${!this.subEntry && entries && showEntities
? html`
<div slot="end" class="summary">
${showEntities &&
!this.expand &&
entries?.referenced_entities.length
? html`<button class="main link" @click=${this._openDetails}>
${this.hass.localize(
"ui.components.target-picker.entities_count",
{
count: entries?.referenced_entities.length,
}
)}
</span>`
: nothing}
</div>
`
: nothing}
${!this.expand && !this.subEntry
? html`
<ha-icon-button
.path=${mdiClose}
slot="end"
@click=${this._removeItem}
></ha-icon-button>
`
: this.subEntry && this.type === "entity"
? html`
<ha-svg-icon
.path=${computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
)
? mdiChevronLeft
: mdiChevronRight}
slot="end"
></ha-svg-icon>
</button>`
: showEntities
? html`<span class="main">
${this.hass.localize(
"ui.components.target-picker.entities_count",
{
count: entries?.referenced_entities.length,
}
)}
</span>`
: nothing}
</div>
`
: nothing}
`;
let item: TemplateResult;
if (replaceable || (this.subEntry && this.type === "entity")) {
item = html`
<ha-list-item-button
class=${classMap({
error: notFound,
replaceable,
})}
@click=${replaceable
? this._replaceItem
: this.subEntry && this.type === "entity"
? this._openMoreInfo
: undefined}
>
${content}
</ha-list-item-button>
`;
} else {
item = html`
<ha-list-item-base
class=${classMap({
error: notFound,
})}
>
${content}
</ha-list-item-base>
`;
}
return html`
${item}
${!this.expand && !this.subEntry
? html`
<ha-icon-button
.path=${mdiClose}
slot="end"
@click=${this._removeItem}
></ha-icon-button>
`
: nothing}
</ha-md-list-item>
${this.expand && entries && entries.referenced_entities
? this._renderEntries()
: nothing}
@@ -276,10 +241,6 @@ export class HaTargetPickerItemRow extends LitElement {
private _renderEntries() {
const entries = this.parentEntries || this._entries;
if (!entries || entries.referenced_entities.length === 0) {
return this._renderEmptyEntries();
}
let nextType: TargetType =
this.type === "floor"
? "area"
@@ -389,64 +350,54 @@ export class HaTargetPickerItemRow extends LitElement {
) || ([] as string[]),
}));
const nextSubLevel = this.subLevel + 1;
return html`
${rows1.map(
(itemId, index) => html`
<ha-target-picker-item-row
sub-entry
.subLevel=${nextSubLevel}
style=${`--sub-entry-indent: calc(${nextSubLevel} * var(--ha-space-10));`}
.hass=${this.hass}
.type=${nextType}
.itemId=${itemId}
.parentEntries=${rows1Entries?.[index]}
.hideContext=${this.hideContext || this.type !== "label"}
expand
></ha-target-picker-item-row>
`
)}
${deviceRows.map(
(itemId, index) => html`
<ha-target-picker-item-row
sub-entry
.subLevel=${nextSubLevel}
style=${`--sub-entry-indent: calc(${nextSubLevel} * var(--ha-space-10));`}
.hass=${this.hass}
type="device"
.itemId=${itemId}
.parentEntries=${deviceRowsEntries?.[index]}
.hideContext=${this.hideContext || this.type !== "label"}
expand
></ha-target-picker-item-row>
`
)}
${entityRows.map(
(itemId) => html`
<ha-target-picker-item-row
sub-entry
.subLevel=${nextSubLevel}
style=${`--sub-entry-indent: calc(${nextSubLevel} * var(--ha-space-10));`}
.hass=${this.hass}
type="entity"
.itemId=${itemId}
.hideContext=${this.hideContext || this.type !== "label"}
></ha-target-picker-item-row>
`
)}
<div class="entries-tree">
<div class="line-wrapper">
<div class="line"></div>
</div>
<ha-md-list class="entries">
${rows1.map(
(itemId, index) => html`
<ha-target-picker-item-row
sub-entry
.hass=${this.hass}
.type=${nextType}
.itemId=${itemId}
.parentEntries=${rows1Entries?.[index]}
.hideContext=${this.hideContext || this.type !== "label"}
expand
></ha-target-picker-item-row>
`
)}
${deviceRows.map(
(itemId, index) => html`
<ha-target-picker-item-row
sub-entry
.hass=${this.hass}
type="device"
.itemId=${itemId}
.parentEntries=${deviceRowsEntries?.[index]}
.hideContext=${this.hideContext || this.type !== "label"}
expand
></ha-target-picker-item-row>
`
)}
${entityRows.map(
(itemId) => html`
<ha-target-picker-item-row
sub-entry
.hass=${this.hass}
type="entity"
.itemId=${itemId}
.hideContext=${this.hideContext || this.type !== "label"}
></ha-target-picker-item-row>
`
)}
</ha-md-list>
</div>
`;
}
private _renderEmptyEntries() {
return html`<ha-list-item-base>
<ha-svg-icon .path=${mdiMinusBox} slot="start" class="icon"></ha-svg-icon>
<span slot="headline"
>${this.hass.localize("ui.components.target-picker.no_targets")}</span
>
</ha-list-item-base>`;
}
private async _updateItemData() {
if (this.type === "entity") {
this._entries = undefined;
@@ -615,14 +566,7 @@ export class HaTargetPickerItemRow extends LitElement {
const areaName = area ? computeAreaName(area) : undefined;
const context = [areaName, entityName ? deviceName : undefined]
.filter(Boolean)
.join(
computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
)
? " ◂ "
: " ▸ "
);
.join(computeRTL(this.hass) ? " ◂ " : " ▸ ");
return {
name: entityName || deviceName || item,
context,
@@ -696,12 +640,6 @@ export class HaTargetPickerItemRow extends LitElement {
});
}
private _openMoreInfo = () => {
showMoreInfoDialog(this, {
entityId: this.itemId,
});
};
static styles = [
buttonLinkStyle,
css`
@@ -713,6 +651,12 @@ export class HaTargetPickerItemRow extends LitElement {
--md-list-item-two-line-container-height: 56px;
}
:host([expand]:not([sub-entry])) ha-md-list-item {
border: 2px solid var(--ha-color-border-neutral-loud);
background-color: var(--ha-color-fill-neutral-quiet-resting);
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
}
.error {
background: var(--ha-color-fill-warning-quiet-resting);
}
@@ -736,7 +680,6 @@ export class HaTargetPickerItemRow extends LitElement {
.icon {
width: 24px;
display: flex;
color: var(--ha-color-on-neutral-normal);
}
img {
@@ -754,21 +697,53 @@ export class HaTargetPickerItemRow extends LitElement {
line-height: var(--ha-line-height-condensed);
}
:host([sub-entry]) .summary {
margin-inline-start: var(--ha-space-12);
margin-right: var(--ha-space-12);
}
.summary .main {
font-weight: var(--ha-font-weight-medium);
}
:host([expand]) .summary .main {
color: var(--ha-color-text-secondary);
font-size: var(--ha-font-size-s);
font-weight: var(--ha-font-weight-normal);
}
.summary .secondary {
font-size: var(--ha-font-size-s);
color: var(--secondary-text-color);
}
.entries-tree {
display: flex;
position: relative;
}
.entries-tree .line-wrapper {
padding: var(--ha-space-5);
}
.entries-tree .line-wrapper .line {
border-left: 2px dashed var(--divider-color);
height: calc(100% - 28px);
position: absolute;
top: 0;
}
:host([sub-entry]) .entries-tree .line-wrapper .line {
height: calc(100% - 12px);
top: -18px;
}
.entries {
padding: 0;
--md-item-overflow: visible;
}
.horizontal-line-wrapper {
position: relative;
}
.horizontal-line-wrapper .horizontal-line {
position: absolute;
top: 11px;
margin-inline-start: -28px;
width: 29px;
border-top: 2px dashed var(--divider-color);
}
button.link {
text-decoration: none;
color: var(--primary-color);
@@ -779,19 +754,12 @@ export class HaTargetPickerItemRow extends LitElement {
text-decoration: underline;
}
.state {
.domain {
width: fit-content;
font-size: var(--ha-font-size-s);
color: var(--ha-color-text-secondary);
}
ha-list-item-button::part(end) {
gap: var(--ha-space-2);
}
:host([sub-entry]) ha-list-item-button::part(base),
:host([sub-entry]) ha-list-item-base::part(base) {
padding-inline-start: var(--sub-entry-indent);
border-radius: var(--ha-border-radius-md);
background-color: var(--ha-color-fill-neutral-quiet-resting);
padding: var(--ha-space-1);
font-family: var(--ha-font-family-code);
}
`,
];
@@ -76,6 +76,7 @@ export class HaTargetPickerValueChip extends LitElement {
? html`<ha-svg-icon .path=${fallbackIconPath}></ha-svg-icon>`
: stateObject
? html`<ha-state-icon
.hass=${this.hass}
.stateObj=${stateObject}
></ha-state-icon>`
: nothing}
+1 -2
View File
@@ -99,8 +99,7 @@ export class HaTileContainer extends LitElement {
display: flex;
flex-direction: row;
align-items: center;
padding: 0 10px;
min-height: var(--row-height, 56px);
padding: 10px;
flex: 1;
min-width: 0;
box-sizing: border-box;
+13 -27
View File
@@ -3,6 +3,7 @@ import { dump } from "js-yaml";
import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
import type { Trigger } from "../../data/automation";
import { migrateAutomationTrigger } from "../../data/automation";
@@ -22,10 +23,9 @@ import "../../panels/logbook/ha-logbook-renderer";
import type { HomeAssistant } from "../../types";
import "../ha-code-editor";
import "../ha-icon-button";
import "../ha-tab-group";
import "../ha-tab-group-tab";
import "./hat-logbook-note";
import type { NodeInfo } from "./hat-script-graph";
import { traceTabStyles } from "./trace-tab-styles";
const TRACE_PATH_TABS = [
"step_config",
@@ -66,21 +66,21 @@ export class HaTracePathDetails extends LitElement {
${this._renderSelectedTraceInfo()}
</div>
<ha-tab-group @wa-tab-show=${this._handleTabChanged}>
<div class="tabs top">
${TRACE_PATH_TABS.map(
(view) => html`
<ha-tab-group-tab
slot="nav"
.active=${this._view === view}
.panel=${view}
<button
.view=${view}
class=${classMap({ active: this._view === view })}
@click=${this._showTab}
>
${this.hass!.localize(
`ui.panel.config.automation.trace.tabs.${view}`
)}
</ha-tab-group-tab>
</button>
`
)}
</ha-tab-group>
</div>
${this._view === "step_config"
? this._renderSelectedConfig()
: this._view === "changed_variables"
@@ -308,12 +308,7 @@ export class HaTracePathDetails extends LitElement {
? this.hass!.localize(
"ui.panel.config.automation.trace.path.no_variables_changed"
)
: html`<ha-code-editor
read-only
dir="ltr"
.hass=${this.hass}
.value=${dump(trace.changed_variables).trimEnd()}
></ha-code-editor>`}
: html`<pre>${dump(trace.changed_variables).trimEnd()}</pre>`}
`
)}
</div>
@@ -388,12 +383,13 @@ export class HaTracePathDetails extends LitElement {
</div>`;
}
private _handleTabChanged(ev: CustomEvent) {
this._view = ev.detail.name as typeof this._view;
private _showTab(ev) {
this._view = ev.target.view;
}
static get styles(): CSSResultGroup {
return [
traceTabStyles,
css`
.padded-box {
margin: 16px;
@@ -410,16 +406,6 @@ export class HaTracePathDetails extends LitElement {
.error {
color: var(--error-color);
}
ha-tab-group {
background-color: var(--primary-background-color);
border-top: 1px solid var(--divider-color);
border-bottom: 1px solid var(--divider-color);
}
ha-tab-group-tab::part(base) {
padding: 2px 16px;
}
`,
];
}
+40
View File
@@ -0,0 +1,40 @@
import { css } from "lit";
export const traceTabStyles = css`
.tabs {
background-color: var(--primary-background-color);
border-top: 1px solid var(--divider-color);
border-bottom: 1px solid var(--divider-color);
display: flex;
padding-left: 4px;
padding-inline-start: 4px;
padding-inline-end: initial;
}
.tabs.top {
border-top: none;
}
.tabs > * {
padding: 2px 16px;
cursor: pointer;
position: relative;
bottom: -1px;
border: none;
border-bottom: 2px solid transparent;
user-select: none;
background: none;
color: var(--primary-text-color);
outline: none;
transition: background 15ms linear;
}
.tabs > *.active {
border-bottom-color: var(--primary-color);
}
.tabs > *:focus,
.tabs > *:hover {
background: var(--secondary-background-color);
}
`;
-1
View File
@@ -164,7 +164,6 @@ export interface BatterySourceTypeEnergyPreference {
stat_energy_to: string;
stat_rate?: string; // always available if power_config is set
power_config?: PowerConfig;
stat_soc?: string;
}
export interface GasSourceTypeEnergyPreference {
type: "gas";
+1 -4
View File
@@ -96,10 +96,7 @@ export const getEntities = (
const domainName = domainToName(hass.localize, computeDomain(entityId));
const isRTL = computeRTL(
hass.language,
hass.translationMetadata.translations
);
const isRTL = computeRTL(hass);
const primary = entityName || deviceName || entityId;
const secondary = [areaName, entityName ? deviceName : undefined]
+29
View File
@@ -0,0 +1,29 @@
import type { HomeAssistant } from "../types";
export interface HttpConfig {
server_host?: string[];
server_port?: number;
ssl_certificate?: string;
ssl_peer_certificate?: string;
ssl_key?: string;
cors_allowed_origins?: string[];
use_x_forwarded_for?: boolean;
trusted_proxies?: string[];
use_x_frame_options?: boolean;
ip_ban_enabled?: boolean;
login_attempts_threshold?: number;
ssl_profile?: "modern" | "intermediate";
}
interface HttpConfigResponse {
config: HttpConfig;
}
export const fetchHttpConfig = (hass: HomeAssistant) =>
hass.callWS<HttpConfigResponse>({ type: "http/config/get" });
export const saveHttpConfig = (hass: HomeAssistant, config: HttpConfig) =>
hass.callWS<HttpConfigResponse>({
type: "http/config/update",
config,
});
+9 -26
View File
@@ -456,13 +456,11 @@ const getIconFromTranslations = (
};
export const entityIcon = async (
entities: HomeAssistant["entities"],
hassConfig: HomeAssistant["config"],
hassConnection: Connection,
hass: HomeAssistant,
stateObj: HassEntity,
state?: string
) => {
const entry = entities?.[stateObj.entity_id] as
const entry = hass.entities?.[stateObj.entity_id] as
| EntityRegistryDisplayEntry
| undefined;
if (entry?.icon) {
@@ -470,14 +468,7 @@ export const entityIcon = async (
}
const domain = computeStateDomain(stateObj);
return getEntityIcon(
hassConfig,
hassConnection,
domain,
stateObj,
state,
entry
);
return getEntityIcon(hass, domain, stateObj, state, entry);
};
export const entryIcon = async (
@@ -489,19 +480,11 @@ export const entryIcon = async (
}
const stateObj = hass.states[entry.entity_id] as HassEntity | undefined;
const domain = computeDomain(entry.entity_id);
return getEntityIcon(
hass.config,
hass.connection,
domain,
stateObj,
undefined,
entry
);
return getEntityIcon(hass, domain, stateObj, undefined, entry);
};
const getEntityIcon = async (
hassConfig: HomeAssistant["config"],
hassConnection: Connection,
hass: HomeAssistant,
domain: string,
stateObj?: HassEntity,
stateValue?: string,
@@ -515,8 +498,8 @@ const getEntityIcon = async (
let icon: string | undefined;
if (translation_key && platform) {
const platformIcons = await getPlatformIcons(
hassConfig,
hassConnection,
hass.config,
hass.connection,
platform
);
if (platformIcons) {
@@ -532,8 +515,8 @@ const getEntityIcon = async (
if (!icon) {
const entityComponentIcons = await getComponentIcons(
hassConnection,
hassConfig,
hass.connection,
hass.config,
domain
);
if (entityComponentIcons) {
@@ -58,6 +58,7 @@ class DialogConfigEntrySystemOptions extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this.hass.localize(
"ui.dialogs.config_entry_system_options.title",
@@ -333,6 +333,7 @@ class DataEntryFlowDialog extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
prevent-scrim-close
@after-show=${this._focusFormStep}
@@ -18,7 +18,7 @@ import "../../../components/ha-slider";
import "../../../components/ha-time-input";
import "../../../components/input/ha-input";
import { isTiltOnly } from "../../../data/cover";
import { isUnavailableState, UNAVAILABLE } from "../../../data/entity/entity";
import { isUnavailableState } from "../../../data/entity/entity";
import type { ImageEntity } from "../../../data/image";
import { computeImageUrl } from "../../../data/image";
import "../../../panels/lovelace/components/hui-timestamp-display";
@@ -266,7 +266,7 @@ class EntityPreviewRow extends LitElement {
<div class="numberflex">
<ha-slider
labeled
.disabled=${stateObj.state === UNAVAILABLE}
.disabled=${isUnavailableState(stateObj.state)}
.step=${Number(stateObj.attributes.step)}
.min=${Number(stateObj.attributes.min)}
.max=${Number(stateObj.attributes.max)}
@@ -280,7 +280,7 @@ class EntityPreviewRow extends LitElement {
: html`<div class="numberflex numberstate">
<ha-input
auto-validate
.disabled=${stateObj.state === UNAVAILABLE}
.disabled=${isUnavailableState(stateObj.state)}
pattern="[0-9]+([\\.][0-9]+)?"
.step=${Number(stateObj.attributes.step)}
.min=${Number(stateObj.attributes.min)}
@@ -303,7 +303,7 @@ class EntityPreviewRow extends LitElement {
<ha-select
.label=${computeStateName(stateObj)}
.value=${stateObj.state}
.disabled=${stateObj.state === UNAVAILABLE}
.disabled=${isUnavailableState(stateObj.state)}
.options=${stateObj.attributes.options?.map((option) => ({
value: option,
label: this.hass!.formatEntityState(stateObj, option),
@@ -103,6 +103,7 @@ export class ListItemsDialog
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this._params.title ?? " "}
@closed=${this._dialogClosed}
@@ -112,6 +112,7 @@ export class DialogEnterCode
if (isText) {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this._dialogParams.title ??
this.hass.localize("ui.dialogs.enter_code.title")}
@@ -149,6 +150,7 @@ export class DialogEnterCode
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this._dialogParams.title ?? "Enter code"}
width="small"
+1
View File
@@ -140,6 +140,7 @@ export class DialogForm
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this._params.title}
prevent-scrim-close
@@ -96,6 +96,7 @@ export class HaImagecropperDialog
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this.hass.localize(
"ui.dialogs.image_cropper.crop_image"
@@ -148,6 +148,7 @@ class DialogLightColorFavorite extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
.headerTitle=${this._dialogParams?.title}
@closed=${this._dialogClosed}
@@ -65,6 +65,7 @@ class MoreInfoSirenAdvancedControls extends LitElement {
return html`
<ha-dialog
.open=${this._open}
.hass=${this.hass}
header-title=${this.hass.localize(
"ui.components.siren.advanced_controls"
)}
@@ -46,7 +46,8 @@ class MoreInfoAlarmControlPanel extends LitElement {
? html`
<div class="status">
<div class="icon">
<ha-state-icon .stateObj=${this.stateObj}> </ha-state-icon>
<ha-state-icon .hass=${this.hass} .stateObj=${this.stateObj}>
</ha-state-icon>
</div>
</div>
`
@@ -3,7 +3,7 @@ import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-date-input";
import "../../../components/ha-time-input";
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity/entity";
import { isUnavailableState, UNKNOWN } from "../../../data/entity/entity";
import {
setInputDateTimeValue,
stateToIsoDateString,
@@ -27,7 +27,7 @@ class MoreInfoInputDatetime extends LitElement {
<ha-date-input
.locale=${this.hass.locale}
.value=${stateToIsoDateString(this.stateObj)}
.disabled=${this.stateObj.state === UNAVAILABLE}
.disabled=${isUnavailableState(this.stateObj.state)}
@value-changed=${this._dateChanged}
>
</ha-date-input>
@@ -42,7 +42,7 @@ class MoreInfoInputDatetime extends LitElement {
? this.stateObj.state.split(" ")[1]
: this.stateObj.state}
.locale=${this.hass.locale}
.disabled=${this.stateObj.state === UNAVAILABLE}
.disabled=${isUnavailableState(this.stateObj.state)}
@value-changed=${this._timeChanged}
@click=${this._stopEventPropagation}
></ha-time-input>
@@ -97,7 +97,10 @@ class MoreInfoLock extends LitElement {
<div class="status">
<span></span>
<div class="icon">
<ha-state-icon .stateObj=${this.stateObj}></ha-state-icon>
<ha-state-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-state-icon>
</div>
</div>
`
@@ -190,6 +190,7 @@ class MoreInfoWeather extends LitElement {
<ha-state-icon
class="weather-icon"
.stateObj=${this.stateObj}
.hass=${this.hass}
></ha-state-icon>
`}
</div>
+2 -4
View File
@@ -594,13 +594,11 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
? !favoritesHandler.hasCustomFavorites(favoritesContext.entry)
: false;
const isRTL = computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
);
const isRTL = computeRTL(this.hass);
return html`
<ha-adaptive-dialog
.hass=${this.hass}
.open=${this._open}
.width=${this._fill ? "full" : this.large ? "large" : "medium"}
@closed=${this._dialogClosed}
+2
View File
@@ -242,6 +242,7 @@ export class QuickBar extends LitElement {
<ha-adaptive-dialog
without-header
flexcontent
.hass=${this.hass}
aria-label=${this.hass.localize("ui.dialogs.quick-bar.title")}
.open=${this._open}
hideActions
@@ -252,6 +253,7 @@ export class QuickBar extends LitElement {
${!this._loading && this._opened
? html`<ha-picker-combo-box
id="combo-box"
.hass=${this.hass}
@index-selected=${this._handleItemSelected}
.notFoundLabel=${this.hass.localize(
"ui.dialogs.quick-bar.nothing_found"
@@ -76,6 +76,7 @@ class DialogRestartWait extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
.headerTitle=${this._title}
@closed=${this._dialogClosed}
+1
View File
@@ -109,6 +109,7 @@ class DialogRestart extends LitElement {
return html`
<ha-adaptive-dialog
.hass=${this.hass}
.open=${this._dialogOpen}
header-title=${dialogTitle}
allow-mode-change
@@ -165,6 +165,7 @@ class DialogEditSidebar extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${dialogTitle}
header-subtitle=${!this._migrateToUserData
+1
View File
@@ -70,6 +70,7 @@ export class TTSTryDialog extends LitElement {
}
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this.hass.localize("ui.dialogs.tts-try.header")}
@closed=${this._dialogClosed}
@@ -29,6 +29,7 @@ class DialogBox extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this.hass.localize("ui.dialogs.update_backup.title")}
width="small"
@@ -143,6 +143,7 @@ export class HaVoiceAssistantSetupDialog extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title="Voice Satellite setup"
prevent-scrim-close
@@ -98,7 +98,12 @@ export class HaVoiceCommandDialog extends LitElement {
}
return html`
<ha-dialog .open=${this._open} @closed=${this._dialogClosed} flexcontent>
<ha-dialog
.hass=${this.hass}
.open=${this._open}
@closed=${this._dialogClosed}
flexcontent
>
<ha-dialog-header slot="header">
<ha-icon-button
slot="navigationIcon"
@@ -552,6 +552,7 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
</hass-tabs-subpage>
${this.showFilters && !showPane
? html`<ha-dialog
.hass=${this.hass}
.open=${true}
width="full"
header-title=${localize("ui.components.subpage-data-table.filters")}
@@ -41,6 +41,7 @@ class ConfirmEventDialogBox extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this._params.title}
width="small"
@@ -66,6 +66,7 @@ class DialogCalendarEventDetail extends LitElement {
const stateObj = this.hass.states[this._calendarId!];
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this._data!.summary}
@closed=${this._dialogClosed}
@@ -149,6 +149,7 @@ class DialogCalendarEventEditor extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this.hass.localize(
`ui.components.calendar.event.${isCreate ? "add" : "edit"}`
+2 -1
View File
@@ -24,8 +24,8 @@ import "../../components/ha-two-pane-top-app-bar-fixed";
import type {
Calendar,
CalendarEvent,
CalendarEventApiData,
CalendarEventSubscription,
CalendarEventApiData,
} from "../../data/calendar";
import {
getCalendars,
@@ -144,6 +144,7 @@ class PanelCalendar extends SubscribeMixin(LitElement) {
>
<ha-state-icon
slot="icon"
.hass=${this.hass}
.stateObj=${selCal}
style="--icon-primary-color: ${selCal.backgroundColor}"
></ha-state-icon>
@@ -98,6 +98,7 @@ export class DialogAddApplicationCredential extends LitElement {
: "";
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
@closed=${this._abortDialog}
.preventScrimClose=${!!this._domain ||
@@ -70,6 +70,7 @@ class AppsRegistriesDialog extends LitElement {
protected render(): TemplateResult {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
@closed=${this._dialogClosed}
header-title=${this.hass.localize(
@@ -87,6 +87,7 @@ class DialogAreasFloorsOrder extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${dialogTitle}
@closed=${this._dialogClosed}
@@ -108,6 +108,7 @@ class DialogFloorDetail extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${entry
? this.hass.localize("ui.panel.config.floors.editor.update_floor")
@@ -76,7 +76,6 @@ import type {
} from "../../../../data/script";
import { getActionType, isAction } from "../../../../data/script";
import { describeAction } from "../../../../data/script_i18n";
import type { TargetSelector } from "../../../../data/selector";
import { callExecuteScript } from "../../../../data/service";
import {
showAlertDialog,
@@ -289,12 +288,6 @@ export default class HaAutomationActionRow extends LitElement {
? { device_id: (this.action as DeviceAction).device_id }
: undefined;
const serviceTargetSpec =
type === "service" && action
? this.hass.services?.[computeDomain(action)]?.[computeObjectId(action)]
?.target
: undefined;
return html`
${type === "service" && "action" in this.action && this.action.action
? html`
@@ -324,11 +317,7 @@ export default class HaAutomationActionRow extends LitElement {
)
)}
${target !== undefined || (actionHasTarget && !this._isNew)
? this._renderTargets(
target,
actionHasTarget && !this._isNew,
serviceTargetSpec
)
? this._renderTargets(target, actionHasTarget && !this._isNew)
: nothing}
${type !== "condition" &&
(this.action as NonConditionAction).continue_on_error === true
@@ -692,16 +681,11 @@ export default class HaAutomationActionRow extends LitElement {
}
private _renderTargets = memoizeOne(
(
target?: HassServiceTarget,
targetRequired = false,
targetSpec?: TargetSelector["target"]
) =>
(target?: HassServiceTarget, targetRequired = false) =>
html`<ha-automation-row-targets
.hass=${this.hass}
.target=${target}
.targetRequired=${targetRequired}
.selector=${targetSpec ? { target: targetSpec } : undefined}
></ha-automation-row-targets>`
);
@@ -598,6 +598,7 @@ class DialogAddAutomationElement
return html`
<ha-dialog
.hass=${this.hass}
width="large"
.open=${this._open}
@closed=${this._handleClosed}
@@ -970,14 +971,7 @@ class DialogAddAutomationElement
subtitle = [areaName, entityName ? deviceName : undefined]
.filter(Boolean)
.join(
computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
)
? " ◂ "
: " ▸ "
);
.join(computeRTL(this.hass) ? " ◂ " : " ▸ ");
}
}
@@ -769,6 +769,7 @@ export default class HaAutomationAddFromTarget extends LitElement {
private _renderEntityIcon =
(stateObj: HassEntity) => (slot: string | undefined) =>
html`<ha-state-icon
.hass=${this.hass}
slot=${ifDefined(slot)}
.stateObj=${stateObj}
></ha-state-icon>`;
@@ -866,13 +867,10 @@ export default class HaAutomationAddFromTarget extends LitElement {
undefined
);
const filteredFloors = this._floorAreas.filter(
({ id, areas }) => id !== undefined && areas.length
);
this._floorAreas.forEach((floor) => {
this._entries[floor.id || `floor${TARGET_SEPARATOR}`] = {
// auto expand if only one floor is present
open: filteredFloors.length === 1 && filteredFloors[0].id === floor.id,
open: this._floorAreas.length === 1,
areas: {},
};
@@ -300,10 +300,7 @@ export class HaAutomationAddSearch extends LitElement {
let showEntityId = false;
if (type === "area" || type === "floor") {
rtl = computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
);
rtl = computeRTL(this.hass);
hasFloor =
type === "area" && !!(item as FloorComboBoxItem).area?.floor_id;
}
@@ -57,6 +57,7 @@ class DialogAutomationSaveTimeout extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._opened}
header-title=${title}
@closed=${this._dialogClosed}
@@ -60,7 +60,6 @@ import {
import { fullEntitiesContext } from "../../../../data/context";
import type { DeviceCondition } from "../../../../data/device/device_automation";
import type { EntityRegistryEntry } from "../../../../data/entity/entity_registry";
import type { TargetSelector } from "../../../../data/selector";
import {
showAlertDialog,
showPromptDialog,
@@ -181,9 +180,6 @@ export default class HaAutomationConditionRow extends LitElement {
? { device_id: [(this.condition as DeviceCondition).device_id] }
: undefined;
const conditionTargetSpec =
this.conditionDescriptions[this.condition.condition]?.target;
return html`
<ha-condition-icon
slot="leading-icon"
@@ -195,11 +191,7 @@ export default class HaAutomationConditionRow extends LitElement {
describeCondition(this.condition, this.hass, this._entityReg)
)}
${target !== undefined || (descriptionHasTarget && !this._isNew)
? this._renderTargets(
target,
descriptionHasTarget && !this._isNew,
conditionTargetSpec
)
? this._renderTargets(target, descriptionHasTarget && !this._isNew)
: nothing}
</h3>
<ha-automation-row-event-chip
@@ -513,16 +505,11 @@ export default class HaAutomationConditionRow extends LitElement {
}
private _renderTargets = memoizeOne(
(
target?: HassServiceTarget,
targetRequired = false,
targetSpec?: TargetSelector["target"]
) =>
(target?: HassServiceTarget, targetRequired = false) =>
html`<ha-automation-row-targets
.hass=${this.hass}
.target=${target}
.targetRequired=${targetRequired}
.selector=${targetSpec ? { target: targetSpec } : undefined}
></ha-automation-row-targets>`
);
@@ -152,6 +152,7 @@ class DialogNewAutomation extends LitElement {
return html`
<ha-adaptive-dialog
.hass=${this.hass}
.open=${this._open}
flexcontent
header-title=${this.hass.localize(
@@ -302,6 +302,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
showNarrow: true,
template: (automation) =>
html`<ha-state-icon
.hass=${this.hass}
.stateObj=${automation}
style=${styleMap({
color:
@@ -303,9 +303,7 @@ export default class HaAutomationSidebar extends LitElement {
private _updateSize(clientX: number) {
let delta = this._resizeStartX - clientX;
if (
computeRTL(this.hass.language, this.hass.translationMetadata.translations)
) {
if (computeRTL(this.hass)) {
delta = -delta;
}
@@ -352,24 +350,14 @@ export default class HaAutomationSidebar extends LitElement {
private _increaseSize = (ev: KeyboardEvent) => {
ev.stopPropagation();
this._resizeStartX -= computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
)
? 10
: -10;
this._resizeStartX -= computeRTL(this.hass) ? 10 : -10;
this._keyboardResize();
};
private _decreaseSize = (ev: KeyboardEvent) => {
ev.stopPropagation();
this._resizeStartX += computeRTL(
this.hass.language,
this.hass.translationMetadata.translations
)
? 10
: -10;
this._resizeStartX += computeRTL(this.hass) ? 10 : -10;
this._keyboardResize();
};
@@ -11,6 +11,7 @@ import {
import type { CSSResultGroup, TemplateResult, PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { fireEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
@@ -20,8 +21,6 @@ import "../../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-tab-group";
import "../../../components/ha-tab-group-tab";
import "../../../components/trace/ha-trace-blueprint-config";
import "../../../components/trace/ha-trace-config";
import "../../../components/trace/ha-trace-logbook";
@@ -32,6 +31,7 @@ import type {
HatScriptGraph,
NodeInfo,
} from "../../../components/trace/hat-script-graph";
import { traceTabStyles } from "../../../components/trace/trace-tab-styles";
import type { AutomationEntity } from "../../../data/automation";
import type { LogbookEntry } from "../../../data/logbook";
import { getLogbookDataForContext } from "../../../data/logbook";
@@ -172,10 +172,7 @@ export class HaAutomationTrace extends LitElement {
.label=${this.hass!.localize(
"ui.panel.config.automation.trace.older_trace"
)}
.path=${computeRTL(
this.hass!.language,
this.hass!.translationMetadata.translations
)
.path=${computeRTL(this.hass!)
? mdiRayStartArrow
: mdiRayEndArrow}
.disabled=${this._traces[this._traces.length - 1].run_id ===
@@ -192,10 +189,7 @@ export class HaAutomationTrace extends LitElement {
.label=${this.hass!.localize(
"ui.panel.config.automation.trace.newer_trace"
)}
.path=${computeRTL(
this.hass!.language,
this.hass!.translationMetadata.translations
)
.path=${computeRTL(this.hass!)
? mdiRayEndArrow
: mdiRayStartArrow}
.disabled=${this._traces[0].run_id === this._runId}
@@ -229,34 +223,40 @@ export class HaAutomationTrace extends LitElement {
</div>
<div class="info">
<ha-tab-group @wa-tab-show=${this._handleTabChanged}>
<div class="tabs top">
${TABS.map(
(view) => html`
<ha-tab-group-tab
slot="nav"
.active=${this._view === view}
.panel=${view}
<button
tabindex="0"
.view=${view}
class=${classMap({
active: this._view === view,
})}
@click=${this._showTab}
>
${this.hass!.localize(
`ui.panel.config.automation.trace.tabs.${view}`
)}
</ha-tab-group-tab>
</button>
`
)}
${this._trace.blueprint_inputs
? html`
<ha-tab-group-tab
slot="nav"
.active=${this._view === "blueprint"}
panel="blueprint"
<button
tabindex="0"
.view=${"blueprint"}
class=${classMap({
active: this._view === "blueprint",
})}
@click=${this._showTab}
>
${this.hass!.localize(
`ui.panel.config.automation.trace.tabs.blueprint_config`
)}
</ha-tab-group-tab>
</button>
`
: ""}
</ha-tab-group>
</div>
${this._selected === undefined ||
this._logbookEntries === undefined ||
trackedNodes === undefined
@@ -483,8 +483,8 @@ export class HaAutomationTrace extends LitElement {
this._logbookEntries = traceInfo.logbookEntries;
}
private _handleTabChanged(ev: CustomEvent) {
this._view = ev.detail.name as typeof this._view;
private _showTab(ev: Event) {
this._view = (ev.target as any).view;
}
private _timelinePathPicked(ev: CustomEvent) {
@@ -536,6 +536,7 @@ export class HaAutomationTrace extends LitElement {
static get styles(): CSSResultGroup {
return [
haStyle,
traceTabStyles,
css`
.toolbar {
display: flex;
@@ -598,14 +599,6 @@ export class HaAutomationTrace extends LitElement {
overflow-y: auto;
background-color: var(--card-background-color);
}
ha-tab-group {
background-color: var(--primary-background-color);
border-bottom: 1px solid var(--divider-color);
direction: var(--direction);
}
ha-tab-group-tab::part(base) {
padding: 2px 16px;
}
:host([narrow]) .info {
overflow: visible;
}
@@ -39,6 +39,7 @@ class DialogPasteReplace extends LitElement {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
@closed=${this._dialogClosed}
header-title=${this.hass.localize(
@@ -142,7 +142,6 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
min-height: 0;
overflow: auto;
margin-top: 0;
padding-top: 0;
padding-bottom: max(var(--safe-area-inset-bottom, 0px), 32px);
}
+1
View File
@@ -241,6 +241,7 @@ export const automationRowsStyles = css`
export const sidebarEditorStyles = css`
.sidebar-editor {
display: block;
padding-top: 8px;
}
.description {
padding-top: 16px;
@@ -14,8 +14,7 @@ export const getTargetIcon = (
targetType: string,
targetId: string | undefined,
configEntryLookup: Record<string, ConfigEntry>,
getLabel?: (id: string) => LabelRegistryEntry | undefined,
slot?: string
getLabel?: (id: string) => LabelRegistryEntry | undefined
): TemplateResult | typeof nothing => {
if (!targetId) {
return nothing;
@@ -23,7 +22,6 @@ export const getTargetIcon = (
if (targetType === "floor" && hass.floors[targetId]) {
return html`<ha-floor-icon
.slot=${slot}
.floor=${hass.floors[targetId]}
></ha-floor-icon>`;
}
@@ -31,12 +29,9 @@ export const getTargetIcon = (
if (targetType === "area") {
const area = hass.areas[targetId];
if (area?.icon) {
return html`<ha-icon .slot=${slot} .icon=${area.icon}></ha-icon>`;
return html`<ha-icon .icon=${area.icon}></ha-icon>`;
}
return html`<ha-svg-icon
.slot=${slot}
.path=${mdiTextureBox}
></ha-svg-icon>`;
return html`<ha-svg-icon .path=${mdiTextureBox}></ha-svg-icon>`;
}
if (targetType === "device" && hass.devices[targetId]) {
@@ -50,24 +45,23 @@ export const getTargetIcon = (
return html`<ha-domain-icon
.domain=${domain}
brand-fallback
.slot=${slot}
></ha-domain-icon>`;
}
}
if (targetType === "entity" && hass.states[targetId]) {
return html`<ha-state-icon
.hass=${hass}
.stateObj=${hass.states[targetId]}
.slot=${slot}
></ha-state-icon>`;
}
if (targetType === "label" && getLabel) {
const label = getLabel(targetId);
if (label?.icon) {
return html`<ha-icon .slot=${slot} .icon=${label.icon}></ha-icon>`;
return html`<ha-icon .icon=${label.icon}></ha-icon>`;
}
return html`<ha-svg-icon .slot=${slot} .path=${mdiLabel}></ha-svg-icon>`;
return html`<ha-svg-icon .path=${mdiLabel}></ha-svg-icon>`;
}
return nothing;
@@ -1,11 +1,9 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume, type ContextType } from "@lit/context";
import {
mdiAlert,
mdiAlertOctagon,
mdiCodeBraces,
mdiFormatListBulleted,
mdiMenuDown,
mdiShape,
} from "@mdi/js";
import type { HassServiceTarget } from "home-assistant-js-websocket";
@@ -14,13 +12,8 @@ import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ensureArray } from "../../../../common/array/ensure-array";
import { transform } from "../../../../common/decorators/transform";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import { isTemplate } from "../../../../common/string/has-template";
import "../../../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import "../../../../components/ha-svg-icon";
import { showTargetDetailsDialog } from "../../../../components/target-picker/dialog/show-dialog-target-details";
import type { ConfigEntry } from "../../../../data/config_entries";
import {
configEntriesContext,
@@ -30,9 +23,6 @@ import {
statesContext,
} from "../../../../data/context";
import type { LabelRegistryEntry } from "../../../../data/label/label_registry";
import type { TargetSelector } from "../../../../data/selector";
import type { TargetType } from "../../../../data/target";
import { showMoreInfoDialog } from "../../../../dialogs/more-info/show-ha-more-info-dialog";
import type { HomeAssistant } from "../../../../types";
import { getTargetIcon } from "./get_target_icon";
import { getTargetText } from "./get_target_text";
@@ -48,9 +38,6 @@ export class HaAutomationRowTargets extends LitElement {
@property({ attribute: false })
public targetRequired = false;
@property({ attribute: false })
public selector?: TargetSelector;
@state()
@consume({ context: internationalizationContext, subscribe: true })
private _i18n!: ContextType<typeof internationalizationContext>;
@@ -123,67 +110,17 @@ export class HaAutomationRowTargets extends LitElement {
);
}
const rows = Object.entries(this.target!)
.reduce<["floor" | "area" | "device" | "entity" | "label", string][]>(
(acc, [targetType, targetId]) => {
const type = targetType.replace("_id", "") as
| "floor"
| "area"
| "device"
| "entity"
| "label";
return [
...acc,
...ensureArray(targetId).map((id): [typeof type, string] => [
type,
id,
]),
];
},
[]
)
.sort(([typeA], [typeB]) => {
const order = ["entity", "device", "area", "floor", "label"];
return order.indexOf(typeA) - order.indexOf(typeB);
});
let lastTargetType: string | null = null;
return html`
<ha-dropdown
@wa-select=${this._handleTargetSelect}
@click=${stopPropagation}
>
<span slot="trigger" class="target interactive">
<ha-svg-icon .path=${mdiFormatListBulleted}></ha-svg-icon>
<div class="label">
${this._i18n.localize(
"ui.panel.config.automation.editor.target_summary.targets",
{
count: totalLength,
}
)}
</div>
<ha-svg-icon .path=${mdiMenuDown}></ha-svg-icon>
</span>
${rows.map(([targetType, targetId]) => {
const content = html`${lastTargetType !== null &&
lastTargetType !== targetType
? html`<wa-divider></wa-divider>`
: nothing}
${!lastTargetType || lastTargetType !== targetType
? html`<h3>
${this._i18n.localize(
`ui.panel.config.automation.editor.target_summary.types.${targetType}`
)}
</h3>`
: nothing}
${this._renderTarget(targetType, targetId, true)}`;
lastTargetType = targetType;
return content;
})}
</ha-dropdown>
`;
return html`<span class="target">
<ha-svg-icon .path=${mdiFormatListBulleted}></ha-svg-icon>
<div class="label">
${this._i18n.localize(
"ui.panel.config.automation.editor.target_summary.targets",
{
count: totalLength,
}
)}
</div>
</span>`;
}
private _getLabel = (id: string) =>
@@ -215,22 +152,9 @@ export class HaAutomationRowTargets extends LitElement {
icon: TemplateResult | typeof nothing,
label: string,
warning = false,
error = false,
targetId?: string,
targetType?: string
error = false
) {
return html`<div
class=${classMap({
target: true,
warning,
error,
interactive: targetId && targetType,
})}
.targetId=${targetId}
.targetType=${targetType}
.label=${label}
@click=${this._handleTargetClick}
>
return html`<div class=${classMap({ target: true, warning, error })}>
${icon}
<div class="label">${label}</div>
</div>`;
@@ -238,132 +162,48 @@ export class HaAutomationRowTargets extends LitElement {
private _renderTarget(
targetType: "floor" | "area" | "device" | "entity" | "label",
targetId: string,
dropdownOption = false
targetId: string
) {
let icon: string | undefined;
let label: string;
let warning = false;
let badgeTargetId: string | undefined = targetId;
let badgeTargetType: string | undefined = targetType;
if (targetType === "entity" && ["all", "none"].includes(targetId)) {
icon = mdiShape;
label = this._i18n.localize(
`ui.panel.config.automation.editor.target_summary.${targetId as "all" | "none"}_entities`
return this._renderTargetBadge(
html`<ha-svg-icon .path=${mdiShape}></ha-svg-icon>`,
this._i18n.localize(
`ui.panel.config.automation.editor.target_summary.${targetId as "all" | "none"}_entities`
)
);
badgeTargetId = undefined;
badgeTargetType = undefined;
} else if (isTemplate(targetId)) {
// Check if the target is a template
icon = mdiCodeBraces;
label = this._i18n.localize(
"ui.panel.config.automation.editor.target_summary.template"
);
badgeTargetId = undefined;
badgeTargetType = undefined;
} else {
const exists = this._checkTargetExists(targetType, targetId);
if (!exists) {
icon = mdiAlert;
label = getTargetText(this.hass, targetType, targetId, this._getLabel);
warning = true;
badgeTargetId = undefined;
badgeTargetType = undefined;
} else {
label = getTargetText(this.hass, targetType, targetId, this._getLabel);
}
}
const iconTemplate = icon
? html`<ha-svg-icon
.slot=${dropdownOption ? "icon" : ""}
.path=${icon}
></ha-svg-icon>`
: getTargetIcon(
this.hass,
targetType,
targetId,
this._configEntryLookup || {},
this._getLabel,
dropdownOption ? "icon" : ""
);
// Check if the target is a template
if (isTemplate(targetId)) {
return this._renderTargetBadge(
html`<ha-svg-icon .path=${mdiCodeBraces}></ha-svg-icon>`,
this._i18n.localize(
"ui.panel.config.automation.editor.target_summary.template"
)
);
}
if (dropdownOption) {
return html`<ha-dropdown-item
.value=${{
targetId: badgeTargetId,
targetType: badgeTargetType,
label,
}}
class=${classMap({
warning,
})}
>${iconTemplate} ${label}</ha-dropdown-item
>`;
const exists = this._checkTargetExists(targetType, targetId);
if (!exists) {
return this._renderTargetBadge(
html`<ha-svg-icon .path=${mdiAlert}></ha-svg-icon>`,
getTargetText(this.hass, targetType, targetId, this._getLabel),
true
);
}
return this._renderTargetBadge(
iconTemplate,
label,
warning,
false,
badgeTargetId,
badgeTargetType
getTargetIcon(
this.hass,
targetType,
targetId,
this._configEntryLookup || {},
this._getLabel
),
getTargetText(this.hass, targetType, targetId, this._getLabel)
);
}
private _handleTargetClick(ev: Event) {
const target = ev.currentTarget as HTMLDivElement & {
targetId: string;
targetType: TargetType;
label: string;
};
if (!target.targetId || !target.targetType) {
return;
}
this._showTargetInfo(target.targetId, target.targetType, target.label, ev);
}
private _handleTargetSelect(
ev: HaDropdownSelectEvent<{
targetId?: string;
targetType?: TargetType;
label: string;
}>
) {
const value = ev.detail.item.value;
if (!value.targetId || !value.targetType) {
return;
}
this._showTargetInfo(value.targetId, value.targetType, value.label);
}
private _showTargetInfo(
targetId: string,
targetType: TargetType,
label: string,
ev?: Event
) {
ev?.stopPropagation();
if (targetType === "entity") {
showMoreInfoDialog(this, { entityId: targetId });
return;
}
showTargetDetailsDialog(this, {
title: label,
type: targetType,
itemId: targetId,
selector: this.selector,
});
}
static styles = css`
:host {
display: contents;
@@ -415,25 +255,6 @@ export class HaAutomationRowTargets extends LitElement {
height: 32px;
align-items: center;
}
.target.interactive {
cursor: pointer;
}
.target.interactive:hover {
background: var(--ha-color-fill-neutral-normal-hover);
}
ha-dropdown-item {
padding: 0 var(--ha-space-2);
}
ha-dropdown-item.warning {
background-color: var(--ha-color-fill-warning-quiet-resting);
color: var(--ha-color-on-warning-normal);
}
ha-dropdown-item.warning:hover {
background-color: var(--ha-color-fill-warning-quiet-hover);
color: var(--ha-color-on-warning-normal);
}
`;
}
@@ -59,7 +59,6 @@ import { validateConfig } from "../../../../data/config";
import { fullEntitiesContext } from "../../../../data/context";
import type { DeviceTrigger } from "../../../../data/device/device_automation";
import type { EntityRegistryEntry } from "../../../../data/entity/entity_registry";
import type { TargetSelector } from "../../../../data/selector";
import type { TriggerDescriptions } from "../../../../data/trigger";
import { isTriggerList } from "../../../../data/trigger";
import {
@@ -215,12 +214,6 @@ export default class HaAutomationTriggerRow extends LitElement {
? { device_id: (this.trigger as DeviceTrigger).device_id }
: undefined;
const triggerTargetSpec =
type === "platform"
? this.triggerDescriptions[(this.trigger as PlatformTrigger).trigger]
?.target
: undefined;
return html`
${type === "list"
? html`<ha-svg-icon
@@ -236,11 +229,7 @@ export default class HaAutomationTriggerRow extends LitElement {
<h3 slot="header">
${describeTrigger(this.trigger, this.hass, this._entityReg)}
${target !== undefined || (descriptionHasTarget && !this._isNew)
? this._renderTargets(
target,
descriptionHasTarget && !this._isNew,
triggerTargetSpec
)
? this._renderTargets(target, descriptionHasTarget && !this._isNew)
: nothing}
</h3>
<ha-automation-row-event-chip
@@ -518,16 +507,11 @@ export default class HaAutomationTriggerRow extends LitElement {
}
private _renderTargets = memoizeOne(
(
target?: HassServiceTarget,
targetRequired = false,
targetSpec?: TargetSelector["target"]
) =>
(target?: HassServiceTarget, targetRequired = false) =>
html`<ha-automation-row-targets
.hass=${this.hass}
.target=${target}
.targetRequired=${targetRequired}
.selector=${targetSpec ? { target: targetSpec } : undefined}
></ha-automation-row-targets>`
);
@@ -209,6 +209,7 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this._stepTitle}
prevent-scrim-close
@@ -89,6 +89,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${dialogTitle}
prevent-scrim-close
@@ -56,6 +56,7 @@ class DialogDownloadDecryptedBackup extends LitElement implements HassDialog {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this.hass.localize(
"ui.panel.config.backup.dialogs.download.title"
@@ -181,7 +181,11 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
const selectedAgents = this._formData.agent_ids;
return html`
<ha-dialog .open=${this._open} @closed=${this._dialogClosed}>
<ha-dialog
.hass=${this.hass}
.open=${this._open}
@closed=${this._dialogClosed}
>
<ha-dialog-header slot="header">
${isFirstStep
? html`
@@ -64,6 +64,7 @@ class LocalBackupLocationDialog extends LitElement {
}
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this.hass.localize(
`ui.panel.config.backup.dialogs.local_backup_location.title`
@@ -46,6 +46,7 @@ class DialogNewBackup extends LitElement implements HassDialog {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._opened}
header-title=${this.hass.localize(
"ui.panel.config.backup.dialogs.new.title"
@@ -137,6 +137,7 @@ class DialogRestoreBackup extends LitElement implements HassDialog {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${dialogTitle}
@closed=${this._dialogClosed}
@@ -86,6 +86,7 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${dialogTitle}
prevent-scrim-close
@@ -47,6 +47,7 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
return html`
<ha-dialog
.hass=${this.hass}
.open=${this._open}
header-title=${this.hass.localize(
"ui.panel.config.backup.dialogs.show_encryption_key.title"

Some files were not shown because too many files have changed in this diff Show More