Compare commits

..

12 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
bb5c61143f refactor: inline single-use picker variables
Co-authored-by: timmo001 <28114703+timmo001@users.noreply.github.com>
2026-02-26 16:29:24 +00:00
Aidan Timson
4ffaf0271a Merge duplicate logic 2026-02-26 16:15:36 +00:00
Aidan Timson
8852843083 Fix replacement 2026-02-26 16:10:30 +00:00
Aidan Timson
3a9795e9c8 Use target anchor for picker when replacing items 2026-02-26 16:03:15 +00:00
Aidan Timson
690f17c7b1 Add types to mouse events 2026-02-26 15:55:23 +00:00
Aidan Timson
9c30f39638 Use shared type for target items 2026-02-26 15:55:10 +00:00
Aidan Timson
6c17c95581 Use proper type 2026-02-26 15:49:11 +00:00
Aidan Timson
8c424e8c89 Select value on open 2026-02-26 15:48:11 +00:00
Aidan Timson
1ce0879d37 Fix autoclose 2026-02-26 15:39:51 +00:00
copilot-swe-agent[bot]
b398994607 refactor: refine target picker replace event handling
Co-authored-by: timmo001 <28114703+timmo001@users.noreply.github.com>
2026-02-26 15:28:52 +00:00
copilot-swe-agent[bot]
ffbe4f1de6 feat: add click-to-replace interaction for target picker items
Co-authored-by: timmo001 <28114703+timmo001@users.noreply.github.com>
2026-02-26 15:25:50 +00:00
copilot-swe-agent[bot]
7d408bb7d3 Initial plan 2026-02-26 15:14:31 +00:00
44 changed files with 792 additions and 867 deletions

View File

@@ -515,14 +515,6 @@ export class DemoHaAdaptiveDialog extends LitElement {
<td><code>--ha-dialog-surface-background</code></td>
<td>Dialog/sheet background color.</td>
</tr>
<tr>
<td><code>--ha-dialog-surface-backdrop-filter</code></td>
<td>Dialog/sheet surface backdrop filter.</td>
</tr>
<tr>
<td><code>--dialog-box-shadow</code></td>
<td>Dialog surface box shadow (dialog mode only).</td>
</tr>
<tr>
<td><code>--ha-dialog-border-radius</code></td>
<td>Border radius of the dialog surface (dialog mode only).</td>
@@ -535,34 +527,6 @@ export class DemoHaAdaptiveDialog extends LitElement {
<td><code>--ha-dialog-hide-duration</code></td>
<td>Hide animation duration (dialog mode only).</td>
</tr>
<tr>
<td><code>--ha-dialog-scrim-backdrop-filter</code></td>
<td>Dialog/sheet scrim backdrop filter.</td>
</tr>
<tr>
<td><code>--dialog-backdrop-filter</code></td>
<td>Dialog/sheet scrim backdrop filter (legacy fallback).</td>
</tr>
<tr>
<td><code>--mdc-dialog-scrim-color</code></td>
<td>Dialog/sheet scrim color (legacy compatibility).</td>
</tr>
<tr>
<td><code>--ha-bottom-sheet-surface-background</code></td>
<td>Bottom sheet background color (sheet mode only).</td>
</tr>
<tr>
<td><code>--ha-bottom-sheet-surface-backdrop-filter</code></td>
<td>Bottom sheet surface backdrop filter (sheet mode only).</td>
</tr>
<tr>
<td><code>--ha-bottom-sheet-scrim-backdrop-filter</code></td>
<td>Bottom sheet scrim backdrop filter (sheet mode only).</td>
</tr>
<tr>
<td><code>--ha-bottom-sheet-scrim-color</code></td>
<td>Bottom sheet scrim color (sheet mode only).</td>
</tr>
</tbody>
</table>

View File

@@ -380,29 +380,13 @@ export class DemoHaDialog extends LitElement {
<td><code>--ha-dialog-surface-background</code></td>
<td>Dialog background color.</td>
</tr>
<tr>
<td><code>--ha-dialog-surface-backdrop-filter</code></td>
<td>Backdrop filter applied to the dialog surface.</td>
</tr>
<tr>
<td><code>--dialog-box-shadow</code></td>
<td>Dialog surface box shadow.</td>
</tr>
<tr>
<td><code>--ha-dialog-border-radius</code></td>
<td>Border radius of the dialog surface.</td>
</tr>
<tr>
<td><code>--ha-dialog-scrim-backdrop-filter</code></td>
<td>Backdrop filter applied to the dialog scrim.</td>
</tr>
<tr>
<td><code>--dialog-backdrop-filter</code></td>
<td>Legacy fallback for the dialog scrim backdrop filter.</td>
</tr>
<tr>
<td><code>--mdc-dialog-scrim-color</code></td>
<td>Dialog scrim color (legacy compatibility).</td>
<td><code>--dialog-z-index</code></td>
<td>Z-index for the dialog.</td>
</tr>
<tr>
<td><code>--dialog-surface-margin-top</code></td>

View File

@@ -92,7 +92,7 @@
"@webcomponents/scoped-custom-element-registry": "0.0.10",
"@webcomponents/webcomponentsjs": "2.8.0",
"app-datepicker": "5.1.1",
"barcode-detector": "3.1.0",
"barcode-detector": "3.0.8",
"color-name": "2.1.0",
"comlink": "4.4.2",
"core-js": "3.48.0",
@@ -149,7 +149,7 @@
"@babel/plugin-transform-runtime": "7.29.0",
"@babel/preset-env": "7.29.0",
"@bundle-stats/plugin-webpack-filter": "4.21.10",
"@html-eslint/eslint-plugin": "0.57.0",
"@html-eslint/eslint-plugin": "0.56.0",
"@lokalise/node-api": "15.6.1",
"@octokit/auth-oauth-device": "8.0.3",
"@octokit/plugin-retry": "8.1.0",
@@ -214,7 +214,7 @@
"terser-webpack-plugin": "5.3.16",
"ts-lit-plugin": "2.0.2",
"typescript": "5.9.3",
"typescript-eslint": "8.56.1",
"typescript-eslint": "8.56.0",
"vite-tsconfig-paths": "6.1.1",
"vitest": "4.0.18",
"webpack-stats-plugin": "1.1.3",

View File

@@ -133,34 +133,33 @@ const computeStateToPartsFromEntityAttributes = (
),
});
} catch (_err) {
// fallback to default numeric formatting below
// fallback to default
}
if (parts.length) {
const TYPE_MAP: Record<string, ValuePart["type"]> = {
integer: "value",
group: "value",
decimal: "value",
fraction: "value",
literal: "literal",
currency: "unit",
};
const TYPE_MAP: Record<string, ValuePart["type"]> = {
integer: "value",
group: "value",
decimal: "value",
fraction: "value",
literal: "literal",
currency: "unit",
};
const valueParts: ValuePart[] = [];
const valueParts: ValuePart[] = [];
for (const part of parts) {
const type = TYPE_MAP[part.type];
if (!type) continue;
const last = valueParts[valueParts.length - 1];
// Merge consecutive numeric parts (e.g. "1" + "," + "234" + "." + "56" → "1,234.56")
if (type === "value" && last?.type === "value") {
last.value += part.value;
} else {
valueParts.push({ type, value: part.value });
}
for (const part of parts) {
const type = TYPE_MAP[part.type];
if (!type) continue;
const last = valueParts[valueParts.length - 1];
// Merge consecutive numeric parts (e.g. "1" + "," + "234" + "." + "56" → "1,234.56")
if (type === "value" && last?.type === "value") {
last.value += part.value;
} else {
valueParts.push({ type, value: part.value });
}
return valueParts;
}
return valueParts;
}
// default processing of numeric values

View File

@@ -31,18 +31,9 @@ type DialogSheetMode = "dialog" | "bottom-sheet";
* @slot footer - Dialog/sheet footer content.
*
* @cssprop --ha-dialog-surface-background - Dialog/sheet background color.
* @cssprop --ha-dialog-surface-backdrop-filter - Dialog/sheet backdrop filter.
* @cssprop --dialog-box-shadow - Dialog box shadow (dialog mode only).
* @cssprop --ha-dialog-border-radius - Border radius of the dialog surface (dialog mode only).
* @cssprop --ha-dialog-show-duration - Show animation duration (dialog mode only).
* @cssprop --ha-dialog-hide-duration - Hide animation duration (dialog mode only).
* @cssprop --ha-dialog-scrim-backdrop-filter - Dialog/sheet scrim backdrop filter.
* @cssprop --dialog-backdrop-filter - Dialog/sheet scrim backdrop filter (legacy).
* @cssprop --mdc-dialog-scrim-color - Dialog/sheet scrim color (legacy).
* @cssprop --ha-bottom-sheet-surface-background - Bottom sheet background color (sheet mode only).
* @cssprop --ha-bottom-sheet-surface-backdrop-filter - Bottom sheet backdrop filter (sheet mode only).
* @cssprop --ha-bottom-sheet-scrim-backdrop-filter - Bottom sheet scrim backdrop filter (sheet mode only).
* @cssprop --ha-bottom-sheet-scrim-color - Bottom sheet scrim color (sheet mode only).
*
* @attr {boolean} open - Controls the dialog/sheet open state.
* @attr {("alert"|"standard")} type - Dialog type (dialog mode only). Defaults to "standard".

View File

@@ -25,27 +25,6 @@ const SWIPE_LOCKED_COMPONENTS = new Set([
const SWIPE_LOCKED_CLASSES = new Set(["volume-slider-container", "forecast"]);
/**
* Home Assistant bottom sheet component.
*
* @element ha-bottom-sheet
* @extends {LitElement}
*
* @cssprop --ha-bottom-sheet-height - Preferred height of the bottom sheet.
* @cssprop --ha-bottom-sheet-max-height - Maximum height of the bottom sheet.
* @cssprop --ha-bottom-sheet-max-width - Maximum width of the bottom sheet.
* @cssprop --ha-bottom-sheet-border-radius - Top border radius of the bottom sheet.
* @cssprop --ha-bottom-sheet-surface-background - Bottom sheet background color.
* @cssprop --ha-bottom-sheet-surface-backdrop-filter - Bottom sheet surface backdrop filter.
* @cssprop --ha-bottom-sheet-scrim-backdrop-filter - Bottom sheet scrim backdrop filter.
* @cssprop --ha-bottom-sheet-scrim-color - Bottom sheet scrim color.
*
* @cssprop --ha-dialog-surface-background - Bottom sheet background color fallback.
* @cssprop --ha-dialog-surface-backdrop-filter - Bottom sheet surface backdrop filter fallback.
* @cssprop --ha-dialog-scrim-backdrop-filter - Bottom sheet scrim backdrop filter fallback.
* @cssprop --dialog-backdrop-filter - Bottom sheet scrim backdrop filter legacy fallback.
* @cssprop --mdc-dialog-scrim-color - Bottom sheet scrim color legacy fallback.
*/
@customElement("ha-bottom-sheet")
export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
@property({ attribute: false }) public hass?: HomeAssistant;
@@ -406,26 +385,6 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
transform: var(--dialog-transform);
transition: var(--dialog-transition);
}
wa-drawer::part(dialog)::backdrop {
-webkit-backdrop-filter: var(
--ha-bottom-sheet-scrim-backdrop-filter,
var(
--ha-dialog-scrim-backdrop-filter,
var(--dialog-backdrop-filter, none)
)
);
backdrop-filter: var(
--ha-bottom-sheet-scrim-backdrop-filter,
var(
--ha-dialog-scrim-backdrop-filter,
var(--dialog-backdrop-filter, none)
)
);
background-color: var(
--ha-bottom-sheet-scrim-color,
var(--mdc-dialog-scrim-color, none)
);
}
wa-drawer::part(body) {
max-width: var(--ha-bottom-sheet-max-width);
width: 100%;
@@ -440,18 +399,7 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
);
background-color: var(
--ha-bottom-sheet-surface-background,
var(
--ha-dialog-surface-background,
var(--card-background-color, var(--ha-color-surface-default))
)
);
-webkit-backdrop-filter: var(
--ha-bottom-sheet-surface-backdrop-filter,
var(--ha-dialog-surface-backdrop-filter, none)
);
backdrop-filter: var(
--ha-bottom-sheet-surface-backdrop-filter,
var(--ha-dialog-surface-backdrop-filter, none)
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
);
padding: var(
--ha-bottom-sheet-padding,

View File

@@ -245,7 +245,7 @@ export class HaButton extends Button {
}
.label {
overflow: var(--ha-button-label-overflow, hidden);
overflow: hidden;
text-overflow: ellipsis;
padding: var(--ha-space-1) 0;
}

View File

@@ -1,9 +1,11 @@
import { mdiMenuDown } from "@mdi/js";
import type { TemplateResult } from "lit";
import type { HassEntity } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import type { HomeAssistant } from "../types";
import "./ha-attribute-icon";
import "./ha-dropdown";
import "./ha-dropdown-item";
import "./ha-icon";
@@ -14,10 +16,17 @@ export interface SelectOption {
value: string;
iconPath?: string;
icon?: string;
attributeIcon?: {
stateObj: HassEntity;
attribute: string;
attributeValue?: string;
};
}
@customElement("ha-control-select-menu")
export class HaControlSelectMenu extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean, attribute: "show-arrow" })
public showArrow = false;
@@ -38,9 +47,6 @@ export class HaControlSelectMenu extends LitElement {
@property({ attribute: false }) public options: SelectOption[] = [];
@property({ attribute: false })
public renderIcon?: (value: string) => TemplateResult<1> | typeof nothing;
@query("button") private _triggerButton!: HTMLButtonElement;
public override render() {
@@ -88,8 +94,14 @@ export class HaControlSelectMenu extends LitElement {
? html`<ha-svg-icon slot="icon" .path=${option.iconPath}></ha-svg-icon>`
: option.icon
? html`<ha-icon slot="icon" .icon=${option.icon}></ha-icon>`
: this.renderIcon
? html`<span slot="icon">${this.renderIcon(option.value)}</span>`
: option.attributeIcon
? html`<ha-attribute-icon
slot="icon"
.hass=${this.hass}
.stateObj=${option.attributeIcon.stateObj}
.attribute=${option.attributeIcon.attribute}
.attributeValue=${option.attributeIcon.attributeValue}
></ha-attribute-icon>`
: nothing}
${option.label}</ha-dropdown-item
>`;
@@ -107,20 +119,24 @@ export class HaControlSelectMenu extends LitElement {
}
private _renderIcon() {
const value = this.getValueObject(this.options, this.value);
const { iconPath, icon, attributeIcon } =
this.getValueObject(this.options, this.value) ?? {};
const defaultIcon = this.querySelector("[slot='icon']");
return html`
<div class="icon">
${value?.iconPath
? html`<ha-svg-icon
slot="icon"
.path=${value.iconPath}
></ha-svg-icon>`
: value?.icon
? html`<ha-icon slot="icon" .icon=${value.icon}></ha-icon>`
: this.renderIcon && this.value
? this.renderIcon(this.value)
${iconPath
? html`<ha-svg-icon slot="icon" .path=${iconPath}></ha-svg-icon>`
: icon
? html`<ha-icon slot="icon" .icon=${icon}></ha-icon>`
: attributeIcon
? html`<ha-attribute-icon
slot="icon"
.hass=${this.hass}
.stateObj=${attributeIcon.stateObj}
.attribute=${attributeIcon.attribute}
.attributeValue=${attributeIcon.attributeValue}
></ha-attribute-icon>`
: defaultIcon
? html`<slot name="icon"></slot>`
: nothing}
@@ -156,12 +172,12 @@ export class HaControlSelectMenu extends LitElement {
font-size: var(--ha-font-size-m);
line-height: 1.4;
width: auto;
color: var(--primary-text-color);
-webkit-tap-highlight-color: transparent;
}
.select-anchor {
border: none;
text-align: left;
color: var(--primary-text-color);
height: var(--control-select-menu-height);
padding: var(--control-select-menu-padding);
overflow: hidden;

View File

@@ -52,12 +52,7 @@ type DialogHideEvent = CustomEvent<{ source?: Element }>;
* @cssprop --ha-dialog-show-duration - Show animation duration.
* @cssprop --ha-dialog-hide-duration - Hide animation duration.
* @cssprop --ha-dialog-surface-background - Dialog background color.
* @cssprop --ha-dialog-surface-backdrop-filter - Dialog backdrop filter.
* @cssprop --dialog-box-shadow - Dialog box shadow.
* @cssprop --ha-dialog-border-radius - Border radius of the dialog surface.
* @cssprop --ha-dialog-scrim-backdrop-filter - Dialog scrim backdrop filter.
* @cssprop --dialog-backdrop-filter - Dialog scrim backdrop filter (legacy).
* @cssprop --mdc-dialog-scrim-color - Dialog scrim color (legacy).
* @cssprop --dialog-surface-margin-top - Top margin for the dialog surface.
*
* @attr {boolean} open - Controls the dialog open state.
@@ -276,6 +271,10 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
--spacing: var(--dialog-content-padding, var(--ha-space-6));
--show-duration: var(--ha-dialog-show-duration, 200ms);
--hide-duration: var(--ha-dialog-hide-duration, 200ms);
--ha-dialog-surface-background: var(
--card-background-color,
var(--ha-color-surface-default)
);
--wa-color-surface-raised: var(
--ha-dialog-surface-background,
var(--card-background-color, var(--ha-color-surface-default))
@@ -306,12 +305,6 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
}
wa-dialog::part(dialog) {
-webkit-backdrop-filter: var(
--ha-dialog-surface-backdrop-filter,
none
);
backdrop-filter: var(--ha-dialog-surface-backdrop-filter, none);
box-shadow: var(--dialog-box-shadow, var(--wa-shadow-l));
color: var(--primary-text-color);
min-width: var(--width, var(--full-width));
max-width: var(--width, var(--full-width));
@@ -341,18 +334,6 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
overflow: hidden;
}
wa-dialog::part(dialog)::backdrop {
-webkit-backdrop-filter: var(
--ha-dialog-scrim-backdrop-filter,
var(--dialog-backdrop-filter, none)
);
backdrop-filter: var(
--ha-dialog-scrim-backdrop-filter,
var(--dialog-backdrop-filter, none)
);
background-color: var(--mdc-dialog-scrim-color, none);
}
@media all and (max-width: 450px), all and (max-height: 500px) {
:host([type="standard"]) {
--ha-dialog-border-radius: 0;

View File

@@ -103,6 +103,10 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
@property({ attribute: "selected-section" }) public selectedSection?: string;
@property({ attribute: false }) public selectedValue?: string;
@property({ attribute: false }) public popoverAnchor?: Element | null;
@property({ type: Boolean, attribute: "use-top-label" })
public useTopLabel = false;
@@ -227,7 +231,8 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
without-arrow
distance="-4"
.placement=${this.popoverPlacement}
for="picker"
.for=${this.popoverAnchor ? null : "picker"}
.anchor=${this.popoverAnchor ?? null}
auto-size="vertical"
auto-size-padding="16"
@wa-after-show=${this._dialogOpened}
@@ -267,6 +272,7 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
.sections=${this.sections}
.sectionTitleFunction=${this.sectionTitleFunction}
.selectedSection=${this.selectedSection}
.selectedValue=${this.selectedValue}
.searchKeys=${this.searchKeys}
.customValueLabel=${this.customValueLabel}
></ha-picker-combo-box>
@@ -345,6 +351,7 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
this._opened = false;
this._pickerWrapperOpen = false;
this._unsubscribeTinyKeys?.();
fireEvent(this, "picker-closed");
}
private _valueChanged(ev: CustomEvent) {
@@ -476,4 +483,8 @@ declare global {
interface HTMLElementTagNameMap {
"ha-generic-picker": HaGenericPicker;
}
interface HASSDomEvents {
"picker-closed": undefined;
}
}

View File

@@ -74,7 +74,6 @@ export class HaIconButton extends LitElement {
);
--wa-color-on-normal: currentColor;
--wa-color-fill-quiet: transparent;
--ha-button-label-overflow: visible;
}
ha-button::after {
content: "";

View File

@@ -109,6 +109,8 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
@property() public value?: string;
@property({ attribute: false }) public selectedValue?: string;
@property({ attribute: false })
public searchKeys?: FuseWeightedKey[];
@@ -256,7 +258,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
.renderItem=${this._renderItem}
style="min-height: 36px;"
class=${this._listScrolled ? "scrolled" : ""}
.layout=${this.value && this._valuePinned
.layout=${this._selectedValue && this._valuePinned
? {
pin: {
index: this._getInitialSelectedIndex(),
@@ -418,7 +420,9 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
const renderer = this.rowRenderer || DEFAULT_ROW_RENDERER;
return html`<div
id=${`list-item-${index}`}
class="combo-box-row ${this._value === item.id ? "current-value" : ""}"
class="combo-box-row ${this._selectedValue === item.id
? "current-value"
: ""}"
.value=${item.id}
.index=${index}
@click=${this._valueSelected}
@@ -433,8 +437,8 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
this._listScrolled = top > 0;
}
private get _value() {
return this.value || "";
private get _selectedValue() {
return this.selectedValue || this.value || "";
}
private _valueSelected = (ev: MouseEvent) => {
@@ -771,14 +775,14 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
typeof item === "string" ? item : item?.id;
private _getInitialSelectedIndex() {
if (!this.virtualizerElement || this._search || !this.value) {
if (!this.virtualizerElement || this._search || !this._selectedValue) {
return 0;
}
const index = this.virtualizerElement.items.findIndex(
(item) =>
typeof item !== "string" &&
(item as PickerComboBoxItem).id === this.value
(item as PickerComboBoxItem).id === this._selectedValue
);
if (index === -1) {

View File

@@ -7,10 +7,11 @@ import Fuse from "fuse.js";
import type { HassServiceTarget } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing, unsafeCSS } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { ensureArray } from "../common/array/ensure-array";
import type { HASSDomEvent } from "../common/dom/fire_event";
import { fireEvent } from "../common/dom/fire_event";
import { isValidEntityId } from "../common/entity/valid_entity_id";
import { caseInsensitiveStringCompare } from "../common/string/compare";
@@ -42,6 +43,7 @@ import {
deviceMeetsFilter,
entityRegMeetsFilter,
getTargetComboBoxItemType,
type TargetItem,
type TargetType,
type TargetTypeFloorless,
} from "../data/target";
@@ -57,6 +59,7 @@ import type { HomeAssistant, ValueChangedEvent } from "../types";
import { brandsUrl } from "../util/brands-url";
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
import "./ha-generic-picker";
import type { HaGenericPicker } from "./ha-generic-picker";
import type { PickerComboBoxItem } from "./ha-picker-combo-box";
import "./ha-svg-icon";
import "./ha-tree-indicator";
@@ -65,6 +68,12 @@ import "./target-picker/ha-target-picker-value-chip";
const SEPARATOR = "________";
const CREATE_ID = "___create-new-entity___";
const isTargetType = (value: string): value is TargetType =>
value === "entity" ||
value === "device" ||
value === "area" ||
value === "label" ||
value === "floor";
@customElement("ha-target-picker")
export class HaTargetPicker extends SubscribeMixin(LitElement) {
@@ -106,13 +115,19 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
@state() private _selectedSection?: TargetTypeFloorless;
@state() private _replaceTarget?: TargetItem;
@state() private _replaceTargetAnchor?: HTMLElement;
@state() private _configEntryLookup: Record<string, ConfigEntry> = {};
@state()
@consume({ context: labelsContext, subscribe: true })
private _labelRegistry!: LabelRegistryEntry[];
private _newTarget?: { type: TargetType; id: string };
@query("ha-generic-picker") private _picker?: HaGenericPicker;
private _newTarget?: TargetItem;
private _getDevicesMemoized = memoizeOne(getDevices);
@@ -286,6 +301,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
@replace-target-item=${this._handleReplace}
type="entity"
.hass=${this.hass}
.items=${{ entity: entityIds }}
@@ -301,6 +317,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
@replace-target-item=${this._handleReplace}
type="device"
.hass=${this.hass}
.items=${{ device: deviceIds }}
@@ -316,6 +333,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
@replace-target-item=${this._handleReplace}
type="area"
.hass=${this.hass}
.items=${{
@@ -334,6 +352,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
@replace-target-item=${this._handleReplace}
type="label"
.hass=${this.hass}
.items=${{ label: labelIds }}
@@ -390,9 +409,14 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
)}
.sectionTitleFunction=${this._sectionTitleFunction}
.selectedSection=${this._selectedSection}
.selectedValue=${this._replaceTarget
? `${this._replaceTarget.type}${SEPARATOR}${this._replaceTarget.id}`
: undefined}
.popoverAnchor=${this._replaceTargetAnchor}
.rowRenderer=${this._renderRow}
.getItems=${this._getItems}
@value-changed=${this._targetPicked}
@picker-closed=${this._handlePickerClosed}
.addButtonLabel=${this.hass.localize(
"ui.components.target-picker.add_target"
)}
@@ -411,34 +435,42 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
return;
}
const [type, id] = ev.detail.value.split(SEPARATOR);
this._addTarget(id, type as TargetType);
const [rawType, id] = value.split(SEPARATOR);
if (!id || !isTargetType(rawType)) {
return;
}
if (this._replaceTarget) {
this._replaceTargetItem(this._replaceTarget, { type: rawType, id });
return;
}
this._addTarget(id, rawType);
}
private _replaceTargetItem(currentTarget: TargetItem, newTarget: TargetItem) {
const value = this._replaceTargetInValue(
this.value,
currentTarget,
newTarget
);
if (value === this.value) {
return;
}
fireEvent(this, "value-changed", { value });
}
private _addTarget(id: string, type: TargetType) {
const typeId = `${type}_id`;
const value = this._addTargetToValue(this.value, { type, id });
if (typeId === "entity_id" && !isValidEntityId(id)) {
if (value === this.value) {
return;
}
if (
this.value &&
this.value[typeId] &&
ensureArray(this.value[typeId]).includes(id)
) {
return;
}
fireEvent(this, "value-changed", {
value: this.value
? {
...this.value,
[typeId]: this.value[typeId]
? [...ensureArray(this.value[typeId]), id]
: id,
}
: { [typeId]: id },
});
fireEvent(this, "value-changed", { value });
this.shadowRoot
?.querySelector(
@@ -447,6 +479,52 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
?.removeAttribute("collapsed");
}
private _replaceTargetInValue(
value: this["value"],
currentTarget: TargetItem,
newTarget: TargetItem
): this["value"] {
if (
!value ||
(currentTarget.type === newTarget.type &&
currentTarget.id === newTarget.id)
) {
return value;
}
const valueWithoutCurrent = this._removeItem(
value,
currentTarget.type,
currentTarget.id
);
return this._addTargetToValue(valueWithoutCurrent, newTarget);
}
private _addTargetToValue(
value: this["value"],
target: TargetItem
): this["value"] {
const typeId = `${target.type}_id`;
if (typeId === "entity_id" && !isValidEntityId(target.id)) {
return value;
}
if (value?.[typeId] && ensureArray(value[typeId]).includes(target.id)) {
return value;
}
return value
? {
...value,
[typeId]: value[typeId]
? [...ensureArray(value[typeId]), target.id]
: target.id,
}
: { [typeId]: target.id };
}
private _createNewDomainElement = (domain: string) => {
showHelperDetailDialog(this, {
domain,
@@ -461,14 +539,14 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
});
};
private _handleRemove(ev) {
private _handleRemove(ev: HASSDomEvent<HASSDomEvents["remove-target-item"]>) {
const { type, id } = ev.detail;
fireEvent(this, "value-changed", {
value: this._removeItem(this.value, type, id),
});
}
private _handleExpand(ev) {
private _handleExpand(ev: HASSDomEvent<HASSDomEvents["expand-target-item"]>) {
const type = ev.detail.type;
const itemId = ev.detail.id;
const newAreas: string[] = [];
@@ -614,6 +692,40 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
fireEvent(this, "value-changed", { value });
}
private _handleReplace(
ev: HASSDomEvent<HASSDomEvents["replace-target-item"]>
) {
ev.stopPropagation();
this._replaceTargetAnchor = ev
.composedPath()
.find(
(node): node is HTMLElement =>
node instanceof HTMLElement &&
node.tagName === "HA-TARGET-PICKER-ITEM-ROW"
);
const type = ev.detail.type;
if (type === "floor") {
this._selectedSection = "area";
} else if (
type === "entity" ||
type === "device" ||
type === "area" ||
type === "label"
) {
this._selectedSection = type;
} else {
return;
}
this._replaceTarget = { type, id: ev.detail.id };
this._picker?.open();
}
private _handlePickerClosed() {
this._replaceTarget = undefined;
this._replaceTargetAnchor = undefined;
}
private _addItems(
value: this["value"],
type: string,
@@ -704,6 +816,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
this.includeDomains,
this.includeDeviceClasses,
this.value,
this._replaceTarget,
searchString,
this._configEntryLookup,
this._selectedSection
@@ -718,10 +831,16 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
includeDomains: this["includeDomains"],
includeDeviceClasses: this["includeDeviceClasses"],
targetValue: this["value"],
replaceTarget: TargetItem | undefined,
searchTerm: string,
configEntryLookup: Record<string, ConfigEntry>,
filterType?: TargetTypeFloorless
) => {
const replacingEntityId =
replaceTarget?.type === "entity" ? replaceTarget.id : undefined;
const replacingDeviceId =
replaceTarget?.type === "device" ? replaceTarget.id : undefined;
const items: (
| string
| FloorComboBoxItem
@@ -739,9 +858,13 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
undefined,
undefined,
targetValue?.entity_id
? ensureArray(targetValue.entity_id)
? ensureArray(targetValue.entity_id).filter(
(entityId) => entityId !== replacingEntityId
)
: undefined,
replacingEntityId
? `entity${SEPARATOR}${replacingEntityId}`
: undefined,
undefined,
`entity${SEPARATOR}`
).sort(this._sortBySortingLabel);
@@ -772,9 +895,11 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
deviceFilter,
entityFilter,
targetValue?.device_id
? ensureArray(targetValue.device_id)
? ensureArray(targetValue.device_id).filter(
(deviceId) => deviceId !== replacingDeviceId
)
: undefined,
undefined,
replacingDeviceId,
`device${SEPARATOR}`
).sort(this._sortBySortingLabel);
@@ -810,8 +935,24 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
includeDeviceClasses,
deviceFilter,
entityFilter,
targetValue?.area_id ? ensureArray(targetValue.area_id) : undefined,
targetValue?.floor_id ? ensureArray(targetValue.floor_id) : undefined
targetValue?.area_id
? ensureArray(targetValue.area_id).filter(
(areaId) =>
areaId !==
(replaceTarget?.type === "area"
? replaceTarget.id
: undefined)
)
: undefined,
targetValue?.floor_id
? ensureArray(targetValue.floor_id).filter(
(floorId) =>
floorId !==
(replaceTarget?.type === "floor"
? replaceTarget.id
: undefined)
)
: undefined
);
if (searchTerm) {
@@ -860,7 +1001,15 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
includeDeviceClasses,
deviceFilter,
entityFilter,
targetValue?.label_id ? ensureArray(targetValue.label_id) : undefined,
targetValue?.label_id
? ensureArray(targetValue.label_id).filter(
(labelId) =>
labelId !==
(replaceTarget?.type === "label"
? replaceTarget.id
: undefined)
)
: undefined,
`label${SEPARATOR}`
).sort(this._sortBySortingLabel);
@@ -1111,14 +1260,9 @@ declare global {
}
interface HASSDomEvents {
"remove-target-item": {
type: string;
id: string;
};
"expand-target-item": {
type: string;
id: string;
};
"remove-target-item": TargetItem;
"expand-target-item": TargetItem;
"replace-target-item": TargetItem;
"remove-target-group": string;
}
}

View File

@@ -131,8 +131,16 @@ export class HaTargetPickerItemRow extends LitElement {
return nothing;
}
const replaceable = !this.subEntry && !this.expand;
return html`
<ha-md-list-item type="text" class=${notFound ? "error" : ""}>
<ha-md-list-item
type=${replaceable ? "button" : "text"}
class=${[notFound ? "error" : "", replaceable ? "replaceable" : ""]
.filter(Boolean)
.join(" ")}
@click=${replaceable ? this._replaceItem : undefined}
>
<div class="icon" slot="start">
${this.subEntry
? html`
@@ -565,7 +573,7 @@ export class HaTargetPickerItemRow extends LitElement {
this._domainName = domainToName(this.hass.localize, domain);
}
private _removeItem(ev) {
private _removeItem(ev: MouseEvent) {
ev.stopPropagation();
fireEvent(this, "remove-target-item", {
type: this.type,
@@ -589,7 +597,16 @@ export class HaTargetPickerItemRow extends LitElement {
}
}
private _openDetails() {
private _replaceItem(ev: MouseEvent) {
ev.stopPropagation();
fireEvent(this, "replace-target-item", {
type: this.type,
id: this.itemId,
});
}
private _openDetails(ev: MouseEvent) {
ev.stopPropagation();
showTargetDetailsDialog(this, {
title: this._itemData(this.type, this.itemId).name,
type: this.type,
@@ -626,6 +643,15 @@ export class HaTargetPickerItemRow extends LitElement {
color: var(--ha-color-on-warning-normal);
}
.replaceable {
cursor: pointer;
}
.replaceable:hover,
.replaceable:focus-visible {
background-color: var(--ha-color-fill-neutral-quiet-hover);
}
state-badge {
color: var(--ha-color-on-neutral-quiet);
}

View File

@@ -215,7 +215,7 @@ export class HaTargetPickerValueChip extends LitElement {
}
}
private _removeItem(ev) {
private _removeItem(ev: MouseEvent) {
ev.stopPropagation();
fireEvent(this, "remove-target-item", {
type: this.type,
@@ -223,7 +223,7 @@ export class HaTargetPickerValueChip extends LitElement {
});
}
private _handleExpand(ev) {
private _handleExpand(ev: MouseEvent) {
ev.stopPropagation();
fireEvent(this, "expand-target-item", {
type: this.type,

View File

@@ -16,6 +16,11 @@ export const TARGET_SEPARATOR = "________";
export type TargetType = "entity" | "device" | "area" | "label" | "floor";
export type TargetTypeFloorless = Exclude<TargetType, "floor">;
export interface TargetItem {
type: TargetType;
id: string;
}
export interface SingleHassServiceTarget {
entity_id?: string;
device_id?: string;

View File

@@ -9,7 +9,6 @@ import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-icon-button-group";
import "../../../components/ha-icon-button-toggle";
@@ -40,38 +39,6 @@ class MoreInfoClimate extends LitElement {
@state() private _mainControl: MainControl = "temperature";
private _renderPresetModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="preset_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private _renderFanModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="fan_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private _renderSwingModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="swing_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private _renderSwingHorizontalModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="swing_horizontal_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
protected willUpdate(changedProps: PropertyValues): void {
if (
changedProps.has("stateObj") &&
@@ -238,8 +205,12 @@ class MoreInfoClimate extends LitElement {
"preset_mode",
mode
),
attributeIcon: {
stateObj,
attribute: "preset_mode",
attributeValue: mode,
},
}))}
.renderIcon=${this._renderPresetModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
</ha-control-select-menu>
@@ -263,8 +234,12 @@ class MoreInfoClimate extends LitElement {
"fan_mode",
mode
),
attributeIcon: {
stateObj,
attribute: "fan_mode",
attributeValue: mode,
},
}))}
.renderIcon=${this._renderFanModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiFan}></ha-svg-icon>
</ha-control-select-menu>
@@ -288,8 +263,12 @@ class MoreInfoClimate extends LitElement {
"swing_mode",
mode
),
attributeIcon: {
stateObj,
attribute: "swing_mode",
attributeValue: mode,
},
}))}
.renderIcon=${this._renderSwingModeIcon}
>
<ha-svg-icon
slot="icon"
@@ -318,9 +297,13 @@ class MoreInfoClimate extends LitElement {
"swing_horizontal_mode",
mode
),
attributeIcon: {
stateObj,
attribute: "swing_horizontal_mode",
attributeValue: mode,
},
})
)}
.renderIcon=${this._renderSwingHorizontalModeIcon}
>
<ha-svg-icon
slot="icon"

View File

@@ -40,22 +40,6 @@ class MoreInfoFan extends LitElement {
@state() public _presetMode?: string;
private _renderPresetModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="preset_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private _renderDirectionIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="direction"
.attributeValue=${value}
></ha-attribute-icon>`;
private _toggle = () => {
const service = this.stateObj?.state === "on" ? "turn_off" : "turn_on";
forwardHaptic(this, "light");
@@ -208,9 +192,15 @@ class MoreInfoFan extends LitElement {
"preset_mode",
mode
),
attributeIcon: this.stateObj
? {
stateObj: this.stateObj,
attribute: "preset_mode",
attributeValue: mode,
}
: undefined,
})
)}
.renderIcon=${this._renderPresetModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
</ha-control-select-menu>
@@ -236,8 +226,14 @@ class MoreInfoFan extends LitElement {
direction
)
: direction,
attributeIcon: this.stateObj
? {
stateObj: this.stateObj,
attribute: "direction",
attributeValue: direction,
}
: undefined,
}))}
.renderIcon=${this._renderDirectionIcon}
>
<ha-attribute-icon
slot="icon"

View File

@@ -23,14 +23,6 @@ class MoreInfoHumidifier extends LitElement {
@state() public _mode?: string;
private _renderModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="mode"
.attributeValue=${value}
></ha-attribute-icon>`;
protected willUpdate(changedProps: PropertyValues): void {
super.willUpdate(changedProps);
if (changedProps.has("stateObj")) {
@@ -114,8 +106,14 @@ class MoreInfoHumidifier extends LitElement {
mode
)
: mode,
attributeIcon: stateObj
? {
stateObj,
attribute: "mode",
attributeValue: mode,
}
: undefined,
})) || []}
.renderIcon=${this._renderModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
</ha-control-select-menu>

View File

@@ -55,14 +55,6 @@ class MoreInfoLight extends LitElement {
@state() private _mainControl: MainControl = "brightness";
private _renderEffectIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="effect"
.attributeValue=${value}
></ha-attribute-icon>`;
protected updated(changedProps: PropertyValues<typeof this>): void {
if (changedProps.has("stateObj")) {
this._effect = this.stateObj?.attributes.effect;
@@ -279,9 +271,15 @@ class MoreInfoLight extends LitElement {
effect
)
: effect,
attributeIcon: this.stateObj
? {
stateObj: this.stateObj,
attribute: "effect",
attributeValue: effect,
}
: undefined,
})
)}
.renderIcon=${this._renderEffectIcon}
>
<ha-svg-icon slot="icon" .path=${mdiCreation}></ha-svg-icon>
</ha-control-select-menu>

View File

@@ -24,14 +24,6 @@ class MoreInfoWaterHeater extends LitElement {
@property({ attribute: false }) public stateObj?: WaterHeaterEntity;
private _renderOperationModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="operation_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
protected render() {
if (!this.stateObj) {
return nothing;
@@ -93,8 +85,12 @@ class MoreInfoWaterHeater extends LitElement {
.map((mode) => ({
value: mode,
label: this.hass.formatEntityState(stateObj, mode),
attributeIcon: {
stateObj,
attribute: "operation_mode",
attributeValue: mode,
},
}))}
.renderIcon=${this._renderOperationModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiWaterBoiler}></ha-svg-icon>
</ha-control-select-menu>

View File

@@ -2,27 +2,17 @@ import type { HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { computeAttributeNameDisplay } from "../../common/entity/compute_attribute_display";
import checkValidDate from "../../common/datetime/check_valid_date";
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
import "../../components/ha-attribute-value";
import "../../components/ha-card";
import type { LocalizeKeys } from "../../common/translations/localize";
import { computeShownAttributes } from "../../data/entity/entity_attributes";
import type { ExtEntityRegistryEntry } from "../../data/entity/entity_registry";
import type { HomeAssistant } from "../../types";
import "../../components/ha-yaml-editor";
interface DetailsViewParams {
entityId: string;
}
interface DetailEntry {
translationKey: LocalizeKeys;
value: string;
}
@customElement("ha-more-info-details")
class HaMoreInfoDetails extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -31,8 +21,6 @@ class HaMoreInfoDetails extends LitElement {
@property({ attribute: false }) public params?: DetailsViewParams;
@property({ attribute: false }) public yamlMode = false;
@state() private _stateObj?: HassEntity;
protected willUpdate(changedProps: PropertyValues): void {
@@ -49,127 +37,60 @@ class HaMoreInfoDetails extends LitElement {
return nothing;
}
const { stateEntries, attributes, yamlData } = this._getDetailData(
this._stateObj
const translatedState = this.hass.formatEntityState(this._stateObj);
const detailsAttributes = computeShownAttributes(this._stateObj);
const detailsAttributeSet = new Set(detailsAttributes);
const builtInAttributes = Object.keys(this._stateObj.attributes).filter(
(attribute) => !detailsAttributeSet.has(attribute)
);
const allAttributes = [...detailsAttributes, ...builtInAttributes];
return html`
<div class="content">
${this.yamlMode
? html`<ha-yaml-editor
.hass=${this.hass}
.value=${yamlData}
read-only
auto-update
></ha-yaml-editor>`
: html`
<section class="section">
<h2 class="section-title">
${this.hass.localize(
"ui.components.entity.entity-state-picker.state"
)}
</h2>
<ha-card>
<div class="card-content">
<div class="data-group">
${stateEntries.map(
(entry) =>
html`<div class="data-entry">
<div class="key">
${this.hass.localize(entry.translationKey)}
</div>
<div class="value">${entry.value}</div>
</div>`
)}
</div>
<section class="section">
<h2 class="section-title">
${this.hass.localize(
"ui.components.entity.entity-state-picker.state"
)}
</h2>
<ha-card>
<div class="card-content">
<div class="attribute-group">
<div class="data-entry">
<div class="key">
${this.hass.localize(
"ui.dialogs.more_info_control.translated"
)}
</div>
</ha-card>
</section>
<div class="value">${translatedState}</div>
</div>
<div class="data-entry">
<div class="key">
${this.hass.localize("ui.dialogs.more_info_control.raw")}
</div>
<div class="value">${this._stateObj.state}</div>
</div>
</div>
</div>
</ha-card>
</section>
<section class="section">
<h2 class="section-title">
${this.hass.localize(
"ui.dialogs.more_info_control.attributes"
)}
</h2>
<ha-card>
<div class="card-content">
<div class="data-group">
${this._renderAttributes(attributes)}
</div>
</div>
</ha-card>
</section>
`}
<section class="section">
<h2 class="section-title">
${this.hass.localize("ui.dialogs.more_info_control.attributes")}
</h2>
<ha-card>
<div class="card-content">
<div class="attribute-group">
${this._renderAttributes(allAttributes)}
</div>
</div>
</ha-card>
</section>
</div>
`;
}
private _getDetailData = memoizeOne(
(
stateObj: HassEntity
): {
stateEntries: DetailEntry[];
attributes: string[];
yamlData: {
state: {
translated: string;
raw: string;
last_changed: string;
last_updated: string;
};
attributes: Record<string, string>;
};
} => {
const translatedState = this.hass.formatEntityState(stateObj);
const detailsAttributes = computeShownAttributes(stateObj);
const detailsAttributeSet = new Set(detailsAttributes);
const builtInAttributes = Object.keys(stateObj.attributes).filter(
(attribute) => !detailsAttributeSet.has(attribute)
);
return {
stateEntries: [
{
translationKey: "ui.dialogs.more_info_control.translated",
value: translatedState,
},
{
translationKey: "ui.dialogs.more_info_control.raw",
value: stateObj.state,
},
{
translationKey: "ui.dialogs.more_info_control.last_changed",
value: this._formatTimestamp(stateObj.last_changed),
},
{
translationKey: "ui.dialogs.more_info_control.last_updated",
value: this._formatTimestamp(stateObj.last_updated),
},
],
attributes: [...detailsAttributes, ...builtInAttributes],
yamlData: {
state: {
translated: translatedState,
raw: stateObj.state,
last_changed: stateObj.last_changed,
last_updated: stateObj.last_updated,
},
attributes: stateObj.attributes,
},
};
}
);
private _formatTimestamp(value: string): string {
const date = new Date(value);
return checkValidDate(date)
? formatDateTimeWithSeconds(date, this.hass.locale, this.hass.config)
: value;
}
private _renderAttributes(attributes: string[]) {
if (attributes.length === 0) {
return html`<div class="empty">
@@ -238,7 +159,7 @@ class HaMoreInfoDetails extends LitElement {
border-bottom: 1px solid var(--divider-color);
}
.data-group .data-entry:last-of-type {
.attribute-group .data-entry:last-of-type {
border-bottom: none;
}

View File

@@ -1,7 +1,6 @@
import {
mdiChartBoxOutline,
mdiClose,
mdiCodeBraces,
mdiCogOutline,
mdiDevices,
mdiDotsVertical,
@@ -133,8 +132,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
@state() private _infoEditMode = false;
@state() private _detailsYamlMode = false;
@state() private _isEscapeEnabled = true;
@state() private _sensorNumericDeviceClasses?: string[] = [];
@@ -185,7 +182,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
this._parentEntityIds = [];
this._entry = undefined;
this._infoEditMode = false;
this._detailsYamlMode = false;
this._initialView = DEFAULT_VIEW;
this._currView = DEFAULT_VIEW;
this._childView = undefined;
@@ -255,7 +251,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
private _goBack() {
if (this._childView) {
this._childView = undefined;
this._detailsYamlMode = false;
return;
}
if (this._initialView !== this._currView) {
@@ -319,10 +314,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
this._infoEditMode = !this._infoEditMode;
}
private _toggleDetailsYamlMode() {
this._detailsYamlMode = !this._detailsYamlMode;
}
private _handleToggleInfoEditModeEvent(ev) {
this._infoEditMode = ev.detail;
}
@@ -646,18 +637,7 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
</ha-dropdown-item>
</ha-dropdown>
`
: this._childView?.viewTag === "ha-more-info-details"
? html`
<ha-icon-button
slot="headerActionItems"
.label=${this.hass.localize(
"ui.dialogs.more_info_control.toggle_yaml_mode"
)}
.path=${mdiCodeBraces}
@click=${this._toggleDetailsYamlMode}
></ha-icon-button>
`
: nothing}
: nothing}
<div
class=${classMap({
"content-wrapper": true,
@@ -683,7 +663,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
hass: this.hass,
entry: this._entry,
params: this._childView.viewParams,
yamlMode: this._detailsYamlMode,
})}
</div>
`
@@ -752,7 +731,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
if (changedProps.has("_currView")) {
this._childView = undefined;
this._infoEditMode = false;
this._detailsYamlMode = false;
}
}

View File

@@ -36,7 +36,7 @@ import { showQuickBar } from "../../../dialogs/quick-bar/show-dialog-quick-bar";
import { showRestartDialog } from "../../../dialogs/restart/show-dialog-restart";
import { showShortcutsDialog } from "../../../dialogs/shortcuts/show-shortcuts-dialog";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles";
import { haStyle, haStyleScrollbar } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { isMac } from "../../../util/is_mac";
@@ -255,88 +255,90 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
</ha-dropdown-item>
</ha-dropdown>
<ha-config-section
.narrow=${this.narrow}
.isWide=${this.isWide}
full-width
>
${repairsIssues.length || canInstallUpdates.length
? html`<ha-card outlined>
${repairsIssues.length
? html`
<ha-config-repairs
<div class="content ha-scrollbar">
<ha-config-section
.narrow=${this.narrow}
.isWide=${this.isWide}
full-width
>
${repairsIssues.length || canInstallUpdates.length
? html`<ha-card outlined>
${repairsIssues.length
? html`
<ha-config-repairs
.hass=${this.hass}
.narrow=${this.narrow}
.total=${totalRepairIssues}
.repairsIssues=${repairsIssues}
></ha-config-repairs>
${totalRepairIssues > repairsIssues.length
? html`
<ha-assist-chip
href="/config/repairs"
.label=${this.hass.localize(
"ui.panel.config.repairs.more_repairs",
{
count:
totalRepairIssues - repairsIssues.length,
}
)}
>
</ha-assist-chip>
`
: ""}
`
: ""}
${repairsIssues.length && canInstallUpdates.length
? html`<hr />`
: ""}
${canInstallUpdates.length
? html`
<ha-config-updates
.hass=${this.hass}
.narrow=${this.narrow}
.total=${totalUpdates}
.updateEntities=${canInstallUpdates}
.isInstallable=${true}
></ha-config-updates>
${totalUpdates > canInstallUpdates.length
? html`
<ha-assist-chip
href="/config/updates"
label=${this.hass.localize(
"ui.panel.config.updates.more_updates",
{
count:
totalUpdates - canInstallUpdates.length,
}
)}
>
</ha-assist-chip>
`
: ""}
`
: ""}
</ha-card>`
: ""}
${this._pages(
this.cloudStatus,
isComponentLoaded(this.hass, "cloud"),
this.hass.auth.external?.config.hasSettingsScreen
).map((categoryPages) =>
categoryPages.length === 0
? nothing
: html`
<ha-card outlined>
<ha-config-navigation
.hass=${this.hass}
.narrow=${this.narrow}
.total=${totalRepairIssues}
.repairsIssues=${repairsIssues}
></ha-config-repairs>
${totalRepairIssues > repairsIssues.length
? html`
<ha-assist-chip
href="/config/repairs"
.label=${this.hass.localize(
"ui.panel.config.repairs.more_repairs",
{
count:
totalRepairIssues - repairsIssues.length,
}
)}
>
</ha-assist-chip>
`
: ""}
`
: ""}
${repairsIssues.length && canInstallUpdates.length
? html`<hr />`
: ""}
${canInstallUpdates.length
? html`
<ha-config-updates
.hass=${this.hass}
.narrow=${this.narrow}
.total=${totalUpdates}
.updateEntities=${canInstallUpdates}
.isInstallable=${true}
></ha-config-updates>
${totalUpdates > canInstallUpdates.length
? html`
<ha-assist-chip
href="/config/updates"
label=${this.hass.localize(
"ui.panel.config.updates.more_updates",
{
count:
totalUpdates - canInstallUpdates.length,
}
)}
>
</ha-assist-chip>
`
: ""}
`
: ""}
</ha-card>`
: ""}
${this._pages(
this.cloudStatus,
isComponentLoaded(this.hass, "cloud"),
this.hass.auth.external?.config.hasSettingsScreen
).map((categoryPages) =>
categoryPages.length === 0
? nothing
: html`
<ha-card outlined>
<ha-config-navigation
.hass=${this.hass}
.narrow=${this.narrow}
.pages=${categoryPages}
></ha-config-navigation>
</ha-card>
`
)}
<ha-tip .hass=${this.hass}>${this._tip}</ha-tip>
</ha-config-section>
.pages=${categoryPages}
></ha-config-navigation>
</ha-card>
`
)}
<ha-tip .hass=${this.hass}>${this._tip}</ha-tip>
</ha-config-section>
</div>
</ha-top-app-bar-fixed>
`;
}
@@ -392,7 +394,36 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleScrollbar,
css`
:host {
display: block;
height: 100%;
}
ha-top-app-bar-fixed {
height: 100%;
overflow: hidden;
}
.content {
height: calc(
100vh - var(--header-height, 0px) - var(
--safe-area-inset-top,
0px
) - var(--safe-area-inset-bottom, 0px)
);
height: calc(
100dvh - var(--header-height, 0px) - var(
--safe-area-inset-top,
0px
) - var(--safe-area-inset-bottom, 0px)
);
padding-bottom: var(--ha-space-5);
box-sizing: border-box;
overflow-x: hidden;
}
:host(:not([narrow])) ha-card:last-child {
margin-bottom: 24px;
}

View File

@@ -86,7 +86,6 @@ class DialogSystemLogDetail extends LitElement {
<ha-dialog
.hass=${this.hass}
.open=${this._open}
width="large"
@closed=${this._dialogClosed}
>
<span slot="headerTitle">${title}</span>

View File

@@ -143,7 +143,8 @@ class HaConfigRepairs extends LitElement {
}
} else if (
issue.domain === "vacuum" &&
issue.translation_key === "segments_changed"
(issue.translation_key === "segments_changed" ||
issue.translation_key === "segments_mapping_not_configured")
) {
const data = await fetchRepairsIssueData(
this.hass.connection,

View File

@@ -670,10 +670,10 @@ export class AssistPipelineDebug extends LitElement {
background-color: var(--light-primary-color);
color: var(--text-light-primary-color, var(--primary-text-color));
direction: var(--direction);
}
.tool_result [slot="header"] {
color: var(--text-light-primary-color, var(--primary-text-color));
--primary-text-color: var(
--text-light-primary-color,
var(--primary-text-color)
);
}
.message.user,

View File

@@ -49,14 +49,6 @@ class HuiClimateFanModesCardFeature
@state() _currentFanMode?: string;
private _renderFanModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="fan_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
@@ -183,8 +175,14 @@ class HuiClimateFanModesCardFeature
.value=${this._currentFanMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options}
.renderIcon=${this._renderFanModeIcon}
.options=${options.map((option) => ({
...option,
attributeIcon: {
stateObj: stateObj,
attribute: "fan_mode",
attributeValue: option.value,
},
}))}
><ha-svg-icon slot="icon" .path=${mdiFan}></ha-svg-icon>
</ha-control-select-menu>
`;

View File

@@ -48,14 +48,6 @@ class HuiClimatePresetModesCardFeature
@state() _currentPresetMode?: string;
private _renderPresetModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="preset_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
@@ -187,8 +179,14 @@ class HuiClimatePresetModesCardFeature
.value=${this._currentPresetMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options}
.renderIcon=${this._renderPresetModeIcon}
.options=${options.map((option) => ({
...option,
attributeIcon: {
stateObj: stateObj,
attribute: "preset_mode",
attributeValue: option.value,
},
}))}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
</ha-control-select-menu>

View File

@@ -48,14 +48,6 @@ class HuiClimateSwingHorizontalModesCardFeature
@state() _currentSwingHorizontalMode?: string;
private _renderSwingHorizontalModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="swing_horizontal_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
@@ -195,8 +187,14 @@ class HuiClimateSwingHorizontalModesCardFeature
.value=${this._currentSwingHorizontalMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options}
.renderIcon=${this._renderSwingHorizontalModeIcon}
.options=${options.map((option) => ({
...option,
attributeIcon: {
stateObj: stateObj,
attribute: "swing_horizontal_mode",
attributeValue: option.value,
},
}))}
>
<ha-svg-icon slot="icon" .path=${mdiArrowOscillating}></ha-svg-icon>
</ha-control-select-menu>

View File

@@ -48,14 +48,6 @@ class HuiClimateSwingModesCardFeature
@state() _currentSwingMode?: string;
private _renderSwingModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="swing_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
@@ -187,8 +179,14 @@ class HuiClimateSwingModesCardFeature
.value=${this._currentSwingMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options}
.renderIcon=${this._renderSwingModeIcon}
.options=${options.map((option) => ({
...option,
attributeIcon: {
stateObj,
attribute: "swing_mode",
attributeValue: option.value,
},
}))}
><ha-svg-icon slot="icon" .path=${mdiArrowOscillating}></ha-svg-icon>
</ha-control-select-menu>
`;

View File

@@ -47,14 +47,6 @@ class HuiFanPresetModesCardFeature
@state() _currentPresetMode?: string;
private _renderPresetModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="preset_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
@@ -181,8 +173,14 @@ class HuiFanPresetModesCardFeature
.value=${this._currentPresetMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options}
.renderIcon=${this._renderPresetModeIcon}
.options=${options.map((option) => ({
...option,
attributeIcon: {
stateObj: stateObj,
attribute: "preset_mode",
attributeValue: option.value,
},
}))}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
</ha-control-select-menu>

View File

@@ -48,14 +48,6 @@ class HuiHumidifierModesCardFeature
@state() _currentMode?: string;
private _renderModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
@@ -182,8 +174,14 @@ class HuiHumidifierModesCardFeature
.value=${this._currentMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options}
.renderIcon=${this._renderModeIcon}
.options=${options.map((option) => ({
...option,
attributeIcon: {
stateObj,
attribute: "mode",
attributeValue: option.value,
},
}))}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
</ha-control-select-menu>

View File

@@ -49,14 +49,6 @@ class HuiWaterHeaterOperationModeCardFeature
@state() _currentOperationMode?: OperationMode;
private _renderOperationModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="operation_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
@@ -161,8 +153,14 @@ class HuiWaterHeaterOperationModeCardFeature
.value=${this._currentOperationMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options}
.renderIcon=${this._renderOperationModeIcon}
.options=${options.map((option) => ({
...option,
attributeIcon: {
stateObj: this._stateObj,
attribute: "operation_mode",
attributeValue: option.value,
},
}))}
>
<ha-svg-icon slot="icon" .path=${mdiWaterBoiler}></ha-svg-icon>
</ha-control-select-menu>

View File

@@ -218,9 +218,7 @@ function formatTooltip(
}
// when comparing the first value is offset to match the main period
// and the real date is in the third value
// find the first param with the real date to handle gap-filled entries
const origDate = params.find((p) => p.value?.[2] != null)?.value?.[2];
const date = new Date(origDate ?? params[0].value?.[0]);
const date = new Date(params[0].value?.[2] ?? params[0].value?.[0]);
let period: string;
if (suggestedPeriod === "month") {

View File

@@ -6,7 +6,6 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeDomain } from "../../../common/entity/compute_domain";
import "../../../components/ha-spinner";
import type { HistoryStates } from "../../../data/history";
import { subscribeHistoryStatesTimeWindow } from "../../../data/history";
import type { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entities";
@@ -67,8 +66,6 @@ export class HuiGraphHeaderFooter
private _error?: string;
private _history?: HistoryStates;
private _interval?: number;
private _subscribed?: Promise<(() => Promise<void>) | undefined>;
@@ -164,8 +161,24 @@ export class HuiGraphHeaderFooter
// Message came in before we had a chance to unload
return;
}
this._history = combinedHistory;
this._computeCoordinates();
const width = this.clientWidth || this.offsetWidth;
// sample to 1 point per hour or 1 point per 5 pixels
const maxDetails = Math.max(
10,
this._config.detail! > 1
? Math.max(width / 5, this._config.hours_to_show!)
: this._config.hours_to_show!
);
const useMean = this._config.detail !== 2;
const { points } = coordinatesMinimalResponseCompressedState(
combinedHistory[this._config.entity],
width,
width / 5,
maxDetails,
{ minY: this._config.limits?.min, maxY: this._config.limits?.max },
useMean
);
this._coordinates = points;
},
this._config.hours_to_show!,
[this._config.entity]
@@ -177,63 +190,10 @@ export class HuiGraphHeaderFooter
this._setRedrawTimer();
}
private _computeCoordinates() {
if (!this._history || !this._config) {
return;
}
const entityHistory = this._history[this._config.entity];
if (!entityHistory?.length) {
return;
}
const width = this.clientWidth || this.offsetWidth;
// sample to 1 point per hour or 1 point per 5 pixels
const maxDetails = Math.max(
10,
this._config.detail! > 1
? Math.max(width / 5, this._config.hours_to_show!)
: this._config.hours_to_show!
);
const useMean = this._config.detail !== 2;
const { points } = coordinatesMinimalResponseCompressedState(
entityHistory,
width,
width / 5,
maxDetails,
{ minY: this._config.limits?.min, maxY: this._config.limits?.max },
useMean
);
this._coordinates = points;
}
private _redrawGraph() {
if (!this._history || !this._config?.hours_to_show) {
return;
if (this._coordinates) {
this._coordinates = [...this._coordinates];
}
const entityId = this._config.entity;
const entityHistory = this._history[entityId];
if (entityHistory?.length) {
const purgeBeforeTimestamp =
(Date.now() - this._config.hours_to_show * 60 * 60 * 1000) / 1000;
let purgedHistory = entityHistory.filter(
(entry) => entry.lu >= purgeBeforeTimestamp
);
if (purgedHistory.length !== entityHistory.length) {
if (
!purgedHistory.length ||
purgedHistory[0].lu !== purgeBeforeTimestamp
) {
// Preserve the last expired state as the start boundary
const lastExpiredState = {
...entityHistory[entityHistory.length - purgedHistory.length - 1],
};
lastExpiredState.lu = purgeBeforeTimestamp;
delete lastExpiredState.lc;
purgedHistory = [lastExpiredState, ...purgedHistory];
}
this._history = { ...this._history, [entityId]: purgedHistory };
}
}
this._computeCoordinates();
}
private _setRedrawTimer() {
@@ -251,7 +211,6 @@ export class HuiGraphHeaderFooter
this._subscribed.then((unsub) => unsub?.());
this._subscribed = undefined;
}
this._history = undefined;
}
protected updated(changedProps: PropertyValues) {

View File

@@ -631,8 +631,11 @@ class HUIRoot extends LitElement {
`;
}
private _handleWindowScroll = () => {
this.toggleAttribute("scrolled", window.scrollY !== 0);
private _handleContainerScroll = () => {
this.toggleAttribute(
"scrolled",
this._viewRoot ? this._viewRoot.scrollTop !== 0 : false
);
};
private _locationChanged = () => {
@@ -663,7 +666,7 @@ class HUIRoot extends LitElement {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
window.addEventListener("scroll", this._handleWindowScroll, {
this._viewRoot?.addEventListener("scroll", this._handleContainerScroll, {
passive: true,
});
this._handleUrlChanged();
@@ -674,7 +677,7 @@ class HUIRoot extends LitElement {
public connectedCallback(): void {
super.connectedCallback();
window.addEventListener("scroll", this._handleWindowScroll, {
this._viewRoot?.addEventListener("scroll", this._handleContainerScroll, {
passive: true,
});
window.addEventListener("popstate", this._handlePopState);
@@ -685,10 +688,13 @@ class HUIRoot extends LitElement {
public disconnectedCallback(): void {
super.disconnectedCallback();
window.removeEventListener("scroll", this._handleWindowScroll);
this._viewRoot?.removeEventListener("scroll", this._handleContainerScroll);
window.removeEventListener("popstate", this._handlePopState);
window.removeEventListener("location-changed", this._locationChanged);
this.toggleAttribute("scrolled", window.scrollY !== 0);
this.toggleAttribute(
"scrolled",
this._viewRoot ? this._viewRoot.scrollTop !== 0 : false
);
// Re-enable history scroll restoration when leaving the page
window.history.scrollRestoration = "auto";
}
@@ -821,9 +827,11 @@ class HUIRoot extends LitElement {
(this._restoreScroll && this._viewScrollPositions[newSelectView]) ||
0;
this._restoreScroll = false;
requestAnimationFrame(() =>
scrollTo({ behavior: "auto", top: position })
);
requestAnimationFrame(() => {
if (this._viewRoot) {
this._viewRoot.scrollTo({ behavior: "auto", top: position });
}
});
}
this._selectView(newSelectView, force);
});
@@ -1148,7 +1156,7 @@ class HUIRoot extends LitElement {
const path = this.config.views[viewIndex].path || viewIndex;
this._navigateToView(path);
} else if (!this._editMode) {
scrollTo({ behavior: "smooth", top: 0 });
this._viewRoot?.scrollTo({ behavior: "smooth", top: 0 });
}
}
@@ -1159,7 +1167,7 @@ class HUIRoot extends LitElement {
// Save scroll position of current view
if (this._curView != null) {
this._viewScrollPositions[this._curView] = window.scrollY;
this._viewScrollPositions[this._curView] = this._viewRoot?.scrollTop ?? 0;
}
viewIndex = viewIndex === undefined ? 0 : viewIndex;
@@ -1467,9 +1475,14 @@ class HUIRoot extends LitElement {
hui-view-container {
position: relative;
display: flex;
min-height: 100vh;
height: calc(
100vh - var(--header-height) - var(--safe-area-inset-top) - var(
--view-container-padding-top,
0px
)
);
box-sizing: border-box;
padding-top: calc(
margin-top: calc(
var(--header-height) + var(--safe-area-inset-top) +
var(--view-container-padding-top, 0px)
);
@@ -1485,8 +1498,6 @@ class HUIRoot extends LitElement {
padding-inline-start: var(--safe-area-inset-left);
}
hui-view-container > * {
display: flex;
flex-direction: column;
flex: 1 1 100%;
max-width: 100%;
}
@@ -1494,7 +1505,12 @@ class HUIRoot extends LitElement {
* In edit mode we have the tab bar on a new line *
*/
hui-view-container.has-tab-bar {
padding-top: calc(
height: calc(
100vh - var(--header-height, 56px) - calc(
var(--tab-bar-height, 56px) - 2px
) - var(--safe-area-inset-top, 0px)
);
margin-top: calc(
var(--header-height, 56px) +
calc(var(--tab-bar-height, 56px) - 2px) +
var(--safe-area-inset-top, 0px)

View File

@@ -418,11 +418,15 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
}
private _toggleView() {
// The scroll container is the hui-view-container parent
const scrollContainer = this.closest("hui-view-container");
const scrollTop = scrollContainer?.scrollTop ?? 0;
// Save current scroll position
if (this._sidebarTabActive) {
this._sidebarScrollTop = window.scrollY;
this._sidebarScrollTop = scrollTop;
} else {
this._contentScrollTop = window.scrollY;
this._contentScrollTop = scrollTop;
}
this._sidebarTabActive = !this._sidebarTabActive;
@@ -438,7 +442,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
const scrollY = this._sidebarTabActive
? this._sidebarScrollTop
: this._contentScrollTop;
window.scrollTo(0, scrollY);
scrollContainer?.scrollTo(0, scrollY);
});
}
@@ -461,7 +465,6 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
--column-min-width: var(--ha-view-sections-column-min-width, 320px);
--top-margin: var(--ha-view-sections-extra-top-margin, 80px);
display: block;
flex: 1;
}
@media (max-width: 600px) {
@@ -471,9 +474,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
}
.wrapper {
display: flex;
flex-direction: column;
min-height: calc(100% - 2 * var(--row-gap));
display: block;
padding: var(--row-gap) var(--column-gap);
box-sizing: content-box;
margin: 0 auto;
@@ -506,7 +507,6 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
gap: var(--row-gap) var(--column-gap);
padding: var(--row-gap) 0;
align-items: flex-start;
flex: 1 0 auto;
}
.wrapper.has-sidebar .container {

View File

@@ -4,6 +4,7 @@ import { customElement, property, state } from "lit/decorators";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { listenMediaQuery } from "../../../common/dom/media_query";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import { haStyleScrollbar } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
type BackgroundConfig = LovelaceViewConfig["background"];
@@ -22,6 +23,7 @@ class HuiViewContainer extends LitElement {
public connectedCallback(): void {
super.connectedCallback();
this.classList.add("ha-scrollbar");
this._setUpMediaQuery();
this._applyTheme();
}
@@ -74,11 +76,16 @@ class HuiViewContainer extends LitElement {
}
}
static styles = css`
:host {
display: relative;
}
`;
static styles = [
haStyleScrollbar,
css`
:host {
display: block;
height: 100%;
-webkit-overflow-scrolling: touch;
}
`,
];
}
declare global {

View File

@@ -244,17 +244,20 @@ export const haStyleDialogFixedTop = css`
`;
export const haStyleScrollbar = css`
.ha-scrollbar::-webkit-scrollbar {
.ha-scrollbar::-webkit-scrollbar,
:host(.ha-scrollbar)::-webkit-scrollbar {
width: 0.4rem;
height: 0.4rem;
}
.ha-scrollbar::-webkit-scrollbar-thumb {
.ha-scrollbar::-webkit-scrollbar-thumb,
:host(.ha-scrollbar)::-webkit-scrollbar-thumb {
border-radius: var(--ha-border-radius-sm);
background: var(--scrollbar-thumb-color);
}
.ha-scrollbar {
.ha-scrollbar,
:host(.ha-scrollbar) {
overflow-y: auto;
scrollbar-color: var(--scrollbar-thumb-color) transparent;
scrollbar-width: thin;

View File

@@ -359,8 +359,6 @@ export const darkColorStyles = css`
--outline-hover-color: rgba(225, 225, 225, 0.24);
--shadow-color: rgba(0, 0, 0, 0.48);
--scrollbar-thumb-color: rgb(110, 110, 110);
--mdc-ripple-color: #aaaaaa;
--mdc-linear-progress-buffer-color: rgba(255, 255, 255, 0.1);

View File

@@ -51,21 +51,6 @@ export const mainStyles = css`
/* dialog backdrop filter */
--ha-dialog-scrim-backdrop-filter: brightness(68%);
/* scrollbar */
scrollbar-color: var(--scrollbar-thumb-color) transparent;
scrollbar-width: thin;
}
html::-webkit-scrollbar {
width: 0.4rem;
height: 0.4rem;
}
html::-webkit-scrollbar-thumb {
border-radius: var(--ha-border-radius-sm);
background: var(--scrollbar-thumb-color);
border: 3px solid transparent;
}
`;

View File

@@ -1524,7 +1524,6 @@
"settings": "Settings",
"edit": "Edit entity",
"details": "Details",
"toggle_yaml_mode": "Toggle YAML mode",
"translated": "Translated",
"raw": "Raw",
"back_to_info": "Back to info",

265
yarn.lock
View File

@@ -1990,75 +1990,74 @@ __metadata:
languageName: node
linkType: hard
"@html-eslint/core@npm:^0.57.0":
version: 0.57.0
resolution: "@html-eslint/core@npm:0.57.0"
"@html-eslint/core@npm:^0.56.0":
version: 0.56.0
resolution: "@html-eslint/core@npm:0.56.0"
dependencies:
"@html-eslint/types": "npm:^0.57.0"
"@html-eslint/types": "npm:^0.56.0"
eslint: "npm:^9.39.1"
html-standard: "npm:^0.0.13"
checksum: 10/18b7aaf455969008dc5b79f6fd4b38792d893053e77c098c56a9238fe9b3ac3fa36441a88b908ade71de2f5179d5b4c45f552d7da5beff1d5c8df9cb3fd14e63
checksum: 10/0722e7405e56b256c471229dbd0ccb49cf0edd3e5224a087240f1954c74af0a6f289e5be0b83957480f1fa5abf6f7cbad7212134a6872a3fd66e1dc1ad879d66
languageName: node
linkType: hard
"@html-eslint/eslint-plugin@npm:0.57.0":
version: 0.57.0
resolution: "@html-eslint/eslint-plugin@npm:0.57.0"
"@html-eslint/eslint-plugin@npm:0.56.0":
version: 0.56.0
resolution: "@html-eslint/eslint-plugin@npm:0.56.0"
dependencies:
"@eslint/plugin-kit": "npm:^0.4.1"
"@html-eslint/core": "npm:^0.57.0"
"@html-eslint/parser": "npm:^0.57.0"
"@html-eslint/template-parser": "npm:^0.57.0"
"@html-eslint/template-syntax-parser": "npm:^0.57.0"
"@html-eslint/types": "npm:^0.57.0"
"@rviscomi/capo.js": "npm:^2.1.0"
"@html-eslint/core": "npm:^0.56.0"
"@html-eslint/parser": "npm:^0.56.0"
"@html-eslint/template-parser": "npm:^0.56.0"
"@html-eslint/template-syntax-parser": "npm:^0.56.0"
"@html-eslint/types": "npm:^0.56.0"
peerDependencies:
eslint: ">=8.0.0 || ^10.0.0-0"
checksum: 10/34cf11eaab3c07436c9c8b2896ed331c3ba5d591db5d8a8ff79d19b174e6e090323eb71e1368ad93d2afa945d4740749c599436a03be1195d4e4a4b0d3d9e9b6
checksum: 10/16c5d638045266ca201b5dc154b3706262801668b2edde4f0905bdba9ba2e14b7175eacf9ca7d2ad5a5a03e3369843822d34ee607b6b7fbbeb020ac7f87629fb
languageName: node
linkType: hard
"@html-eslint/parser@npm:^0.57.0":
version: 0.57.0
resolution: "@html-eslint/parser@npm:0.57.0"
"@html-eslint/parser@npm:^0.56.0":
version: 0.56.0
resolution: "@html-eslint/parser@npm:0.56.0"
dependencies:
"@eslint/css-tree": "npm:^3.6.9"
"@html-eslint/template-syntax-parser": "npm:^0.57.0"
"@html-eslint/types": "npm:^0.57.0"
"@html-eslint/template-syntax-parser": "npm:^0.56.0"
"@html-eslint/types": "npm:^0.56.0"
css-tree: "npm:^3.1.0"
es-html-parser: "npm:0.3.1"
checksum: 10/d742a26ef8122eab82e35ab27de6fe40cd45d2ac59ade9bed9d16df0da3cd18425c7892fedd245b9d60da2a4fbfb8c6afe05b1b6415033ab8fad996b94be020e
checksum: 10/1fead2be97481f461ee7030f4ea9274e5f969540118b4429e3eea361f62138660c1b7538668c7f8d55a8ae6985deff3056968d71bae62b91fb001d4209a24b50
languageName: node
linkType: hard
"@html-eslint/template-parser@npm:^0.57.0":
version: 0.57.0
resolution: "@html-eslint/template-parser@npm:0.57.0"
"@html-eslint/template-parser@npm:^0.56.0":
version: 0.56.0
resolution: "@html-eslint/template-parser@npm:0.56.0"
dependencies:
"@html-eslint/types": "npm:^0.57.0"
"@html-eslint/types": "npm:^0.56.0"
es-html-parser: "npm:0.3.1"
checksum: 10/160d7f6156f242ed7bd4e7f13d79c350bf5db7fef089ba7b404ef770d80547620e9ee860ead8892a4079668db1cdccf6a6c6b91183a8c478a634314b39efea32
checksum: 10/45e41dc8fb46336f40d1b78ef55ee3150d7a1034d393529eb40ce37a7d0a8fa04f6ed8f9a63478ff884d9588b1359b97a799681a37cd485d237dce867b7f9d20
languageName: node
linkType: hard
"@html-eslint/template-syntax-parser@npm:^0.57.0":
version: 0.57.0
resolution: "@html-eslint/template-syntax-parser@npm:0.57.0"
"@html-eslint/template-syntax-parser@npm:^0.56.0":
version: 0.56.0
resolution: "@html-eslint/template-syntax-parser@npm:0.56.0"
dependencies:
"@html-eslint/types": "npm:^0.57.0"
checksum: 10/147d8d15ebc5ea9b75eea04463b9f29cfc476be2e98c5a2129442be6f66d32a9e8bb11a1be13cc3778df25e14d7ed1cd345342951ecec05292545f64bb3d92c6
"@html-eslint/types": "npm:^0.56.0"
checksum: 10/2cc4f2cbfa58c6381b2542ca86e424a97429326aa0b8b6b09e0a51d27b0e26fd723a4550e4c17536e00cfd506cbd0c61d8fd3dff1c829714c4432406096b4a01
languageName: node
linkType: hard
"@html-eslint/types@npm:^0.57.0":
version: 0.57.0
resolution: "@html-eslint/types@npm:0.57.0"
"@html-eslint/types@npm:^0.56.0":
version: 0.56.0
resolution: "@html-eslint/types@npm:0.56.0"
dependencies:
"@types/css-tree": "npm:^2.3.11"
"@types/estree": "npm:^1.0.6"
es-html-parser: "npm:0.3.1"
eslint: "npm:^9.39.1"
checksum: 10/44d0bde592786a2da966beab13c3d543e6669b1e38b6e5feea0f01121eec928be0081969df3ada1f27c9bd43d6286596afa56a2e075da843251079a940cf0825
checksum: 10/5359ccfb8f431ccedfe7d9c0b20d883d19bbb6a24eee925c036c7774d6f2177eda021826abbc6f947b59c2364d7cf25a596591e852c1141000a3fab6ea70acc9
languageName: node
linkType: hard
@@ -4258,13 +4257,6 @@ __metadata:
languageName: node
linkType: hard
"@rviscomi/capo.js@npm:^2.1.0":
version: 2.1.0
resolution: "@rviscomi/capo.js@npm:2.1.0"
checksum: 10/83ce30106ec6bd4b4791af12556119c568cb9d0ec7550b7b015a8c2c1c49687d8c6605564415d9d85e991581958212109ee5caa0db4166c1d3628033175e80b8
languageName: node
linkType: hard
"@shoelace-style/animations@npm:^1.2.0":
version: 1.2.0
resolution: "@shoelace-style/animations@npm:1.2.0"
@@ -5071,138 +5063,138 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/eslint-plugin@npm:8.56.1"
"@typescript-eslint/eslint-plugin@npm:8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.56.0"
dependencies:
"@eslint-community/regexpp": "npm:^4.12.2"
"@typescript-eslint/scope-manager": "npm:8.56.1"
"@typescript-eslint/type-utils": "npm:8.56.1"
"@typescript-eslint/utils": "npm:8.56.1"
"@typescript-eslint/visitor-keys": "npm:8.56.1"
"@typescript-eslint/scope-manager": "npm:8.56.0"
"@typescript-eslint/type-utils": "npm:8.56.0"
"@typescript-eslint/utils": "npm:8.56.0"
"@typescript-eslint/visitor-keys": "npm:8.56.0"
ignore: "npm:^7.0.5"
natural-compare: "npm:^1.4.0"
ts-api-utils: "npm:^2.4.0"
peerDependencies:
"@typescript-eslint/parser": ^8.56.1
"@typescript-eslint/parser": ^8.56.0
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/669d19cff91fcad5fe34dba97cc8c0c2df3160ae14646759fb23dfd6ffbb861d00d8d081e74d1060d544bfba0ea4d05588c5b73ae104907af26cc18189c0d139
checksum: 10/44201eae518c759cf3110f7e0a374372ef22bffa3cca61685aebe916b06bcb5f3ed6ffedba252199dca0006dfc22c54b132cd0337fd15e8c083eda22f9cf6b0c
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/parser@npm:8.56.1"
"@typescript-eslint/parser@npm:8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/parser@npm:8.56.0"
dependencies:
"@typescript-eslint/scope-manager": "npm:8.56.1"
"@typescript-eslint/types": "npm:8.56.1"
"@typescript-eslint/typescript-estree": "npm:8.56.1"
"@typescript-eslint/visitor-keys": "npm:8.56.1"
"@typescript-eslint/scope-manager": "npm:8.56.0"
"@typescript-eslint/types": "npm:8.56.0"
"@typescript-eslint/typescript-estree": "npm:8.56.0"
"@typescript-eslint/visitor-keys": "npm:8.56.0"
debug: "npm:^4.4.3"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/280b041a69153caf9e721b307781830483dd39d881b02d993156635bd8600e30e6a816aaead8bdd662ae5149b8870aef7b3823d3b98ec974d924c23a786fb6d9
checksum: 10/9bdb2c7915665a1031499049974997020bbc34557803a8c3718b323d5583a3fdfc27797ec714b617743225c8dce9ab589eb442b71c435b464ad45365a6fbba57
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/project-service@npm:8.56.1"
"@typescript-eslint/project-service@npm:8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/project-service@npm:8.56.0"
dependencies:
"@typescript-eslint/tsconfig-utils": "npm:^8.56.1"
"@typescript-eslint/types": "npm:^8.56.1"
"@typescript-eslint/tsconfig-utils": "npm:^8.56.0"
"@typescript-eslint/types": "npm:^8.56.0"
debug: "npm:^4.4.3"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/5e7fdc95aebcefc72fec77806bb0821a9a59e5e88f86d72b15ad011eb6110da05419b803875f021716a219fc7fb8517331a6736364344c8613a90209539a6d32
checksum: 10/b46cc78bfb50ee84cb12e2e99c1a3d9606161980cf56ba33be6244ccff2ba4c78ceec46706ad597508fda167e0f965d849c1c516400ad41259027be3fbd815eb
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/scope-manager@npm:8.56.1"
"@typescript-eslint/scope-manager@npm:8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/scope-manager@npm:8.56.0"
dependencies:
"@typescript-eslint/types": "npm:8.56.1"
"@typescript-eslint/visitor-keys": "npm:8.56.1"
checksum: 10/f358cf8bd32952eed005d4f34c1e95805baefe35abee96d866222b0eff8027cc02f831cee04b308707d74db2b415437a134191207b4213ee8acbc6d67a435616
"@typescript-eslint/types": "npm:8.56.0"
"@typescript-eslint/visitor-keys": "npm:8.56.0"
checksum: 10/3662355120ea8e21ce01c999decbd2a09fe4edb1c01e376fe347952d968ecfedff99b9484334e133e41284a15f2e1bc8efd490b1e73a16980614445c25b07b0d
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.56.1, @typescript-eslint/tsconfig-utils@npm:^8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/tsconfig-utils@npm:8.56.1"
"@typescript-eslint/tsconfig-utils@npm:8.56.0, @typescript-eslint/tsconfig-utils@npm:^8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.56.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/d509f1ae4b14969173e498db6d15c833b6407db456c7fca9e25396798a35014229a73754691f353c4a99f5f0bbb4535b4144f42f84e596645a16d88a2022135f
checksum: 10/b1834aeffcdc07835eae0bf52aca573cba7e6528b5c1483e9b1f7f4f9e1f6450a8650796be11140e0437caf7eb1b0f9711c22989c8294547534f12614a759760
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/type-utils@npm:8.56.1"
"@typescript-eslint/type-utils@npm:8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/type-utils@npm:8.56.0"
dependencies:
"@typescript-eslint/types": "npm:8.56.1"
"@typescript-eslint/typescript-estree": "npm:8.56.1"
"@typescript-eslint/utils": "npm:8.56.1"
"@typescript-eslint/types": "npm:8.56.0"
"@typescript-eslint/typescript-estree": "npm:8.56.0"
"@typescript-eslint/utils": "npm:8.56.0"
debug: "npm:^4.4.3"
ts-api-utils: "npm:^2.4.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/2b07c674c26d797d05c05779ac5c89761b6b96680ecaf01440957727d12c6d06a2e48f0b139e45752eb4b53bf13c03940628656c519d362082b716d6a0ece6d9
checksum: 10/f272b9acc004f125cbf0df18265a43ba50cd3666262afc663585acdd1be6b6b30724bd8cf4cd5aa2757b7f10ceafa92fd1af30c1931fb22ac38521eda7f79c89
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.56.1, @typescript-eslint/types@npm:^8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/types@npm:8.56.1"
checksum: 10/4bcffab5b0fd48adb731fcade86a776ca4a66e229defa5a282b58ba9c95af16ffc459a7d188e27c988a35be1f6fb5b812f9cf0952692eac38d5b3e87daafb20a
"@typescript-eslint/types@npm:8.56.0, @typescript-eslint/types@npm:^8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/types@npm:8.56.0"
checksum: 10/d7549535c99d9202742bf0191bcc2822c2d18a03e206be9ad5a6f6b0902de7381c93e8c238754fe5d1dfdcc22d7e3bbafa032f63ba165d6dc03e180f84b138f9
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/typescript-estree@npm:8.56.1"
"@typescript-eslint/typescript-estree@npm:8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/typescript-estree@npm:8.56.0"
dependencies:
"@typescript-eslint/project-service": "npm:8.56.1"
"@typescript-eslint/tsconfig-utils": "npm:8.56.1"
"@typescript-eslint/types": "npm:8.56.1"
"@typescript-eslint/visitor-keys": "npm:8.56.1"
"@typescript-eslint/project-service": "npm:8.56.0"
"@typescript-eslint/tsconfig-utils": "npm:8.56.0"
"@typescript-eslint/types": "npm:8.56.0"
"@typescript-eslint/visitor-keys": "npm:8.56.0"
debug: "npm:^4.4.3"
minimatch: "npm:^10.2.2"
minimatch: "npm:^9.0.5"
semver: "npm:^7.7.3"
tinyglobby: "npm:^0.2.15"
ts-api-utils: "npm:^2.4.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/af39dae0a8fada72295a11f0efb49f311241134b0a3d819100eeda6a2f92368844645873ba785de5513ad541cd9c2ba17b9bfed2679daac4682fa2a3b627f087
checksum: 10/55c8cfc7e265f320d780e69a677838821225fad8b853108ce2095f01509bf2ee8943280df9ac75560ed86265ef0e0a979ae4cb375d712f648b336032de79d19b
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/utils@npm:8.56.1"
"@typescript-eslint/utils@npm:8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/utils@npm:8.56.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.9.1"
"@typescript-eslint/scope-manager": "npm:8.56.1"
"@typescript-eslint/types": "npm:8.56.1"
"@typescript-eslint/typescript-estree": "npm:8.56.1"
"@typescript-eslint/scope-manager": "npm:8.56.0"
"@typescript-eslint/types": "npm:8.56.0"
"@typescript-eslint/typescript-estree": "npm:8.56.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/528cbd187d8288a8cfce24a043f993b93711087f53d2b6f95cdd615a1a4087af1dab083a747761af97474a621c7b14f11c84ee50c250f31566ebc64cf73867fe
checksum: 10/f357bd15fe568cba0b89371e9a724eda38d78361a21dc0c4f49b0af4a23a140c77e2a8c6285c6fe8d8277e256a8a137aef7bcf6d97428eecd0c6e72ef08849ae
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/visitor-keys@npm:8.56.1"
"@typescript-eslint/visitor-keys@npm:8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/visitor-keys@npm:8.56.0"
dependencies:
"@typescript-eslint/types": "npm:8.56.1"
"@typescript-eslint/types": "npm:8.56.0"
eslint-visitor-keys: "npm:^5.0.0"
checksum: 10/efed6a9867e7be203eec543e5a65da5aaec96aae55fba6fe74a305bf600e57c707764835e82bb8eb541f49a9b70442ff1e1a0ecf3543c78c91b84dda43b95c53
checksum: 10/1eaa26ffe8a2c83d42d428beef207d793aef73c2e306f94e716d39519eaaa07547da898c7d63a5c406a3662895d735b4b1f33b513a1addb69473c65e7d92a2b5
languageName: node
linkType: hard
@@ -6086,12 +6078,12 @@ __metadata:
languageName: node
linkType: hard
"barcode-detector@npm:3.1.0":
version: 3.1.0
resolution: "barcode-detector@npm:3.1.0"
"barcode-detector@npm:3.0.8":
version: 3.0.8
resolution: "barcode-detector@npm:3.0.8"
dependencies:
zxing-wasm: "npm:3.0.0"
checksum: 10/60767161081b827e290b60bb3416999dee616bab39291ee55565df9b72d59f0bbbf511fd3bb85db18eee7c0ad9acf1ff90359cdb21e10f80793acd0105c86a1d
zxing-wasm: "npm:2.2.4"
checksum: 10/7de6225f659c69a0f4101d080a9e0812f2404c485fa2406424b8b13eaff274f6e7405c94de24827a09def52c32a63b19b9c9fba61c5274b074d558f696ec1684
languageName: node
linkType: hard
@@ -9200,7 +9192,7 @@ __metadata:
"@fullcalendar/luxon3": "npm:6.1.20"
"@fullcalendar/timegrid": "npm:6.1.20"
"@home-assistant/webawesome": "npm:3.2.1-ha.3"
"@html-eslint/eslint-plugin": "npm:0.57.0"
"@html-eslint/eslint-plugin": "npm:0.56.0"
"@lezer/highlight": "npm:1.2.3"
"@lit-labs/motion": "npm:1.1.0"
"@lit-labs/observers": "npm:2.1.0"
@@ -9268,7 +9260,7 @@ __metadata:
app-datepicker: "npm:5.1.1"
babel-loader: "npm:10.0.0"
babel-plugin-template-html-minifier: "npm:4.1.0"
barcode-detector: "npm:3.1.0"
barcode-detector: "npm:3.0.8"
browserslist-useragent-regexp: "npm:4.1.3"
color-name: "npm:2.1.0"
comlink: "npm:4.4.2"
@@ -9343,7 +9335,7 @@ __metadata:
tinykeys: "npm:3.0.0"
ts-lit-plugin: "npm:2.0.2"
typescript: "npm:5.9.3"
typescript-eslint: "npm:8.56.1"
typescript-eslint: "npm:8.56.0"
ua-parser-js: "npm:2.0.9"
vite-tsconfig-paths: "npm:6.1.1"
vitest: "npm:4.0.18"
@@ -11125,6 +11117,15 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:^9.0.5":
version: 9.0.5
resolution: "minimatch@npm:9.0.5"
dependencies:
brace-expansion: "npm:^2.0.1"
checksum: 10/dd6a8927b063aca6d910b119e1f2df6d2ce7d36eab91de83167dd136bb85e1ebff97b0d3de1cb08bd1f7e018ca170b4962479fefab5b2a69e2ae12cb2edc8348
languageName: node
linkType: hard
"minimist@npm:^1.2.0, minimist@npm:^1.2.6":
version: 1.2.8
resolution: "minimist@npm:1.2.8"
@@ -14139,12 +14140,12 @@ __metadata:
languageName: node
linkType: hard
"type-fest@npm:^5.4.4":
version: 5.4.4
resolution: "type-fest@npm:5.4.4"
"type-fest@npm:^5.2.0":
version: 5.4.1
resolution: "type-fest@npm:5.4.1"
dependencies:
tagged-tag: "npm:^1.0.0"
checksum: 10/0bbdca645f95740587f389a2d712fe8d5e9ab7d13e74aac97cf396112510abcaab6b75fd90d65172bc13b02fdfc827e6a871322cc9c1c1a5a2754d9ab264c6f5
checksum: 10/be7d4749e1e5cf2e2c9904fa1aaf9da5eef6c47c130881bf93bfd5a670b2ab59c5502466768e42c521281056a2375b1617176a75cf6c52b575f4bbabbd450b21
languageName: node
linkType: hard
@@ -14211,18 +14212,18 @@ __metadata:
languageName: node
linkType: hard
"typescript-eslint@npm:8.56.1":
version: 8.56.1
resolution: "typescript-eslint@npm:8.56.1"
"typescript-eslint@npm:8.56.0":
version: 8.56.0
resolution: "typescript-eslint@npm:8.56.0"
dependencies:
"@typescript-eslint/eslint-plugin": "npm:8.56.1"
"@typescript-eslint/parser": "npm:8.56.1"
"@typescript-eslint/typescript-estree": "npm:8.56.1"
"@typescript-eslint/utils": "npm:8.56.1"
"@typescript-eslint/eslint-plugin": "npm:8.56.0"
"@typescript-eslint/parser": "npm:8.56.0"
"@typescript-eslint/typescript-estree": "npm:8.56.0"
"@typescript-eslint/utils": "npm:8.56.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/e18cd347ddce0f0e5b28121346f27736a418adf68e73d613690ea3a1d0adfe03bc393f77a8872c2cef77ca74bcc0974212d1775c360de33a9987a94cda11a05b
checksum: 10/7c07af35e6b4eaaebad4b50c37bc6dd50f0cecebe5a4e648ef117fd43a8496f3132020061e19a2fbaf826978e91c100054e638701bf89db8f342dd1353bb5b7e
languageName: node
linkType: hard
@@ -15703,14 +15704,14 @@ __metadata:
languageName: node
linkType: hard
"zxing-wasm@npm:3.0.0":
version: 3.0.0
resolution: "zxing-wasm@npm:3.0.0"
"zxing-wasm@npm:2.2.4":
version: 2.2.4
resolution: "zxing-wasm@npm:2.2.4"
dependencies:
"@types/emscripten": "npm:^1.41.5"
type-fest: "npm:^5.4.4"
type-fest: "npm:^5.2.0"
peerDependencies:
"@types/emscripten": ">=1.39.6"
checksum: 10/0acf04829acf8f3987173af011784642792fc877c7765f79222fe33efff8af09fbf95bf5d590d2490ae39ec411e6c4de06ea24e96d4eb48189b9d06f7502eaa2
checksum: 10/e5928cbb066c854c970cbf724e978e502c3469d69de2469bd37d1f6ab8f5d2a2acdbaa9dea32d35cfc058e2b482e29a9c4f12161d9df3e1e952c30dda3a96d8c
languageName: node
linkType: hard