mirror of
https://github.com/home-assistant/frontend.git
synced 2025-10-25 11:39:41 +00:00
Compare commits
22 Commits
loading-an
...
card-featu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
336a8e6241 | ||
|
|
01ad51617a | ||
|
|
be741de31d | ||
|
|
772459f5a7 | ||
|
|
c139ec22f9 | ||
|
|
a6ef3a26da | ||
|
|
221ca56121 | ||
|
|
4e6e3629a8 | ||
|
|
fe94ae0243 | ||
|
|
8a1a22d4bd | ||
|
|
153a578986 | ||
|
|
04bb10d0a2 | ||
|
|
35e52de2c1 | ||
|
|
b0862fddaa | ||
|
|
77735f5310 | ||
|
|
e388756533 | ||
|
|
e9ca9bb781 | ||
|
|
e48918442c | ||
|
|
52f37f41f0 | ||
|
|
4687006fec | ||
|
|
aca4ca3066 | ||
|
|
3a2c00622a |
@@ -203,7 +203,7 @@
|
||||
"husky": "9.1.7",
|
||||
"jsdom": "27.0.1",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "16.2.4",
|
||||
"lint-staged": "16.2.5",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.5.0",
|
||||
@@ -217,7 +217,7 @@
|
||||
"terser-webpack-plugin": "5.3.14",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.46.1",
|
||||
"typescript-eslint": "8.46.2",
|
||||
"vite-tsconfig-paths": "5.1.4",
|
||||
"vitest": "3.2.4",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
|
||||
@@ -88,9 +88,19 @@ export class HaChartBase extends LitElement {
|
||||
|
||||
private _lastTapTime?: number;
|
||||
|
||||
private _shouldResizeChart = false;
|
||||
|
||||
// @ts-ignore
|
||||
private _resizeController = new ResizeController(this, {
|
||||
callback: () => this.chart?.resize(),
|
||||
callback: () => {
|
||||
if (this.chart) {
|
||||
if (!this.chart.getZr().animation.isFinished()) {
|
||||
this._shouldResizeChart = true;
|
||||
} else {
|
||||
this.chart.resize();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
private _loading = false;
|
||||
@@ -366,6 +376,7 @@ export class HaChartBase extends LitElement {
|
||||
if (!this.options?.dataZoom) {
|
||||
this.chart.getZr().on("dblclick", this._handleClickZoom);
|
||||
}
|
||||
this.chart.on("finished", this._handleChartRenderFinished);
|
||||
if (this._isTouchDevice) {
|
||||
this.chart.getZr().on("click", (e: ECElementEvent) => {
|
||||
if (!e.zrByTouch) {
|
||||
@@ -945,6 +956,13 @@ export class HaChartBase extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _handleChartRenderFinished = () => {
|
||||
if (this._shouldResizeChart) {
|
||||
this.chart?.resize();
|
||||
this._shouldResizeChart = false;
|
||||
}
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
|
||||
@@ -1,23 +1,39 @@
|
||||
import { mdiDragHorizontalVariant } from "@mdi/js";
|
||||
import "@material/mwc-menu/mwc-menu-surface";
|
||||
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { IFuseOptions } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import {
|
||||
STATE_DISPLAY_SPECIAL_CONTENT,
|
||||
STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS,
|
||||
} from "../../state-display/state-display";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../ha-combo-box";
|
||||
import "../ha-sortable";
|
||||
import "../chips/ha-input-chip";
|
||||
import "../chips/ha-assist-chip";
|
||||
import "../chips/ha-chip-set";
|
||||
import "../chips/ha-input-chip";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-sortable";
|
||||
|
||||
interface StateContentOption {
|
||||
primary: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<StateContentOption> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
<span slot="headline">${item.primary}</span>
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
|
||||
const HIDDEN_ATTRIBUTES = [
|
||||
"access_token",
|
||||
@@ -74,7 +90,7 @@ const HIDDEN_ATTRIBUTES = [
|
||||
];
|
||||
|
||||
@customElement("ha-entity-state-content-picker")
|
||||
class HaEntityStatePicker extends LitElement {
|
||||
export class HaStateContentPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public entityId?: string;
|
||||
@@ -95,26 +111,28 @@ class HaEntityStatePicker extends LitElement {
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@state() private _opened = false;
|
||||
@query(".container", true) private _container?: HTMLDivElement;
|
||||
|
||||
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues) {
|
||||
return !(!changedProps.has("_opened") && this._opened);
|
||||
}
|
||||
@state() private _opened = false;
|
||||
|
||||
private options = memoizeOne(
|
||||
private _editIndex?: number;
|
||||
|
||||
private _options = memoizeOne(
|
||||
(entityId?: string, stateObj?: HassEntity, allowName?: boolean) => {
|
||||
const domain = entityId ? computeDomain(entityId) : undefined;
|
||||
return [
|
||||
{
|
||||
label: this.hass.localize("ui.components.state-content-picker.state"),
|
||||
primary: this.hass.localize(
|
||||
"ui.components.state-content-picker.state"
|
||||
),
|
||||
value: "state",
|
||||
},
|
||||
...(allowName
|
||||
? [
|
||||
{
|
||||
label: this.hass.localize(
|
||||
primary: this.hass.localize(
|
||||
"ui.components.state-content-picker.name"
|
||||
),
|
||||
value: "name",
|
||||
@@ -122,13 +140,13 @@ class HaEntityStatePicker extends LitElement {
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: this.hass.localize(
|
||||
primary: this.hass.localize(
|
||||
"ui.components.state-content-picker.last_changed"
|
||||
),
|
||||
value: "last_changed",
|
||||
},
|
||||
{
|
||||
label: this.hass.localize(
|
||||
primary: this.hass.localize(
|
||||
"ui.components.state-content-picker.last_updated"
|
||||
),
|
||||
value: "last_updated",
|
||||
@@ -137,7 +155,7 @@ class HaEntityStatePicker extends LitElement {
|
||||
? STATE_DISPLAY_SPECIAL_CONTENT.filter((content) =>
|
||||
STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS[domain]?.includes(content)
|
||||
).map((content) => ({
|
||||
label: this.hass.localize(
|
||||
primary: this.hass.localize(
|
||||
`ui.components.state-content-picker.${content}`
|
||||
),
|
||||
value: content,
|
||||
@@ -146,108 +164,201 @@ class HaEntityStatePicker extends LitElement {
|
||||
...Object.keys(stateObj?.attributes ?? {})
|
||||
.filter((a) => !HIDDEN_ATTRIBUTES.includes(a))
|
||||
.map((attribute) => ({
|
||||
primary: this.hass.formatEntityAttributeName(stateObj!, attribute),
|
||||
value: attribute,
|
||||
label: this.hass.formatEntityAttributeName(stateObj!, attribute),
|
||||
})),
|
||||
];
|
||||
] satisfies StateContentOption[];
|
||||
}
|
||||
);
|
||||
|
||||
private _filter = "";
|
||||
|
||||
protected render() {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const value = this._value;
|
||||
|
||||
const stateObj = this.entityId
|
||||
? this.hass.states[this.entityId]
|
||||
: undefined;
|
||||
|
||||
const options = this.options(this.entityId, stateObj, this.allowName);
|
||||
const optionItems = options.filter(
|
||||
(option) => !this._value.includes(option.value)
|
||||
);
|
||||
const options = this._options(this.entityId, stateObj, this.allowName);
|
||||
|
||||
return html`
|
||||
${value?.length
|
||||
? html`
|
||||
<ha-sortable
|
||||
no-style
|
||||
@item-moved=${this._moveItem}
|
||||
.disabled=${this.disabled}
|
||||
handle-selector="button.primary.action"
|
||||
>
|
||||
<ha-chip-set>
|
||||
${repeat(
|
||||
this._value,
|
||||
(item) => item,
|
||||
(item, idx) => {
|
||||
const label =
|
||||
options.find((option) => option.value === item)?.label ||
|
||||
item;
|
||||
return html`
|
||||
<ha-input-chip
|
||||
.idx=${idx}
|
||||
@remove=${this._removeItem}
|
||||
.label=${label}
|
||||
selected
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
${label}
|
||||
</ha-input-chip>
|
||||
`;
|
||||
}
|
||||
)}
|
||||
</ha-chip-set>
|
||||
</ha-sortable>
|
||||
`
|
||||
: nothing}
|
||||
${this.label ? html`<label>${this.label}</label>` : nothing}
|
||||
<div class="container ${this.disabled ? "disabled" : ""}">
|
||||
<ha-sortable
|
||||
no-style
|
||||
@item-moved=${this._moveItem}
|
||||
.disabled=${this.disabled}
|
||||
handle-selector="button.primary.action"
|
||||
filter=".add"
|
||||
>
|
||||
<ha-chip-set>
|
||||
${repeat(
|
||||
this._value,
|
||||
(item) => item,
|
||||
(item: string, idx) => {
|
||||
const label = options.find((o) => o.value === item)?.primary;
|
||||
const isValid = !!label;
|
||||
return html`
|
||||
<ha-input-chip
|
||||
data-idx=${idx}
|
||||
@remove=${this._removeItem}
|
||||
@click=${this._editItem}
|
||||
.label=${label || item}
|
||||
.selected=${!this.disabled}
|
||||
.disabled=${this.disabled}
|
||||
class=${!isValid ? "invalid" : ""}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
</ha-input-chip>
|
||||
`;
|
||||
}
|
||||
)}
|
||||
${this.disabled
|
||||
? nothing
|
||||
: html`
|
||||
<ha-assist-chip
|
||||
@click=${this._addItem}
|
||||
.disabled=${this.disabled}
|
||||
label=${this.hass.localize(
|
||||
"ui.components.entity.entity-state-content-picker.add"
|
||||
)}
|
||||
class="add"
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-assist-chip>
|
||||
`}
|
||||
</ha-chip-set>
|
||||
</ha-sortable>
|
||||
|
||||
<ha-combo-box
|
||||
item-value-path="value"
|
||||
item-label-path="label"
|
||||
.hass=${this.hass}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required && !value.length}
|
||||
.value=${""}
|
||||
.items=${optionItems}
|
||||
allow-custom-value
|
||||
@filter-changed=${this._filterChanged}
|
||||
@value-changed=${this._comboBoxValueChanged}
|
||||
@opened-changed=${this._openedChanged}
|
||||
></ha-combo-box>
|
||||
<mwc-menu-surface
|
||||
.open=${this._opened}
|
||||
@closed=${this._onClosed}
|
||||
@opened=${this._onOpened}
|
||||
@input=${stopPropagation}
|
||||
.anchor=${this._container}
|
||||
>
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.value=${""}
|
||||
.autofocus=${this.autofocus}
|
||||
.disabled=${this.disabled || !this.entityId}
|
||||
.required=${this.required && !value.length}
|
||||
.helper=${this.helper}
|
||||
.items=${options}
|
||||
allow-custom-value
|
||||
item-id-path="value"
|
||||
item-value-path="value"
|
||||
item-label-path="primary"
|
||||
.renderer=${rowRenderer}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._comboBoxValueChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
>
|
||||
</ha-combo-box>
|
||||
</mwc-menu-surface>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _onClosed(ev) {
|
||||
ev.stopPropagation();
|
||||
this._opened = false;
|
||||
this._editIndex = undefined;
|
||||
}
|
||||
|
||||
private async _onOpened(ev) {
|
||||
if (!this._opened) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
this._opened = true;
|
||||
await this._comboBox?.focus();
|
||||
await this._comboBox?.open();
|
||||
}
|
||||
|
||||
private async _addItem(ev) {
|
||||
ev.stopPropagation();
|
||||
this._opened = true;
|
||||
}
|
||||
|
||||
private async _editItem(ev) {
|
||||
ev.stopPropagation();
|
||||
const idx = parseInt(ev.currentTarget.dataset.idx, 10);
|
||||
this._editIndex = idx;
|
||||
this._opened = true;
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return !this.value ? [] : ensureArray(this.value);
|
||||
}
|
||||
|
||||
private _toValue = memoizeOne((value: string[]): typeof this.value => {
|
||||
if (value.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (value.length === 1) {
|
||||
return value[0];
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
private _openedChanged(ev: ValueChangedEvent<boolean>) {
|
||||
this._opened = ev.detail.value;
|
||||
this._comboBox.filteredItems = this._comboBox.items;
|
||||
const open = ev.detail.value;
|
||||
if (open) {
|
||||
const options = this._comboBox.items || [];
|
||||
|
||||
const initialValue =
|
||||
this._editIndex != null ? this._value[this._editIndex] : "";
|
||||
const filteredItems = this._filterSelectedOptions(options, initialValue);
|
||||
|
||||
this._comboBox.filteredItems = filteredItems;
|
||||
this._comboBox.setInputValue(initialValue);
|
||||
} else {
|
||||
this._opened = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _filterChanged(ev?: CustomEvent): void {
|
||||
this._filter = ev?.detail.value || "";
|
||||
private _filterSelectedOptions = (
|
||||
options: StateContentOption[],
|
||||
current?: string
|
||||
) => {
|
||||
const value = this._value;
|
||||
|
||||
const filteredItems = this._comboBox.items?.filter((item) => {
|
||||
const label = item.label || item.value;
|
||||
return label.toLowerCase().includes(this._filter?.toLowerCase());
|
||||
});
|
||||
return options.filter(
|
||||
(option) => !value.includes(option.value) || option.value === current
|
||||
);
|
||||
};
|
||||
|
||||
if (this._filter) {
|
||||
filteredItems?.unshift({ label: this._filter, value: this._filter });
|
||||
private _filterChanged(ev: ValueChangedEvent<string>) {
|
||||
const input = ev.detail.value;
|
||||
const filter = input?.toLowerCase() || "";
|
||||
const options = this._comboBox.items || [];
|
||||
|
||||
const currentValue =
|
||||
this._editIndex != null ? this._value[this._editIndex] : "";
|
||||
|
||||
this._comboBox.filteredItems = this._filterSelectedOptions(
|
||||
options,
|
||||
currentValue
|
||||
);
|
||||
|
||||
if (!filter) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fuseOptions: IFuseOptions<StateContentOption> = {
|
||||
keys: ["primary", "secondary", "value"],
|
||||
isCaseSensitive: false,
|
||||
minMatchCharLength: Math.min(filter.length, 2),
|
||||
threshold: 0.2,
|
||||
ignoreDiacritics: true,
|
||||
};
|
||||
|
||||
const fuse = new Fuse(this._comboBox.filteredItems, fuseOptions);
|
||||
const filteredItems = fuse.search(filter).map((result) => result.item);
|
||||
|
||||
this._comboBox.filteredItems = filteredItems;
|
||||
}
|
||||
|
||||
@@ -260,43 +371,40 @@ class HaEntityStatePicker extends LitElement {
|
||||
newValue.splice(newIndex, 0, element);
|
||||
this._setValue(newValue);
|
||||
await this.updateComplete;
|
||||
this._filterChanged();
|
||||
this._filterChanged({ detail: { value: "" } } as ValueChangedEvent<string>);
|
||||
}
|
||||
|
||||
private async _removeItem(ev) {
|
||||
ev.stopPropagation();
|
||||
const value: string[] = [...this._value];
|
||||
value.splice(ev.target.idx, 1);
|
||||
const value = [...this._value];
|
||||
const idx = parseInt(ev.target.dataset.idx, 10);
|
||||
value.splice(idx, 1);
|
||||
this._setValue(value);
|
||||
await this.updateComplete;
|
||||
this._filterChanged();
|
||||
this._filterChanged({ detail: { value: "" } } as ValueChangedEvent<string>);
|
||||
}
|
||||
|
||||
private _comboBoxValueChanged(ev: CustomEvent): void {
|
||||
private _comboBoxValueChanged(ev: ValueChangedEvent<string>): void {
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (this.disabled || newValue === "") {
|
||||
if (this.disabled || value === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentValue = this._value;
|
||||
const newValue = [...this._value];
|
||||
|
||||
if (currentValue.includes(newValue)) {
|
||||
return;
|
||||
if (this._editIndex != null) {
|
||||
newValue[this._editIndex] = value;
|
||||
} else {
|
||||
newValue.push(value);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this._filterChanged();
|
||||
this._comboBox.setInputValue("");
|
||||
}, 0);
|
||||
|
||||
this._setValue([...currentValue, newValue]);
|
||||
this._setValue(newValue);
|
||||
}
|
||||
|
||||
private _setValue(value: string[]) {
|
||||
const newValue =
|
||||
value.length === 0 ? undefined : value.length === 1 ? value[0] : value;
|
||||
const newValue = this._toValue(value);
|
||||
this.value = newValue;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
@@ -306,10 +414,64 @@ class HaEntityStatePicker extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
background-color: var(--mdc-text-field-fill-color, whitesmoke);
|
||||
border-radius: var(--ha-border-radius-sm);
|
||||
border-end-end-radius: var(--ha-border-radius-square);
|
||||
border-end-start-radius: var(--ha-border-radius-square);
|
||||
}
|
||||
.container:after {
|
||||
display: block;
|
||||
content: "";
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: var(
|
||||
--mdc-text-field-idle-line-color,
|
||||
rgba(0, 0, 0, 0.42)
|
||||
);
|
||||
transform:
|
||||
height 180ms ease-in-out,
|
||||
background-color 180ms ease-in-out;
|
||||
}
|
||||
.container.disabled:after {
|
||||
background-color: var(
|
||||
--mdc-text-field-disabled-line-color,
|
||||
rgba(0, 0, 0, 0.42)
|
||||
);
|
||||
}
|
||||
.container:focus-within:after {
|
||||
height: 2px;
|
||||
background-color: var(--mdc-theme-primary);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin: 0 0 var(--ha-space-2);
|
||||
}
|
||||
|
||||
.add {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
mwc-menu-surface {
|
||||
--mdc-menu-min-width: 100%;
|
||||
}
|
||||
|
||||
ha-chip-set {
|
||||
padding: 8px 0;
|
||||
padding: var(--ha-space-2) var(--ha-space-2);
|
||||
}
|
||||
|
||||
.invalid {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.sortable-fallback {
|
||||
@@ -329,6 +491,6 @@ class HaEntityStatePicker extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-entity-state-content-picker": HaEntityStatePicker;
|
||||
"ha-entity-state-content-picker": HaStateContentPicker;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import "@home-assistant/webawesome/dist/components/drawer/drawer";
|
||||
import { css, html, LitElement, type PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
|
||||
export const BOTTOM_SHEET_ANIMATION_DURATION_MS = 300;
|
||||
|
||||
@@ -37,49 +38,61 @@ export class HaBottomSheet extends LitElement {
|
||||
@wa-after-hide=${this._handleAfterHide}
|
||||
without-header
|
||||
>
|
||||
<slot></slot>
|
||||
<slot name="header"></slot>
|
||||
<div class="body ha-scrollbar">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</wa-drawer>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
wa-drawer {
|
||||
--wa-color-surface-raised: transparent;
|
||||
--spacing: 0;
|
||||
--size: var(--ha-bottom-sheet-height, auto);
|
||||
--show-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||
--hide-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||
}
|
||||
wa-drawer::part(dialog) {
|
||||
max-height: var(--ha-bottom-sheet-max-height, 90vh);
|
||||
align-items: center;
|
||||
}
|
||||
wa-drawer::part(body) {
|
||||
max-width: var(--ha-bottom-sheet-max-width);
|
||||
width: 100%;
|
||||
border-top-left-radius: var(
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
);
|
||||
border-top-right-radius: var(
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
);
|
||||
background-color: var(
|
||||
--ha-bottom-sheet-surface-background,
|
||||
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
|
||||
);
|
||||
padding: var(
|
||||
--ha-bottom-sheet-padding,
|
||||
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
|
||||
var(--safe-area-inset-left)
|
||||
);
|
||||
}
|
||||
|
||||
:host([flexcontent]) wa-drawer::part(body) {
|
||||
display: flex;
|
||||
}
|
||||
`;
|
||||
static styles = [
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
wa-drawer {
|
||||
--wa-color-surface-raised: transparent;
|
||||
--spacing: 0;
|
||||
--size: var(--ha-bottom-sheet-height, auto);
|
||||
--show-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||
--hide-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||
}
|
||||
wa-drawer::part(dialog) {
|
||||
max-height: var(--ha-bottom-sheet-max-height, 90vh);
|
||||
align-items: center;
|
||||
}
|
||||
wa-drawer::part(body) {
|
||||
max-width: var(--ha-bottom-sheet-max-width);
|
||||
width: 100%;
|
||||
border-top-left-radius: var(
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
);
|
||||
border-top-right-radius: var(
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
);
|
||||
background-color: var(
|
||||
--ha-bottom-sheet-surface-background,
|
||||
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
|
||||
);
|
||||
padding: var(
|
||||
--ha-bottom-sheet-padding,
|
||||
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
|
||||
var(--safe-area-inset-left)
|
||||
);
|
||||
}
|
||||
:host([flexcontent]) wa-drawer::part(body) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
:host([flexcontent]) .body {
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -31,6 +31,9 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
@property({ type: Boolean, reflect: true, attribute: "no-wrap" })
|
||||
public nowrap = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: "full-width" })
|
||||
public fullWidth = false;
|
||||
|
||||
@property() public variant:
|
||||
| "brand"
|
||||
| "neutral"
|
||||
@@ -38,6 +41,13 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
| "warning"
|
||||
| "danger" = "brand";
|
||||
|
||||
@property({ attribute: "active-variant" }) public activeVariant?:
|
||||
| "brand"
|
||||
| "neutral"
|
||||
| "success"
|
||||
| "warning"
|
||||
| "danger";
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<wa-button-group childSelector="ha-button">
|
||||
@@ -46,7 +56,9 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
html`<ha-button
|
||||
iconTag="ha-svg-icon"
|
||||
class="icon"
|
||||
.variant=${this.variant}
|
||||
.variant=${this.active !== button.value || !this.activeVariant
|
||||
? this.variant
|
||||
: this.activeVariant}
|
||||
.size=${this.size}
|
||||
.value=${button.value}
|
||||
@click=${this._handleClick}
|
||||
@@ -78,6 +90,19 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
:host([no-wrap]) wa-button-group::part(base) {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
wa-button-group {
|
||||
padding: var(--ha-button-toggle-group-padding);
|
||||
}
|
||||
|
||||
:host([full-width]) wa-button-group,
|
||||
:host([full-width]) wa-button-group::part(base) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:host([full-width]) ha-button {
|
||||
flex: 1;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ export class HaDialogHeader extends LitElement {
|
||||
@property({ type: String, attribute: "subtitle-position" })
|
||||
public subtitlePosition: "above" | "below" = "below";
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: "show-border" })
|
||||
public showBorder = false;
|
||||
|
||||
protected render() {
|
||||
const titleSlot = html`<div class="header-title">
|
||||
<slot name="title"></slot>
|
||||
|
||||
@@ -248,7 +248,7 @@ export class HaFilterDevices extends LitElement {
|
||||
}
|
||||
search-input-outlined {
|
||||
display: block;
|
||||
padding: 0 8px;
|
||||
padding: var(--ha-space-1) var(--ha-space-2) 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -199,7 +199,7 @@ export class HaFilterDomains extends LitElement {
|
||||
}
|
||||
search-input-outlined {
|
||||
display: block;
|
||||
padding: 0 8px;
|
||||
padding: var(--ha-space-1) var(--ha-space-2) 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -264,7 +264,7 @@ export class HaFilterEntities extends LitElement {
|
||||
}
|
||||
search-input-outlined {
|
||||
display: block;
|
||||
padding: 0 8px;
|
||||
padding: var(--ha-space-1) var(--ha-space-2) 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -217,7 +217,7 @@ export class HaFilterIntegrations extends LitElement {
|
||||
}
|
||||
search-input-outlined {
|
||||
display: block;
|
||||
padding: 0 8px;
|
||||
padding: var(--ha-space-1) var(--ha-space-2) 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -256,7 +256,7 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
search-input-outlined {
|
||||
display: block;
|
||||
padding: 0 8px;
|
||||
padding: var(--ha-space-1) var(--ha-space-2) 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -52,9 +52,10 @@ export class HaObjectSelector extends LitElement {
|
||||
const translationKey = this.selector.object?.translation_key;
|
||||
|
||||
if (this.localizeValue && translationKey) {
|
||||
const label = this.localizeValue(
|
||||
`${translationKey}.fields.${schema.name}`
|
||||
);
|
||||
const label =
|
||||
this.localizeValue(`${translationKey}.fields.${schema.name}.name`) ||
|
||||
// Fallback for backward compatibility
|
||||
this.localizeValue(`${translationKey}.fields.${schema.name}`);
|
||||
if (label) {
|
||||
return label;
|
||||
}
|
||||
@@ -62,6 +63,20 @@ export class HaObjectSelector extends LitElement {
|
||||
return this.selector.object?.fields?.[schema.name]?.label || schema.name;
|
||||
};
|
||||
|
||||
private _computeHelper = (schema: HaFormSchema): string => {
|
||||
const translationKey = this.selector.object?.translation_key;
|
||||
|
||||
if (this.localizeValue && translationKey) {
|
||||
const helper = this.localizeValue(
|
||||
`${translationKey}.fields.${schema.name}.description`
|
||||
);
|
||||
if (helper) {
|
||||
return helper;
|
||||
}
|
||||
}
|
||||
return this.selector.object?.fields?.[schema.name]?.description || "";
|
||||
};
|
||||
|
||||
private _renderItem(item: any, index: number) {
|
||||
const labelField =
|
||||
this.selector.object!.label_field ||
|
||||
@@ -214,6 +229,7 @@ export class HaObjectSelector extends LitElement {
|
||||
schema: this._schema(this.selector),
|
||||
data: {},
|
||||
computeLabel: this._computeLabel,
|
||||
computeHelper: this._computeHelper,
|
||||
submitText: this.hass.localize("ui.common.add"),
|
||||
});
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export class HaSelectorUiStateContent extends SubscribeMixin(LitElement) {
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.allowName=${this.selector.ui_state_content?.allow_name}
|
||||
.allowName=${this.selector.ui_state_content?.allow_name || false}
|
||||
></ha-entity-state-content-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,9 @@ class HaServicePicker extends LitElement {
|
||||
const description =
|
||||
this.hass.localize(
|
||||
`component.${domain}.services.${service}.description`
|
||||
) || services[domain][service].description;
|
||||
) ||
|
||||
services[domain][service].description ||
|
||||
"";
|
||||
|
||||
items.push({
|
||||
id: serviceId,
|
||||
|
||||
@@ -29,6 +29,7 @@ import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import { throttle } from "../common/util/throttle";
|
||||
import { subscribeFrontendUserData } from "../data/frontend";
|
||||
import type { ActionHandlerDetail } from "../data/lovelace/action_handler";
|
||||
@@ -536,11 +537,17 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _renderUserItem(selectedPanel: string) {
|
||||
const isRTL = computeRTL(this.hass);
|
||||
|
||||
return html`
|
||||
<ha-md-list-item
|
||||
href="/profile"
|
||||
type="link"
|
||||
class="user ${selectedPanel === "profile" ? " selected" : ""}"
|
||||
class=${classMap({
|
||||
user: true,
|
||||
selected: selectedPanel === "profile",
|
||||
rtl: isRTL,
|
||||
})}
|
||||
@mouseenter=${this._itemMouseEnter}
|
||||
@mouseleave=${this._itemMouseLeave}
|
||||
>
|
||||
@@ -666,7 +673,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
tooltip.style.display = "block";
|
||||
tooltip.style.position = "fixed";
|
||||
tooltip.style.top = `${top}px`;
|
||||
tooltip.style.left = `calc(${item.offsetLeft + item.clientWidth + 8}px + var(--safe-area-inset-left, 0px))`;
|
||||
tooltip.style.left = `calc(${item.offsetLeft + item.clientWidth + 8}px + var(--safe-area-inset-left, var(--ha-space-0)))`;
|
||||
}
|
||||
|
||||
private _hideTooltip() {
|
||||
@@ -705,13 +712,17 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
background-color: var(--sidebar-background-color);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-bottom: calc(14px + var(--safe-area-inset-bottom, 0px));
|
||||
padding-bottom: calc(
|
||||
14px + var(--safe-area-inset-bottom, var(--ha-space-0))
|
||||
);
|
||||
}
|
||||
.menu {
|
||||
height: calc(var(--header-height) + var(--safe-area-inset-top, 0px));
|
||||
height: calc(
|
||||
var(--header-height) + var(--safe-area-inset-top, var(--ha-space-0))
|
||||
);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
padding: 0 4px;
|
||||
padding: 0 var(--ha-space-1);
|
||||
border-bottom: 1px solid transparent;
|
||||
white-space: nowrap;
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
@@ -726,13 +737,17 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
font-size: var(--ha-font-size-xl);
|
||||
align-items: center;
|
||||
padding-left: calc(4px + var(--safe-area-inset-left, 0px));
|
||||
padding-inline-start: calc(4px + var(--safe-area-inset-left, 0px));
|
||||
padding-left: calc(
|
||||
var(--ha-space-1) + var(--safe-area-inset-left, var(--ha-space-0))
|
||||
);
|
||||
padding-inline-start: calc(
|
||||
var(--ha-space-1) + var(--safe-area-inset-left, var(--ha-space-0))
|
||||
);
|
||||
padding-inline-end: initial;
|
||||
padding-top: var(--safe-area-inset-top, 0px);
|
||||
padding-top: var(--safe-area-inset-top, var(--ha-space-0));
|
||||
}
|
||||
:host([expanded]) .menu {
|
||||
width: calc(256px + var(--safe-area-inset-left, 0px));
|
||||
width: calc(256px + var(--safe-area-inset-left, var(--ha-space-0)));
|
||||
}
|
||||
:host([narrow][expanded]) .menu {
|
||||
width: 100%;
|
||||
@@ -748,8 +763,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
display: none;
|
||||
}
|
||||
:host([narrow]) .title {
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
margin: var(--ha-space-0);
|
||||
padding: var(--ha-space-0) var(--ha-space-4);
|
||||
}
|
||||
:host([expanded]) .title {
|
||||
display: initial;
|
||||
@@ -761,13 +776,16 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
ha-fade-in,
|
||||
ha-md-list {
|
||||
height: calc(
|
||||
100% - var(--header-height) - var(--safe-area-inset-top, 0px) -
|
||||
100% - var(--header-height) - var(
|
||||
--safe-area-inset-top,
|
||||
var(--ha-space-0)
|
||||
) -
|
||||
132px
|
||||
);
|
||||
}
|
||||
|
||||
ha-fade-in {
|
||||
padding: 4px 0;
|
||||
padding: var(--ha-space-1) var(--ha-space-0);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -777,29 +795,29 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
ha-md-list {
|
||||
overflow-x: hidden;
|
||||
background: none;
|
||||
margin-left: var(--safe-area-inset-left, 0px);
|
||||
margin-left: var(--safe-area-inset-left, var(--ha-space-0));
|
||||
}
|
||||
|
||||
ha-md-list-item {
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
margin: 4px;
|
||||
margin: var(--ha-space-1);
|
||||
border-radius: var(--ha-border-radius-sm);
|
||||
--md-list-item-one-line-container-height: 40px;
|
||||
--md-list-item-one-line-container-height: var(--ha-space-10);
|
||||
--md-list-item-top-space: 0;
|
||||
--md-list-item-bottom-space: 0;
|
||||
width: 48px;
|
||||
width: var(--ha-space-12);
|
||||
position: relative;
|
||||
--md-list-item-label-text-color: var(--sidebar-text-color);
|
||||
--md-list-item-leading-space: 12px;
|
||||
--md-list-item-trailing-space: 12px;
|
||||
--md-list-item-leading-icon-size: 24px;
|
||||
--md-list-item-leading-space: var(--ha-space-3);
|
||||
--md-list-item-trailing-space: var(--ha-space-3);
|
||||
--md-list-item-leading-icon-size: var(--ha-space-6);
|
||||
}
|
||||
:host([expanded]) ha-md-list-item {
|
||||
width: 248px;
|
||||
}
|
||||
:host([narrow][expanded]) ha-md-list-item {
|
||||
width: calc(240px - var(--safe-area-inset-left, 0px));
|
||||
width: calc(240px - var(--safe-area-inset-left, var(--ha-space-0)));
|
||||
}
|
||||
|
||||
ha-md-list-item.selected {
|
||||
@@ -823,7 +841,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
|
||||
ha-icon[slot="start"],
|
||||
ha-svg-icon[slot="start"] {
|
||||
width: 24px;
|
||||
width: var(--ha-space-6);
|
||||
flex-shrink: 0;
|
||||
color: var(--sidebar-icon-color);
|
||||
}
|
||||
@@ -856,7 +874,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-width: 8px;
|
||||
min-width: var(--ha-space-2);
|
||||
border-radius: var(--ha-border-radius-xl);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
line-height: normal;
|
||||
@@ -867,22 +885,26 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
|
||||
ha-svg-icon + .badge {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
top: var(--ha-space-1);
|
||||
left: 26px;
|
||||
border-radius: var(--ha-border-radius-md);
|
||||
font-size: 0.65em;
|
||||
line-height: var(--ha-line-height-expanded);
|
||||
padding: 0 4px;
|
||||
padding: var(--ha-space-0) var(--ha-space-1);
|
||||
}
|
||||
|
||||
ha-md-list-item.user {
|
||||
--md-list-item-leading-icon-size: 40px;
|
||||
--md-list-item-leading-space: 4px;
|
||||
--md-list-item-leading-icon-size: var(--ha-space-10);
|
||||
--md-list-item-leading-space: var(--ha-space-1);
|
||||
}
|
||||
|
||||
ha-md-list-item.user.rtl {
|
||||
--md-list-item-leading-space: var(--ha-space-3);
|
||||
}
|
||||
|
||||
ha-user-badge {
|
||||
flex-shrink: 0;
|
||||
margin-right: -8px;
|
||||
margin-right: calc(var(--ha-space-2) * -1);
|
||||
}
|
||||
|
||||
.spacer {
|
||||
@@ -894,7 +916,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
color: var(--sidebar-text-color);
|
||||
font-size: var(--ha-font-size-m);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
padding: 16px;
|
||||
padding: var(--ha-space-4);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -906,7 +928,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
white-space: nowrap;
|
||||
color: var(--sidebar-background-color);
|
||||
background-color: var(--sidebar-text-color);
|
||||
padding: 4px;
|
||||
padding: var(--ha-space-1);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,26 +2,13 @@ import { TopAppBarFixedBase } from "@material/mwc-top-app-bar-fixed/mwc-top-app-
|
||||
import { styles } from "@material/mwc-top-app-bar/mwc-top-app-bar.css";
|
||||
import { css } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { ViewTransitionMixin } from "../mixins/view-transition-mixin";
|
||||
import { haStyleViewTransitions } from "../resources/styles";
|
||||
|
||||
@customElement("ha-top-app-bar-fixed")
|
||||
export class HaTopAppBarFixed extends ViewTransitionMixin(TopAppBarFixedBase) {
|
||||
export class HaTopAppBarFixed extends TopAppBarFixedBase {
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: "content-loading" })
|
||||
public contentLoading = true;
|
||||
|
||||
protected override onLoadTransition(): void {
|
||||
// Use reflected property since we can't add class to base component's rendered elements
|
||||
this.startViewTransition(() => {
|
||||
this.contentLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
styles,
|
||||
haStyleViewTransitions,
|
||||
css`
|
||||
header {
|
||||
padding-top: var(--safe-area-inset-top);
|
||||
@@ -36,10 +23,6 @@ export class HaTopAppBarFixed extends ViewTransitionMixin(TopAppBarFixedBase) {
|
||||
);
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
padding-right: var(--safe-area-inset-right);
|
||||
view-transition-name: layout-fade-in;
|
||||
}
|
||||
:host([content-loading]) .mdc-top-app-bar--fixed-adjust {
|
||||
opacity: 0;
|
||||
}
|
||||
:host([narrow]) .mdc-top-app-bar--fixed-adjust {
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
|
||||
@@ -10,15 +10,14 @@ import { html, css, nothing } from "lit";
|
||||
import { property, query, customElement } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styles } from "@material/mwc-top-app-bar/mwc-top-app-bar.css";
|
||||
import { ViewTransitionMixin } from "../mixins/view-transition-mixin";
|
||||
import { haStyleScrollbar, haStyleViewTransitions } from "../resources/styles";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
|
||||
export const passiveEventOptionsIfSupported = supportsPassiveEventListener
|
||||
? { passive: true }
|
||||
: undefined;
|
||||
|
||||
@customElement("ha-two-pane-top-app-bar-fixed")
|
||||
export class TopAppBarBaseBase extends ViewTransitionMixin(BaseElement) {
|
||||
export class TopAppBarBaseBase extends BaseElement {
|
||||
protected override mdcFoundation!: MDCFixedTopAppBarFoundation;
|
||||
|
||||
protected override mdcFoundationClass = MDCFixedTopAppBarFoundation;
|
||||
@@ -145,12 +144,7 @@ export class TopAppBarBaseBase extends ViewTransitionMixin(BaseElement) {
|
||||
: nothing}
|
||||
<div class="main">
|
||||
${this.pane ? html`<div class="shadow-container"></div>` : nothing}
|
||||
<div
|
||||
class=${classMap({
|
||||
content: true,
|
||||
loading: !this._loaded,
|
||||
})}
|
||||
>
|
||||
<div class="content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
@@ -251,7 +245,6 @@ export class TopAppBarBaseBase extends ViewTransitionMixin(BaseElement) {
|
||||
static override styles = [
|
||||
styles,
|
||||
haStyleScrollbar,
|
||||
haStyleViewTransitions,
|
||||
css`
|
||||
header {
|
||||
padding-top: var(--safe-area-inset-top);
|
||||
@@ -348,10 +341,6 @@ export class TopAppBarBaseBase extends ViewTransitionMixin(BaseElement) {
|
||||
.mdc-top-app-bar--pane .content {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
view-transition-name: layout-fade-in;
|
||||
}
|
||||
.content.loading {
|
||||
opacity: 0;
|
||||
}
|
||||
.mdc-top-app-bar__title {
|
||||
font-size: var(--ha-font-size-xl);
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "@home-assistant/webawesome/dist/components/dialog/dialog";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import "./ha-dialog-header";
|
||||
import "./ha-icon-button";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import {
|
||||
customElement,
|
||||
eventOptions,
|
||||
property,
|
||||
query,
|
||||
state,
|
||||
} from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-dialog-header";
|
||||
import "./ha-icon-button";
|
||||
|
||||
export type DialogWidth = "small" | "medium" | "large" | "full";
|
||||
|
||||
@@ -90,6 +96,11 @@ export class HaWaDialog extends LitElement {
|
||||
@state()
|
||||
private _open = false;
|
||||
|
||||
@query(".body") public bodyContainer!: HTMLDivElement;
|
||||
|
||||
@state()
|
||||
private _bodyScrolled = false;
|
||||
|
||||
protected updated(
|
||||
changedProperties: Map<string | number | symbol, unknown>
|
||||
): void {
|
||||
@@ -107,10 +118,14 @@ export class HaWaDialog extends LitElement {
|
||||
.lightDismiss=${!this.preventScrimClose}
|
||||
without-header
|
||||
@wa-show=${this._handleShow}
|
||||
@wa-after-show=${this._handleAfterShow}
|
||||
@wa-after-hide=${this._handleAfterHide}
|
||||
>
|
||||
<slot name="header">
|
||||
<ha-dialog-header .subtitlePosition=${this.headerSubtitlePosition}>
|
||||
<ha-dialog-header
|
||||
.subtitlePosition=${this.headerSubtitlePosition}
|
||||
.showBorder=${this._bodyScrolled}
|
||||
>
|
||||
<slot name="headerNavigationIcon" slot="navigationIcon">
|
||||
<ha-icon-button
|
||||
data-dialog="close"
|
||||
@@ -129,7 +144,7 @@ export class HaWaDialog extends LitElement {
|
||||
<slot name="headerActionItems" slot="actionItems"></slot>
|
||||
</ha-dialog-header>
|
||||
</slot>
|
||||
<div class="body ha-scrollbar">
|
||||
<div class="body ha-scrollbar" @scroll=${this._handleBodyScroll}>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<slot name="footer" slot="footer"></slot>
|
||||
@@ -146,6 +161,10 @@ export class HaWaDialog extends LitElement {
|
||||
(this.querySelector("[autofocus]") as HTMLElement | null)?.focus();
|
||||
};
|
||||
|
||||
private _handleAfterShow = () => {
|
||||
fireEvent(this, "after-show");
|
||||
};
|
||||
|
||||
private _handleAfterHide = () => {
|
||||
this._open = false;
|
||||
fireEvent(this, "closed");
|
||||
@@ -156,6 +175,11 @@ export class HaWaDialog extends LitElement {
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private _handleBodyScroll(ev: Event) {
|
||||
this._bodyScrolled = (ev.target as HTMLDivElement).scrollTop > 0;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
@@ -172,7 +196,7 @@ export class HaWaDialog extends LitElement {
|
||||
)
|
||||
)
|
||||
);
|
||||
--width: var(--ha-dialog-width-md, min(580px, var(--full-width)));
|
||||
--width: min(var(--ha-dialog-width-md, 580px), var(--full-width));
|
||||
--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);
|
||||
@@ -193,11 +217,11 @@ export class HaWaDialog extends LitElement {
|
||||
}
|
||||
|
||||
:host([width="small"]) wa-dialog {
|
||||
--width: var(--ha-dialog-width-sm, min(320px, var(--full-width)));
|
||||
--width: min(var(--ha-dialog-width-sm, 320px), var(--full-width));
|
||||
}
|
||||
|
||||
:host([width="large"]) wa-dialog {
|
||||
--width: var(--ha-dialog-width-lg, min(720px, var(--full-width)));
|
||||
--width: min(var(--ha-dialog-width-lg, 720px), var(--full-width));
|
||||
}
|
||||
|
||||
:host([width="full"]) wa-dialog {
|
||||
@@ -211,6 +235,7 @@ export class HaWaDialog extends LitElement {
|
||||
--ha-dialog-max-height,
|
||||
calc(100% - var(--ha-space-20))
|
||||
);
|
||||
min-height: var(--ha-dialog-min-height);
|
||||
position: var(--dialog-surface-position, relative);
|
||||
margin-top: var(--dialog-surface-margin-top, auto);
|
||||
display: flex;
|
||||
@@ -284,6 +309,7 @@ export class HaWaDialog extends LitElement {
|
||||
}
|
||||
:host([flexcontent]) .body {
|
||||
max-width: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -312,6 +338,7 @@ declare global {
|
||||
|
||||
interface HASSDomEvents {
|
||||
opened: undefined;
|
||||
"after-show": undefined;
|
||||
closed: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { until } from "lit/directives/until";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { slugify } from "../../common/string/slugify";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { isUnavailableState } from "../../data/entity";
|
||||
import type {
|
||||
@@ -693,10 +694,12 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<ha-tooltip .for="grid-${child.title}" distance="-4">
|
||||
<ha-tooltip .for="grid-${slugify(child.title)}" distance="-4">
|
||||
${child.title}
|
||||
</ha-tooltip>
|
||||
<div .id="grid-${child.title}" class="title">${child.title}</div>
|
||||
<div .id="grid-${slugify(child.title)}" class="title">
|
||||
${child.title}
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../../ha-dialog-header";
|
||||
import "../../ha-icon-button";
|
||||
import "../../ha-icon-next";
|
||||
import "../../ha-md-dialog";
|
||||
import type { HaMdDialog } from "../../ha-md-dialog";
|
||||
import "../../ha-md-list";
|
||||
import "../../ha-md-list-item";
|
||||
import "../../ha-svg-icon";
|
||||
import "../../ha-wa-dialog";
|
||||
import "../ha-target-picker-item-row";
|
||||
import type { TargetDetailsDialogParams } from "./show-dialog-target-details";
|
||||
|
||||
@@ -21,14 +19,15 @@ class DialogTargetDetails extends LitElement implements HassDialog {
|
||||
|
||||
@state() private _params?: TargetDetailsDialogParams;
|
||||
|
||||
@query("ha-md-dialog") private _dialog?: HaMdDialog;
|
||||
@state() private _opened = false;
|
||||
|
||||
public showDialog(params: TargetDetailsDialogParams): void {
|
||||
this._params = params;
|
||||
this._opened = true;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._dialog?.close();
|
||||
this._opened = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -43,58 +42,31 @@ class DialogTargetDetails extends LitElement implements HassDialog {
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-md-dialog open @closed=${this._dialogClosed}>
|
||||
<ha-dialog-header slot="headline">
|
||||
<ha-icon-button
|
||||
slot="navigationIcon"
|
||||
@click=${this.closeDialog}
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
<span slot="title"
|
||||
>${this.hass.localize(
|
||||
"ui.components.target-picker.target_details"
|
||||
)}</span
|
||||
>
|
||||
<span slot="subtitle"
|
||||
>${this.hass.localize(
|
||||
`ui.components.target-picker.type.${this._params.type}`
|
||||
)}:
|
||||
${this._params.title}</span
|
||||
>
|
||||
</ha-dialog-header>
|
||||
<div slot="content">
|
||||
<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}
|
||||
expand
|
||||
></ha-target-picker-item-row>
|
||||
</div>
|
||||
</ha-md-dialog>
|
||||
<ha-wa-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}
|
||||
>
|
||||
<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}
|
||||
expand
|
||||
></ha-target-picker-item-row>
|
||||
</ha-wa-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-md-dialog {
|
||||
min-width: 400px;
|
||||
max-height: 90%;
|
||||
--dialog-content-padding: var(--ha-space-2) var(--ha-space-6)
|
||||
max(var(--safe-area-inset-bottom, var(--ha-space-0)), var(--ha-space-8));
|
||||
}
|
||||
|
||||
@media all and (max-width: 600px), all and (max-height: 500px) {
|
||||
ha-md-dialog {
|
||||
--md-dialog-container-shape: var(--ha-space-0);
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -162,11 +162,12 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
<div slot="headline">${name}</div>
|
||||
${context && !this.hideContext
|
||||
? html`<span slot="supporting-text">${context}</span>`
|
||||
: this._domainName && this.subEntry
|
||||
? html`<span slot="supporting-text" class="domain"
|
||||
>${this._domainName}</span
|
||||
>`
|
||||
: nothing}
|
||||
: 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">
|
||||
@@ -231,9 +232,11 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
const rows1 =
|
||||
(nextType === "area"
|
||||
? entries?.referenced_areas
|
||||
: nextType === "device"
|
||||
: nextType === "device" && this.type !== "label"
|
||||
? entries?.referenced_devices
|
||||
: entries?.referenced_entities) || [];
|
||||
: this.type !== "label"
|
||||
? entries?.referenced_entities
|
||||
: []) || [];
|
||||
|
||||
const devicesInAreas = [] as string[];
|
||||
|
||||
@@ -284,9 +287,13 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
|
||||
const entityRows =
|
||||
this.type === "label" && entries
|
||||
? entries.referenced_entities.filter((entity_id) =>
|
||||
this.hass.entities[entity_id].labels.includes(this.itemId)
|
||||
)
|
||||
? entries.referenced_entities.filter((entity_id) => {
|
||||
const entity = this.hass.entities[entity_id];
|
||||
return (
|
||||
entity.labels.includes(this.itemId) &&
|
||||
!entries.referenced_devices.includes(entity.device_id || "")
|
||||
);
|
||||
})
|
||||
: nextType === "device" && entries
|
||||
? entries.referenced_entities.filter(
|
||||
(entity_id) =>
|
||||
@@ -412,7 +419,6 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
const device = this.hass.devices[device_id];
|
||||
if (
|
||||
!hiddenAreaIds.includes(device.area_id || "") &&
|
||||
(this.type !== "label" || device.labels.includes(this.itemId)) &&
|
||||
deviceMeetsFilter(
|
||||
device,
|
||||
this.hass.entities,
|
||||
@@ -669,6 +675,14 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
button.link:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.domain {
|
||||
width: fit-content;
|
||||
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);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import memoizeOne from "memoize-one";
|
||||
import { computeCssColor } from "../../common/color/compute-color";
|
||||
import { hex2rgb } from "../../common/color/convert-color";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { slugify } from "../../common/string/slugify";
|
||||
import {
|
||||
computeDeviceName,
|
||||
computeDeviceNameDisplay,
|
||||
@@ -102,7 +103,7 @@ export class HaTargetPickerValueChip extends LitElement {
|
||||
${this.type === "entity"
|
||||
? nothing
|
||||
: html`<span role="gridcell">
|
||||
<ha-tooltip .for="expand-${this.itemId}"
|
||||
<ha-tooltip .for="expand-${slugify(this.itemId)}"
|
||||
>${this.hass.localize(
|
||||
`ui.components.target-picker.expand_${this.type}_id`
|
||||
)}
|
||||
@@ -114,13 +115,13 @@ export class HaTargetPickerValueChip extends LitElement {
|
||||
)}
|
||||
.path=${mdiUnfoldMoreVertical}
|
||||
hide-title
|
||||
.id="expand-${this.itemId}"
|
||||
.id="expand-${slugify(this.itemId)}"
|
||||
.type=${this.type}
|
||||
@click=${this._handleExpand}
|
||||
></ha-icon-button>
|
||||
</span>`}
|
||||
<span role="gridcell">
|
||||
<ha-tooltip .for="remove-${this.itemId}">
|
||||
<ha-tooltip .for="remove-${slugify(this.itemId)}">
|
||||
${this.hass.localize(
|
||||
`ui.components.target-picker.remove_${this.type}_id`
|
||||
)}
|
||||
@@ -130,7 +131,7 @@ export class HaTargetPickerValueChip extends LitElement {
|
||||
.label=${this.hass.localize("ui.components.target-picker.remove")}
|
||||
.path=${mdiClose}
|
||||
hide-title
|
||||
.id="remove-${this.itemId}"
|
||||
.id="remove-${slugify(this.itemId)}"
|
||||
.type=${this.type}
|
||||
@click=${this._removeItem}
|
||||
></ha-icon-button>
|
||||
|
||||
@@ -6,8 +6,6 @@ import {
|
||||
mdiCallSplit,
|
||||
mdiCodeBraces,
|
||||
mdiDevices,
|
||||
mdiDotsHorizontal,
|
||||
mdiExcavator,
|
||||
mdiFormatListNumbered,
|
||||
mdiGestureDoubleTap,
|
||||
mdiHandBackRight,
|
||||
@@ -16,10 +14,10 @@ import {
|
||||
mdiRoomService,
|
||||
mdiShuffleDisabled,
|
||||
mdiTimerOutline,
|
||||
mdiTools,
|
||||
mdiTrafficLight,
|
||||
} from "@mdi/js";
|
||||
import type { AutomationElementGroup } from "./automation";
|
||||
import type { AutomationElementGroupCollection } from "./automation";
|
||||
import type { Action } from "./script";
|
||||
|
||||
export const ACTION_ICONS = {
|
||||
condition: mdiAbTesting,
|
||||
@@ -48,37 +46,73 @@ export const YAML_ONLY_ACTION_TYPES = new Set<keyof typeof ACTION_ICONS>([
|
||||
"variables",
|
||||
]);
|
||||
|
||||
export const ACTION_GROUPS: AutomationElementGroup = {
|
||||
device_id: {},
|
||||
helpers: {
|
||||
icon: mdiTools,
|
||||
members: {},
|
||||
},
|
||||
building_blocks: {
|
||||
icon: mdiExcavator,
|
||||
members: {
|
||||
condition: {},
|
||||
delay: {},
|
||||
wait_template: {},
|
||||
wait_for_trigger: {},
|
||||
repeat_count: {},
|
||||
repeat_while: {},
|
||||
repeat_until: {},
|
||||
repeat_for_each: {},
|
||||
choose: {},
|
||||
if: {},
|
||||
stop: {},
|
||||
sequence: {},
|
||||
parallel: {},
|
||||
variables: {},
|
||||
export const ACTION_COLLECTIONS: AutomationElementGroupCollection[] = [
|
||||
{
|
||||
groups: {
|
||||
device_id: {},
|
||||
serviceGroups: {},
|
||||
},
|
||||
},
|
||||
other: {
|
||||
icon: mdiDotsHorizontal,
|
||||
members: {
|
||||
{
|
||||
titleKey: "ui.panel.config.automation.editor.actions.groups.helpers.label",
|
||||
groups: {
|
||||
helpers: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
titleKey: "ui.panel.config.automation.editor.actions.groups.other.label",
|
||||
groups: {
|
||||
event: {},
|
||||
service: {},
|
||||
set_conversation_response: {},
|
||||
other: {},
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
|
||||
export const ACTION_BUILDING_BLOCKS_GROUP = {
|
||||
condition: {},
|
||||
delay: {},
|
||||
wait_template: {},
|
||||
wait_for_trigger: {},
|
||||
repeat_count: {},
|
||||
repeat_while: {},
|
||||
repeat_until: {},
|
||||
repeat_for_each: {},
|
||||
choose: {},
|
||||
if: {},
|
||||
stop: {},
|
||||
sequence: {},
|
||||
parallel: {},
|
||||
variables: {},
|
||||
};
|
||||
|
||||
// These will be replaced with the correct action
|
||||
export const VIRTUAL_ACTIONS: Partial<
|
||||
Record<keyof typeof ACTION_BUILDING_BLOCKS_GROUP, Action>
|
||||
> = {
|
||||
repeat_count: {
|
||||
repeat: {
|
||||
count: 2,
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
repeat_while: {
|
||||
repeat: {
|
||||
while: [],
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
repeat_until: {
|
||||
repeat: {
|
||||
until: [],
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
repeat_for_each: {
|
||||
repeat: {
|
||||
for_each: {},
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
} from "home-assistant-js-websocket";
|
||||
import { ensureArray } from "../common/array/ensure-array";
|
||||
import { navigate } from "../common/navigate";
|
||||
import type { LocalizeKeys } from "../common/translations/localize";
|
||||
import { createSearchParam } from "../common/url/search-params";
|
||||
import type { Context, HomeAssistant } from "../types";
|
||||
import type { BlueprintInput } from "./blueprint";
|
||||
@@ -293,6 +294,11 @@ export interface ShorthandNotCondition extends ShorthandBaseCondition {
|
||||
not: Condition[];
|
||||
}
|
||||
|
||||
export interface AutomationElementGroupCollection {
|
||||
titleKey?: LocalizeKeys;
|
||||
groups: AutomationElementGroup;
|
||||
}
|
||||
|
||||
export type AutomationElementGroup = Record<
|
||||
string,
|
||||
{ icon?: string; members?: AutomationElementGroup }
|
||||
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
mdiClockOutline,
|
||||
mdiCodeBraces,
|
||||
mdiDevices,
|
||||
mdiDotsHorizontal,
|
||||
mdiExcavator,
|
||||
mdiGateOr,
|
||||
mdiIdentifier,
|
||||
mdiMapClock,
|
||||
@@ -15,7 +13,7 @@ import {
|
||||
mdiStateMachine,
|
||||
mdiWeatherSunny,
|
||||
} from "@mdi/js";
|
||||
import type { AutomationElementGroup } from "./automation";
|
||||
import type { AutomationElementGroupCollection } from "./automation";
|
||||
|
||||
export const CONDITION_ICONS = {
|
||||
device: mdiDevices,
|
||||
@@ -31,25 +29,31 @@ export const CONDITION_ICONS = {
|
||||
zone: mdiMapMarkerRadius,
|
||||
};
|
||||
|
||||
export const CONDITION_GROUPS: AutomationElementGroup = {
|
||||
device: {},
|
||||
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
|
||||
time_location: {
|
||||
icon: mdiMapClock,
|
||||
members: { sun: {}, time: {}, zone: {} },
|
||||
export const CONDITION_COLLECTIONS: AutomationElementGroupCollection[] = [
|
||||
{
|
||||
groups: {
|
||||
device: {},
|
||||
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
|
||||
time_location: {
|
||||
icon: mdiMapClock,
|
||||
members: { sun: {}, time: {}, zone: {} },
|
||||
},
|
||||
},
|
||||
},
|
||||
building_blocks: {
|
||||
icon: mdiExcavator,
|
||||
members: { and: {}, or: {}, not: {} },
|
||||
},
|
||||
other: {
|
||||
icon: mdiDotsHorizontal,
|
||||
members: {
|
||||
{
|
||||
titleKey: "ui.panel.config.automation.editor.conditions.groups.other.label",
|
||||
groups: {
|
||||
template: {},
|
||||
trigger: {},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
] as const;
|
||||
|
||||
export const CONDITION_BUILDING_BLOCKS_GROUP = {
|
||||
and: {},
|
||||
or: {},
|
||||
not: {},
|
||||
};
|
||||
|
||||
export const CONDITION_BUILDING_BLOCKS = ["and", "or", "not"];
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ export const floorCompare =
|
||||
const floorA = entries?.[a];
|
||||
const floorB = entries?.[b];
|
||||
if (floorA && floorB && floorA.level !== floorB.level) {
|
||||
return (floorA.level ?? 9999) - (floorB.level ?? 9999);
|
||||
return (floorB.level ?? -9999) - (floorA.level ?? -9999);
|
||||
}
|
||||
const nameA = floorA?.name ?? a;
|
||||
const nameB = floorB?.name ?? b;
|
||||
|
||||
@@ -352,6 +352,7 @@ export interface NumberSelector {
|
||||
interface ObjectSelectorField {
|
||||
selector: Selector;
|
||||
label?: string;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
mdiClockOutline,
|
||||
mdiCodeBraces,
|
||||
mdiDevices,
|
||||
mdiDotsHorizontal,
|
||||
mdiFormatListBulleted,
|
||||
mdiGestureDoubleTap,
|
||||
mdiMapClock,
|
||||
@@ -23,7 +22,7 @@ import {
|
||||
|
||||
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
|
||||
import type {
|
||||
AutomationElementGroup,
|
||||
AutomationElementGroupCollection,
|
||||
Trigger,
|
||||
TriggerList,
|
||||
} from "./automation";
|
||||
@@ -49,16 +48,26 @@ export const TRIGGER_ICONS = {
|
||||
list: mdiFormatListBulleted,
|
||||
};
|
||||
|
||||
export const TRIGGER_GROUPS: AutomationElementGroup = {
|
||||
device: {},
|
||||
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
|
||||
time_location: {
|
||||
icon: mdiMapClock,
|
||||
members: { calendar: {}, sun: {}, time: {}, time_pattern: {}, zone: {} },
|
||||
export const TRIGGER_COLLECTIONS: AutomationElementGroupCollection[] = [
|
||||
{
|
||||
groups: {
|
||||
device: {},
|
||||
entity: { icon: mdiShape, members: { state: {}, numeric_state: {} } },
|
||||
time_location: {
|
||||
icon: mdiMapClock,
|
||||
members: {
|
||||
calendar: {},
|
||||
sun: {},
|
||||
time: {},
|
||||
time_pattern: {},
|
||||
zone: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
other: {
|
||||
icon: mdiDotsHorizontal,
|
||||
members: {
|
||||
{
|
||||
titleKey: "ui.panel.config.automation.editor.triggers.groups.other.label",
|
||||
groups: {
|
||||
event: {},
|
||||
geo_location: {},
|
||||
homeassistant: {},
|
||||
@@ -70,7 +79,7 @@ export const TRIGGER_GROUPS: AutomationElementGroup = {
|
||||
persistent_notification: {},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
] as const;
|
||||
|
||||
export const isTriggerList = (trigger: Trigger): trigger is TriggerList =>
|
||||
"triggers" in trigger;
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
view-transition-name: layout-fade-out;
|
||||
}
|
||||
#ha-launch-screen svg {
|
||||
width: 112px;
|
||||
|
||||
@@ -61,7 +61,6 @@ class HassLoadingScreen extends LitElement {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background-color: var(--primary-background-color);
|
||||
view-transition-name: layout-fade-out;
|
||||
}
|
||||
.toolbar {
|
||||
display: flex;
|
||||
|
||||
@@ -3,7 +3,6 @@ import { ReactiveElement } from "lit";
|
||||
import { property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { ViewTransitionMixin } from "../mixins/view-transition-mixin";
|
||||
import type { Route } from "../types";
|
||||
|
||||
const extractPage = (path: string, defaultPage: string) => {
|
||||
@@ -44,7 +43,7 @@ export interface RouterOptions {
|
||||
// Time to wait for code to load before we show loading screen.
|
||||
const LOADING_SCREEN_THRESHOLD = 400; // ms
|
||||
|
||||
export class HassRouterPage extends ViewTransitionMixin(ReactiveElement) {
|
||||
export class HassRouterPage extends ReactiveElement {
|
||||
@property({ attribute: false }) public route?: Route;
|
||||
|
||||
protected routerOptions!: RouterOptions;
|
||||
@@ -311,18 +310,16 @@ export class HassRouterPage extends ViewTransitionMixin(ReactiveElement) {
|
||||
page: string,
|
||||
routeOptions: RouteOptions
|
||||
) {
|
||||
this.startViewTransition(() => {
|
||||
if (this.lastChild) {
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
if (this.lastChild) {
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
|
||||
const panelEl = this._cache[page] || this.createElement(routeOptions.tag);
|
||||
this.updatePageEl(panelEl);
|
||||
this.appendChild(panelEl);
|
||||
const panelEl = this._cache[page] || this.createElement(routeOptions.tag);
|
||||
this.updatePageEl(panelEl);
|
||||
this.appendChild(panelEl);
|
||||
|
||||
if (routerOptions.cacheAll || routeOptions.cache) {
|
||||
this._cache[page] = panelEl;
|
||||
}
|
||||
});
|
||||
if (routerOptions.cacheAll || routeOptions.cache) {
|
||||
this._cache[page] = panelEl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, eventOptions, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { restoreScroll } from "../common/decorators/restore-scroll";
|
||||
import { goBack } from "../common/navigate";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
import "../components/ha-menu-button";
|
||||
import { ViewTransitionMixin } from "../mixins/view-transition-mixin";
|
||||
import { haStyleScrollbar, haStyleViewTransitions } from "../resources/styles";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
@customElement("hass-subpage")
|
||||
class HassSubpage extends ViewTransitionMixin(LitElement) {
|
||||
class HassSubpage extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public header?: string;
|
||||
@@ -62,14 +60,7 @@ class HassSubpage extends ViewTransitionMixin(LitElement) {
|
||||
<slot name="toolbar-icon"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class=${classMap({
|
||||
content: true,
|
||||
"ha-scrollbar": true,
|
||||
loading: !this._loaded,
|
||||
})}
|
||||
@scroll=${this._saveScrollPos}
|
||||
>
|
||||
<div class="content ha-scrollbar" @scroll=${this._saveScrollPos}>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div id="fab">
|
||||
@@ -94,7 +85,6 @@ class HassSubpage extends ViewTransitionMixin(LitElement) {
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleScrollbar,
|
||||
haStyleViewTransitions,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
@@ -177,10 +167,6 @@ class HassSubpage extends ViewTransitionMixin(LitElement) {
|
||||
overflow-y: auto;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
view-transition-name: layout-fade-in;
|
||||
}
|
||||
.content.loading {
|
||||
opacity: 0;
|
||||
}
|
||||
:host([narrow]) .content {
|
||||
width: calc(
|
||||
|
||||
@@ -11,8 +11,7 @@ import "../components/ha-icon-button-arrow-prev";
|
||||
import "../components/ha-menu-button";
|
||||
import "../components/ha-svg-icon";
|
||||
import "../components/ha-tab";
|
||||
import { ViewTransitionMixin } from "../mixins/view-transition-mixin";
|
||||
import { haStyleScrollbar, haStyleViewTransitions } from "../resources/styles";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../types";
|
||||
|
||||
export interface PageNavigation {
|
||||
@@ -30,7 +29,7 @@ export interface PageNavigation {
|
||||
}
|
||||
|
||||
@customElement("hass-tabs-subpage")
|
||||
class HassTabsSubpage extends ViewTransitionMixin(LitElement) {
|
||||
class HassTabsSubpage extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public supervisor = false;
|
||||
@@ -186,12 +185,7 @@ class HassTabsSubpage extends ViewTransitionMixin(LitElement) {
|
||||
</div>`
|
||||
: nothing}
|
||||
<div
|
||||
class=${classMap({
|
||||
content: true,
|
||||
"ha-scrollbar": true,
|
||||
tabs: showTabs,
|
||||
loading: !this._loaded,
|
||||
})}
|
||||
class="content ha-scrollbar ${classMap({ tabs: showTabs })}"
|
||||
@scroll=${this._saveScrollPos}
|
||||
>
|
||||
<slot></slot>
|
||||
@@ -220,7 +214,6 @@ class HassTabsSubpage extends ViewTransitionMixin(LitElement) {
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleScrollbar,
|
||||
haStyleViewTransitions,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
@@ -339,10 +332,6 @@ class HassTabsSubpage extends ViewTransitionMixin(LitElement) {
|
||||
margin-bottom: var(--safe-area-inset-bottom);
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
view-transition-name: layout-fade-in;
|
||||
}
|
||||
.content.loading {
|
||||
opacity: 0;
|
||||
}
|
||||
:host([narrow]) .content {
|
||||
margin-left: var(--safe-area-inset-left);
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
import type { PropertyValues, ReactiveElement } from "lit";
|
||||
import { state } from "lit/decorators";
|
||||
|
||||
/**
|
||||
* Abstract constructor type for a class that extends a reactive element
|
||||
* @param T - The type of the reactive element
|
||||
* @returns The abstract constructor
|
||||
*/
|
||||
type AbstractConstructor<T extends ReactiveElement> = abstract new (
|
||||
...args: any[]
|
||||
) => T;
|
||||
|
||||
/**
|
||||
* ViewTransitionMixin - Adds view transition support to reactive elements
|
||||
*
|
||||
* This mixin provides automatic fade-in transitions when content loads using the
|
||||
* View Transition API. User preferences are respected for reduced motion.
|
||||
* Falls back to synchronous updates for browsers that don't support the API.
|
||||
*
|
||||
* @example
|
||||
* Basic usage:
|
||||
* ```typescript
|
||||
* @customElement("my-component")
|
||||
* class MyComponent extends ViewTransitionMixin(LitElement) {
|
||||
* render() {
|
||||
* return html`
|
||||
* <div class=${classMap({ content: true, loading: !this._loaded })}>
|
||||
* <slot></slot>
|
||||
* </div>
|
||||
* `;
|
||||
* }
|
||||
*
|
||||
* static styles = css`
|
||||
* .content {
|
||||
* view-transition-name: layout-fade-in;
|
||||
* }
|
||||
* .content.loading {
|
||||
* opacity: 0; // Hidden during initial load for transition
|
||||
* }
|
||||
* `;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* Triggering transitions manually:
|
||||
* ```typescript
|
||||
* private _switchView() {
|
||||
* this.startViewTransition(() => {
|
||||
* // DOM updates here will be animated
|
||||
* this.currentView = newView;
|
||||
* });
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* Custom load behavior:
|
||||
* ```typescript
|
||||
* protected override onLoadTransition(): void {
|
||||
* // Custom logic before triggering transition
|
||||
* this.startViewTransition(() => {
|
||||
* this._loaded = true;
|
||||
* this._additionalSetup();
|
||||
* });
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Features:
|
||||
* - Automatic fade-in transition when slotted content loads
|
||||
* - Provides `_loaded` state property for conditional rendering
|
||||
* - `startViewTransition()` method for manual transitions
|
||||
* - Respects prefers-reduced-motion user preference
|
||||
* - Falls back gracefully when View Transition API unavailable
|
||||
* - Automatic cleanup of event listeners
|
||||
*
|
||||
* The mixin monitors the default slot and triggers `onLoadTransition()` when
|
||||
* content is available. Override `onLoadTransition()` to customize this behavior.
|
||||
*/
|
||||
export const ViewTransitionMixin = <
|
||||
T extends AbstractConstructor<ReactiveElement>,
|
||||
>(
|
||||
superClass: T
|
||||
) => {
|
||||
abstract class ViewTransitionClass extends superClass {
|
||||
/**
|
||||
* Reference to the default (unnamed) slot element for monitoring content changes.
|
||||
* Used to detect when slotted content is available to trigger load transitions.
|
||||
*/
|
||||
private _slot?: HTMLSlotElement;
|
||||
|
||||
/**
|
||||
* Prevents multiple slotchange events from triggering the transition more than once.
|
||||
* Once content loads and transition starts, this flag ensures it won't retrigger.
|
||||
*/
|
||||
private _transitionTriggered = false;
|
||||
|
||||
/**
|
||||
* State property indicating whether content has finished loading.
|
||||
* Use this in templates with the loading class pattern to hide content until ready.
|
||||
*/
|
||||
@state() protected _loaded = false;
|
||||
|
||||
/**
|
||||
* Trigger a view transition if supported by the browser
|
||||
* @param updateCallback - Callback function that updates the DOM
|
||||
* @returns Promise that resolves when the transition is complete
|
||||
*/
|
||||
protected async startViewTransition(
|
||||
updateCallback: () => void | Promise<void>
|
||||
): Promise<void> {
|
||||
if (
|
||||
!document.startViewTransition ||
|
||||
window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
||||
) {
|
||||
// Fallback: update without view transition
|
||||
await updateCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
const transition = document.startViewTransition(async () => {
|
||||
await updateCallback();
|
||||
});
|
||||
|
||||
try {
|
||||
await transition.finished;
|
||||
} catch {
|
||||
// View transition failed - this is non-critical, continue silently
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback executed when content is ready to transition in.
|
||||
*
|
||||
* Called automatically when:
|
||||
* - The default slot receives content (slotchange event)
|
||||
* - No slot exists in the component (triggers immediately after firstUpdated)
|
||||
*
|
||||
* Default implementation sets `_loaded = true` within a view transition.
|
||||
* Override this method to add custom logic before or during the transition,
|
||||
* but ensure you call `startViewTransition()` to maintain transition behavior.
|
||||
*/
|
||||
protected onLoadTransition(): void {
|
||||
this.startViewTransition(() => {
|
||||
this._loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if slot has content and trigger transition if it does
|
||||
*/
|
||||
private _checkSlotContent = (): void => {
|
||||
// Guard against multiple slotchange events triggering the transition multiple times
|
||||
if (this._transitionTriggered) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._slot) {
|
||||
const elements = this._slot.assignedElements();
|
||||
if (elements.length > 0) {
|
||||
this._transitionTriggered = true;
|
||||
this.onLoadTransition();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Automatically apply view transition on first render
|
||||
* @param changedProperties - Properties that changed
|
||||
*/
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
|
||||
// Wait for slotted content to be ready, then trigger transition
|
||||
// Only monitor the default (unnamed) slot - named slots are for specific purposes
|
||||
this._slot = this.shadowRoot?.querySelector("slot:not([name])") as
|
||||
| HTMLSlotElement
|
||||
| undefined;
|
||||
if (this._slot) {
|
||||
this._checkSlotContent();
|
||||
this._slot.addEventListener("slotchange", this._checkSlotContent);
|
||||
} else {
|
||||
// Start transition immediately if no slot is found
|
||||
this.onLoadTransition();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup event listeners when component is removed from the DOM.
|
||||
* Removes the slotchange listener.
|
||||
*/
|
||||
override disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
if (this._slot) {
|
||||
this._slot.removeEventListener("slotchange", this._checkSlotContent);
|
||||
this._slot = undefined;
|
||||
this._transitionTriggered = false;
|
||||
this._loaded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ViewTransitionClass;
|
||||
};
|
||||
@@ -1,9 +1,10 @@
|
||||
import { mdiDragHorizontalVariant, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, queryAll, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { ensureArray } from "../../../../common/array/ensure-array";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
@@ -15,19 +16,18 @@ import {
|
||||
ACTION_BUILDING_BLOCKS,
|
||||
getService,
|
||||
isService,
|
||||
VIRTUAL_ACTIONS,
|
||||
} from "../../../../data/action";
|
||||
import type { AutomationClipboard } from "../../../../data/automation";
|
||||
import type { Action } from "../../../../data/script";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
PASTE_VALUE,
|
||||
VIRTUAL_ACTIONS,
|
||||
showAddAutomationElementDialog,
|
||||
} from "../show-add-automation-element-dialog";
|
||||
import { automationRowsStyles } from "../styles";
|
||||
import type HaAutomationActionRow from "./ha-automation-action-row";
|
||||
import { getAutomationActionType } from "./ha-automation-action-row";
|
||||
import { ensureArray } from "../../../../common/array/ensure-array";
|
||||
|
||||
@customElement("ha-automation-action")
|
||||
export default class HaAutomationAction extends LitElement {
|
||||
@@ -136,17 +136,6 @@ export default class HaAutomationAction extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.add"
|
||||
)}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._addActionBuildingBlockDialog}
|
||||
appearance="plain"
|
||||
.size=${this.root ? "medium" : "small"}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.add_building_block"
|
||||
)}
|
||||
</ha-button>
|
||||
</div>
|
||||
</div>
|
||||
</ha-sortable>
|
||||
@@ -222,15 +211,6 @@ export default class HaAutomationAction extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _addActionBuildingBlockDialog() {
|
||||
showAddAutomationElementDialog(this, {
|
||||
type: "action",
|
||||
add: this._addAction,
|
||||
clipboardItem: getAutomationActionType(this._clipboard?.action),
|
||||
group: "building_blocks",
|
||||
});
|
||||
}
|
||||
|
||||
private _addAction = (action: string) => {
|
||||
let actions: Action[];
|
||||
if (action === PASTE_VALUE) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -214,17 +214,6 @@ export default class HaAutomationCondition extends LitElement {
|
||||
"ui.panel.config.automation.editor.conditions.add"
|
||||
)}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
.disabled=${this.disabled}
|
||||
appearance="plain"
|
||||
.size=${this.root ? "medium" : "small"}
|
||||
@click=${this._addConditionBuildingBlockDialog}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.add_building_block"
|
||||
)}
|
||||
</ha-button>
|
||||
</div>
|
||||
</div>
|
||||
</ha-sortable>
|
||||
@@ -242,15 +231,6 @@ export default class HaAutomationCondition extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _addConditionBuildingBlockDialog() {
|
||||
showAddAutomationElementDialog(this, {
|
||||
type: "condition",
|
||||
add: this._addCondition,
|
||||
clipboardItem: this._clipboard?.condition?.condition,
|
||||
group: "building_blocks",
|
||||
});
|
||||
}
|
||||
|
||||
private _addCondition = (value) => {
|
||||
let conditions: Condition[];
|
||||
if (value === PASTE_VALUE) {
|
||||
|
||||
@@ -36,7 +36,8 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
@property({ type: Number, attribute: "sidebar-key" })
|
||||
public sidebarKey?: number;
|
||||
|
||||
@state() private _yamlMode = false;
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
|
||||
@state() private _sidebarConfig?: SidebarConfig;
|
||||
|
||||
@state() private _sidebarKey?: string;
|
||||
@state() private _sidebarKey = 0;
|
||||
|
||||
@storage({
|
||||
key: "automation-sidebar-width",
|
||||
@@ -350,7 +350,9 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
// deselect previous selected row
|
||||
this._sidebarConfig?.close?.();
|
||||
this._sidebarConfig = ev.detail;
|
||||
this._sidebarKey = JSON.stringify(this._sidebarConfig);
|
||||
|
||||
// be sure the sidebar editor is recreated
|
||||
this._sidebarKey++;
|
||||
|
||||
await this._sidebarElement?.updateComplete;
|
||||
this._sidebarElement?.focus();
|
||||
@@ -375,6 +377,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
return;
|
||||
}
|
||||
this._sidebarConfig?.close();
|
||||
this._sidebarKey = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,45 +1,11 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { ACTION_GROUPS } from "../../../data/action";
|
||||
import type { ActionType } from "../../../data/script";
|
||||
|
||||
export const PASTE_VALUE = "__paste__";
|
||||
|
||||
// These will be replaced with the correct action
|
||||
export const VIRTUAL_ACTIONS: Record<
|
||||
keyof (typeof ACTION_GROUPS)["building_blocks"]["members"],
|
||||
ActionType
|
||||
> = {
|
||||
repeat_count: {
|
||||
repeat: {
|
||||
count: 2,
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
repeat_while: {
|
||||
repeat: {
|
||||
while: [],
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
repeat_until: {
|
||||
repeat: {
|
||||
until: [],
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
repeat_for_each: {
|
||||
repeat: {
|
||||
for_each: {},
|
||||
sequence: [],
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export interface AddAutomationElementDialogParams {
|
||||
type: "trigger" | "condition" | "action";
|
||||
add: (key: string) => void;
|
||||
clipboardItem: string | undefined;
|
||||
group?: string;
|
||||
}
|
||||
const loadDialog = () => import("./add-automation-element-dialog");
|
||||
|
||||
|
||||
@@ -44,7 +44,8 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
@property({ type: Number, attribute: "sidebar-key" })
|
||||
public sidebarKey?: number;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
|
||||
@@ -44,7 +44,8 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
@property({ type: Number, attribute: "sidebar-key" })
|
||||
public sidebarKey?: number;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
@property({ type: Number, attribute: "sidebar-key" })
|
||||
public sidebarKey?: number;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
@property({ type: Number, attribute: "sidebar-key" })
|
||||
public sidebarKey?: number;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
@property({ type: Number, attribute: "sidebar-key" })
|
||||
public sidebarKey?: number;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
|
||||
@@ -115,8 +115,8 @@ export class HaConfigLabels extends LitElement {
|
||||
borderRadius: "var(--ha-border-radius-md)",
|
||||
border: "1px solid var(--outline-color)",
|
||||
boxSizing: "border-box",
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
width: "var(--ha-space-5)",
|
||||
height: "var(--ha-space-5)",
|
||||
})}
|
||||
></div>`
|
||||
: nothing,
|
||||
|
||||
@@ -89,7 +89,7 @@ export class HaManualScriptEditor extends LitElement {
|
||||
|
||||
@state() private _sidebarConfig?: SidebarConfig;
|
||||
|
||||
@state() private _sidebarKey?: string;
|
||||
@state() private _sidebarKey = 0;
|
||||
|
||||
@storage({
|
||||
key: "automation-sidebar-width",
|
||||
@@ -512,7 +512,9 @@ export class HaManualScriptEditor extends LitElement {
|
||||
// deselect previous selected row
|
||||
this._sidebarConfig?.close?.();
|
||||
this._sidebarConfig = ev.detail;
|
||||
this._sidebarKey = JSON.stringify(this._sidebarConfig);
|
||||
|
||||
// be sure the sidebar editor is recreated
|
||||
this._sidebarKey++;
|
||||
|
||||
await this._sidebarElement?.updateComplete;
|
||||
this._sidebarElement?.focus();
|
||||
@@ -537,6 +539,7 @@ export class HaManualScriptEditor extends LitElement {
|
||||
return;
|
||||
}
|
||||
this._sidebarConfig?.close();
|
||||
this._sidebarKey = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-control-number-buttons";
|
||||
import { isUnavailableState } from "../../../data/entity";
|
||||
import {
|
||||
MediaPlayerEntityFeature,
|
||||
type MediaPlayerEntity,
|
||||
} from "../../../data/media-player";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
|
||||
import { cardFeatureStyles } from "./common/card-feature-styles";
|
||||
import type {
|
||||
LovelaceCardFeatureContext,
|
||||
MediaPlayerVolumeButtonsCardFeatureConfig,
|
||||
} from "./types";
|
||||
import { clamp } from "../../../common/number/clamp";
|
||||
|
||||
export const supportsMediaPlayerVolumeButtonsCardFeature = (
|
||||
hass: HomeAssistant,
|
||||
context: LovelaceCardFeatureContext
|
||||
) => {
|
||||
const stateObj = context.entity_id
|
||||
? hass.states[context.entity_id]
|
||||
: undefined;
|
||||
if (!stateObj) return false;
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
return (
|
||||
domain === "media_player" &&
|
||||
supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_SET)
|
||||
);
|
||||
};
|
||||
|
||||
@customElement("hui-media-player-volume-buttons-card-feature")
|
||||
class HuiMediaPlayerVolumeButtonsCardFeature
|
||||
extends LitElement
|
||||
implements LovelaceCardFeature
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
|
||||
|
||||
@state() private _config?: MediaPlayerVolumeButtonsCardFeatureConfig;
|
||||
|
||||
private get _stateObj() {
|
||||
if (!this.hass || !this.context || !this.context.entity_id) {
|
||||
return undefined;
|
||||
}
|
||||
return this.hass.states[this.context.entity_id] as
|
||||
| MediaPlayerEntity
|
||||
| undefined;
|
||||
}
|
||||
|
||||
static getStubConfig(): MediaPlayerVolumeButtonsCardFeatureConfig {
|
||||
return {
|
||||
type: "media-player-volume-buttons",
|
||||
step: 5,
|
||||
};
|
||||
}
|
||||
|
||||
public static async getConfigElement(): Promise<LovelaceCardFeatureEditor> {
|
||||
await import(
|
||||
"../editor/config-elements/hui-media-player-volume-buttons-card-feature-editor"
|
||||
);
|
||||
return document.createElement(
|
||||
"hui-media-player-volume-buttons-card-feature-editor"
|
||||
);
|
||||
}
|
||||
|
||||
public setConfig(config: MediaPlayerVolumeButtonsCardFeatureConfig): void {
|
||||
if (!config) {
|
||||
throw new Error("Invalid configuration");
|
||||
}
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (
|
||||
!this._config ||
|
||||
!this.hass ||
|
||||
!this.context ||
|
||||
!this._stateObj ||
|
||||
!supportsMediaPlayerVolumeButtonsCardFeature(this.hass, this.context)
|
||||
) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const position =
|
||||
this._stateObj.attributes.volume_level != null
|
||||
? Math.round(this._stateObj.attributes.volume_level * 100)
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
<ha-control-number-buttons
|
||||
.disabled=${!this._stateObj || isUnavailableState(this._stateObj.state)}
|
||||
.locale=${this.hass.locale}
|
||||
min="0"
|
||||
max="100"
|
||||
.step=${this._config.step ?? 5}
|
||||
.value=${position}
|
||||
unit="%"
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-control-number-buttons>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.hass!.callService("media_player", "volume_set", {
|
||||
entity_id: this._stateObj!.entity_id,
|
||||
volume_level: clamp(ev.detail.value, 0, 100) / 100,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return cardFeatureStyles;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-media-player-volume-buttons-card-feature": HuiMediaPlayerVolumeButtonsCardFeature;
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,11 @@ export interface MediaPlayerVolumeSliderCardFeatureConfig {
|
||||
type: "media-player-volume-slider";
|
||||
}
|
||||
|
||||
export interface MediaPlayerVolumeButtonsCardFeatureConfig {
|
||||
type: "media-player-volume-buttons";
|
||||
step?: number;
|
||||
}
|
||||
|
||||
export interface FanDirectionCardFeatureConfig {
|
||||
type: "fan-direction";
|
||||
}
|
||||
@@ -252,6 +257,7 @@ export type LovelaceCardFeatureConfig =
|
||||
| LockCommandsCardFeatureConfig
|
||||
| LockOpenDoorCardFeatureConfig
|
||||
| MediaPlayerPlaybackCardFeatureConfig
|
||||
| MediaPlayerVolumeButtonsCardFeatureConfig
|
||||
| MediaPlayerVolumeSliderCardFeatureConfig
|
||||
| NumericInputCardFeatureConfig
|
||||
| SelectOptionsCardFeatureConfig
|
||||
|
||||
@@ -23,6 +23,7 @@ import "../card-features/hui-light-color-temp-card-feature";
|
||||
import "../card-features/hui-lock-commands-card-feature";
|
||||
import "../card-features/hui-lock-open-door-card-feature";
|
||||
import "../card-features/hui-media-player-playback-card-feature";
|
||||
import "../card-features/hui-media-player-volume-buttons-card-feature";
|
||||
import "../card-features/hui-media-player-volume-slider-card-feature";
|
||||
import "../card-features/hui-numeric-input-card-feature";
|
||||
import "../card-features/hui-select-options-card-feature";
|
||||
@@ -72,6 +73,7 @@ const TYPES = new Set<LovelaceCardFeatureConfig["type"]>([
|
||||
"lock-commands",
|
||||
"lock-open-door",
|
||||
"media-player-playback",
|
||||
"media-player-volume-buttons",
|
||||
"media-player-volume-slider",
|
||||
"numeric-input",
|
||||
"select-options",
|
||||
|
||||
@@ -48,6 +48,7 @@ import { supportsLightColorTempCardFeature } from "../../card-features/hui-light
|
||||
import { supportsLockCommandsCardFeature } from "../../card-features/hui-lock-commands-card-feature";
|
||||
import { supportsLockOpenDoorCardFeature } from "../../card-features/hui-lock-open-door-card-feature";
|
||||
import { supportsMediaPlayerPlaybackCardFeature } from "../../card-features/hui-media-player-playback-card-feature";
|
||||
import { supportsMediaPlayerVolumeButtonsCardFeature } from "../../card-features/hui-media-player-volume-buttons-card-feature";
|
||||
import { supportsMediaPlayerVolumeSliderCardFeature } from "../../card-features/hui-media-player-volume-slider-card-feature";
|
||||
import { supportsNumericInputCardFeature } from "../../card-features/hui-numeric-input-card-feature";
|
||||
import { supportsSelectOptionsCardFeature } from "../../card-features/hui-select-options-card-feature";
|
||||
@@ -102,6 +103,7 @@ const UI_FEATURE_TYPES = [
|
||||
"lock-commands",
|
||||
"lock-open-door",
|
||||
"media-player-playback",
|
||||
"media-player-volume-buttons",
|
||||
"media-player-volume-slider",
|
||||
"numeric-input",
|
||||
"select-options",
|
||||
@@ -131,6 +133,7 @@ const EDITABLES_FEATURE_TYPES = new Set<UiFeatureTypes>([
|
||||
"fan-preset-modes",
|
||||
"humidifier-modes",
|
||||
"lawn-mower-commands",
|
||||
"media-player-volume-buttons",
|
||||
"numeric-input",
|
||||
"select-options",
|
||||
"trend-graph",
|
||||
@@ -171,6 +174,7 @@ const SUPPORTS_FEATURE_TYPES: Record<
|
||||
"lock-commands": supportsLockCommandsCardFeature,
|
||||
"lock-open-door": supportsLockOpenDoorCardFeature,
|
||||
"media-player-playback": supportsMediaPlayerPlaybackCardFeature,
|
||||
"media-player-volume-buttons": supportsMediaPlayerVolumeButtonsCardFeature,
|
||||
"media-player-volume-slider": supportsMediaPlayerVolumeSliderCardFeature,
|
||||
"numeric-input": supportsNumericInputCardFeature,
|
||||
"select-options": supportsSelectOptionsCardFeature,
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type {
|
||||
LovelaceCardFeatureContext,
|
||||
MediaPlayerVolumeButtonsCardFeatureConfig,
|
||||
} from "../../card-features/types";
|
||||
import type { LovelaceCardFeatureEditor } from "../../types";
|
||||
|
||||
@customElement("hui-media-player-volume-buttons-card-feature-editor")
|
||||
export class HuiMediaPlayerVolumeButtonsCardFeatureEditor
|
||||
extends LitElement
|
||||
implements LovelaceCardFeatureEditor
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
|
||||
|
||||
@state() private _config?: MediaPlayerVolumeButtonsCardFeatureConfig;
|
||||
|
||||
public setConfig(config: MediaPlayerVolumeButtonsCardFeatureConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
private _schema = memoizeOne(
|
||||
() =>
|
||||
[
|
||||
{
|
||||
name: "step",
|
||||
selector: {
|
||||
number: {
|
||||
mode: "slider",
|
||||
step: 1,
|
||||
min: 1,
|
||||
max: 100,
|
||||
unit_of_measurement: "%",
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this.hass || !this._config) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const data: MediaPlayerVolumeButtonsCardFeatureConfig = {
|
||||
type: "media-player-volume-buttons",
|
||||
step: this._config.step ?? 5,
|
||||
};
|
||||
|
||||
const schema = this._schema();
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${data}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
) =>
|
||||
this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.features.types.media-player-volume-buttons.${schema.name}`
|
||||
);
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-media-player-volume-buttons-card-feature-editor": HuiMediaPlayerVolumeButtonsCardFeatureEditor;
|
||||
}
|
||||
}
|
||||
@@ -72,8 +72,7 @@ import {
|
||||
} from "../../dialogs/quick-bar/show-dialog-quick-bar";
|
||||
import { showShortcutsDialog } from "../../dialogs/shortcuts/show-shortcuts-dialog";
|
||||
import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-ha-voice-command-dialog";
|
||||
import { ViewTransitionMixin } from "../../mixins/view-transition-mixin";
|
||||
import { haStyle, haStyleViewTransitions } from "../../resources/styles";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant, PanelInfo } from "../../types";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import { showToast } from "../../util/toast";
|
||||
@@ -115,7 +114,7 @@ interface SubActionItem {
|
||||
}
|
||||
|
||||
@customElement("hui-root")
|
||||
class HUIRoot extends ViewTransitionMixin(LitElement) {
|
||||
class HUIRoot extends LitElement {
|
||||
@property({ attribute: false }) public panel?: PanelInfo<LovelacePanelConfig>;
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -497,7 +496,6 @@ class HUIRoot extends ViewTransitionMixin(LitElement) {
|
||||
class=${classMap({
|
||||
"edit-mode": this._editMode,
|
||||
narrow: this.narrow,
|
||||
loading: !this._loaded,
|
||||
})}
|
||||
>
|
||||
<div class="header">
|
||||
@@ -1167,45 +1165,43 @@ class HUIRoot extends ViewTransitionMixin(LitElement) {
|
||||
// Recreate a new element to clear the applied themes.
|
||||
const root = this._viewRoot;
|
||||
|
||||
this.startViewTransition(() => {
|
||||
if (root.lastChild) {
|
||||
root.removeChild(root.lastChild);
|
||||
}
|
||||
if (root.lastChild) {
|
||||
root.removeChild(root.lastChild);
|
||||
}
|
||||
|
||||
if (viewIndex === "hass-unused-entities") {
|
||||
const unusedEntities = document.createElement("hui-unused-entities");
|
||||
// Wait for promise to resolve so that the element has been upgraded.
|
||||
import("./editor/unused-entities/hui-unused-entities").then(() => {
|
||||
unusedEntities.hass = this.hass!;
|
||||
unusedEntities.lovelace = this.lovelace!;
|
||||
unusedEntities.narrow = this.narrow;
|
||||
});
|
||||
root.appendChild(unusedEntities);
|
||||
return;
|
||||
}
|
||||
if (viewIndex === "hass-unused-entities") {
|
||||
const unusedEntities = document.createElement("hui-unused-entities");
|
||||
// Wait for promise to resolve so that the element has been upgraded.
|
||||
import("./editor/unused-entities/hui-unused-entities").then(() => {
|
||||
unusedEntities.hass = this.hass!;
|
||||
unusedEntities.lovelace = this.lovelace!;
|
||||
unusedEntities.narrow = this.narrow;
|
||||
});
|
||||
root.appendChild(unusedEntities);
|
||||
return;
|
||||
}
|
||||
|
||||
let view;
|
||||
const viewConfig = this.config.views[viewIndex];
|
||||
let view;
|
||||
const viewConfig = this.config.views[viewIndex];
|
||||
|
||||
if (!viewConfig) {
|
||||
this.lovelace!.setEditMode(true);
|
||||
return;
|
||||
}
|
||||
if (!viewConfig) {
|
||||
this.lovelace!.setEditMode(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!force && this._viewCache![viewIndex]) {
|
||||
view = this._viewCache![viewIndex];
|
||||
} else {
|
||||
view = document.createElement("hui-view");
|
||||
view.index = viewIndex;
|
||||
this._viewCache![viewIndex] = view;
|
||||
}
|
||||
if (!force && this._viewCache![viewIndex]) {
|
||||
view = this._viewCache![viewIndex];
|
||||
} else {
|
||||
view = document.createElement("hui-view");
|
||||
view.index = viewIndex;
|
||||
this._viewCache![viewIndex] = view;
|
||||
}
|
||||
|
||||
view.lovelace = this.lovelace;
|
||||
view.hass = this.hass;
|
||||
view.narrow = this.narrow;
|
||||
view.lovelace = this.lovelace;
|
||||
view.hass = this.hass;
|
||||
view.narrow = this.narrow;
|
||||
|
||||
root.appendChild(view);
|
||||
});
|
||||
root.appendChild(view);
|
||||
}
|
||||
|
||||
private _openShortcutDialog(ev: Event) {
|
||||
@@ -1216,21 +1212,12 @@ class HUIRoot extends ViewTransitionMixin(LitElement) {
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
haStyleViewTransitions,
|
||||
css`
|
||||
:host {
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
::view-transition-new(hui-root-container) {
|
||||
animation: fade-in var(--ha-animation-layout-duration) ease-out;
|
||||
animation-delay: var(--ha-animation-layout-delay-base);
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: var(--app-header-background-color);
|
||||
color: var(--app-header-text-color, white);
|
||||
@@ -1419,10 +1406,6 @@ class HUIRoot extends ViewTransitionMixin(LitElement) {
|
||||
padding-right: var(--safe-area-inset-right);
|
||||
padding-inline-end: var(--safe-area-inset-right);
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
view-transition-name: hui-root-container;
|
||||
}
|
||||
.loading hui-view-container {
|
||||
opacity: 0;
|
||||
}
|
||||
.narrow hui-view-container {
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
@@ -1431,7 +1414,6 @@ class HUIRoot extends ViewTransitionMixin(LitElement) {
|
||||
hui-view-container > * {
|
||||
flex: 1 1 100%;
|
||||
max-width: 100%;
|
||||
view-transition-name: layout-fade-in;
|
||||
}
|
||||
/**
|
||||
* In edit mode we have the tab bar on a new line *
|
||||
|
||||
@@ -199,56 +199,3 @@ export const baseEntrypointStyles = css`
|
||||
width: 100vw;
|
||||
}
|
||||
`;
|
||||
|
||||
export const haStyleViewTransitions = css`
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-out {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
/* Prevent root cross-fade during view transitions (pseudo-element) */
|
||||
::view-transition-old(root) {
|
||||
animation: none;
|
||||
}
|
||||
::view-transition-new(root) {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
/* Elements leaving the view (loading screen) */
|
||||
::view-transition-group(layout-fade-out) {
|
||||
animation-duration: var(--ha-animation-layout-duration);
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
::view-transition-old(layout-fade-out) {
|
||||
animation: fade-out var(--ha-animation-layout-duration) ease-out;
|
||||
}
|
||||
::view-transition-new(layout-fade-out) {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
/* New content entering (panels, subpages)
|
||||
Uses base delay to be less abrupt and allow for elements to render */
|
||||
::view-transition-group(layout-fade-in) {
|
||||
animation-duration: var(--ha-animation-layout-duration);
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
::view-transition-new(layout-fade-in) {
|
||||
animation: fade-in var(--ha-animation-layout-duration) ease-out;
|
||||
animation-delay: var(--ha-animation-layout-delay-base);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -42,17 +42,6 @@ export const coreStyles = css`
|
||||
--ha-space-18: 72px;
|
||||
--ha-space-19: 76px;
|
||||
--ha-space-20: 80px;
|
||||
|
||||
/* Animation timing */
|
||||
--ha-animation-layout-duration: 350ms;
|
||||
--ha-animation-layout-delay-base: 100ms;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html {
|
||||
--ha-animation-layout-duration: 0ms;
|
||||
--ha-animation-layout-delay-base: 0ms;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -676,6 +676,9 @@
|
||||
},
|
||||
"entity-state-picker": {
|
||||
"state": "State"
|
||||
},
|
||||
"entity-state-content-picker": {
|
||||
"add": "Add"
|
||||
}
|
||||
},
|
||||
"target-picker": {
|
||||
@@ -3915,7 +3918,6 @@
|
||||
"edit_yaml": "Edit in YAML",
|
||||
"edit_ui": "Edit in visual editor",
|
||||
"copy_to_clipboard": "Copy to clipboard",
|
||||
"search_in": "Search · {group}",
|
||||
"unknown_entity": "unknown entity",
|
||||
"edit_unknown_device": "Editor not available for unknown device",
|
||||
"switch_ui_yaml_error": "There are currently YAML errors in the automation, and it cannot be parsed. Switching to UI mode may cause pending changes to be lost. Press cancel to correct any errors before proceeding to prevent loss of pending changes, or continue if you are sure.",
|
||||
@@ -3930,6 +3932,7 @@
|
||||
"item_pasted": "{item} pasted",
|
||||
"ctrl": "Ctrl",
|
||||
"del": "Del",
|
||||
"blocks": "Blocks",
|
||||
"triggers": {
|
||||
"name": "Triggers",
|
||||
"header": "When",
|
||||
@@ -3937,7 +3940,7 @@
|
||||
"learn_more": "Learn more about triggers",
|
||||
"triggered": "Triggered",
|
||||
"add": "Add trigger",
|
||||
"search": "Search trigger",
|
||||
"empty_search": "No triggers found for {term}",
|
||||
"id": "Trigger ID",
|
||||
"id_helper": "Helps identify each run based on which trigger fired.",
|
||||
"optional": "Optional",
|
||||
@@ -3958,14 +3961,16 @@
|
||||
"trigger": "Trigger",
|
||||
"copied_to_clipboard": "Trigger copied to clipboard",
|
||||
"cut_to_clipboard": "Trigger cut to clipboard",
|
||||
"select": "Select a trigger",
|
||||
"groups": {
|
||||
"device": {
|
||||
"label": "Device"
|
||||
},
|
||||
"entity": {
|
||||
"label": "Entity",
|
||||
"description": "When something happens to an entity."
|
||||
"label": "Entity"
|
||||
},
|
||||
"time_location": {
|
||||
"label": "Time and location",
|
||||
"description": "When someone enters or leaves a zone, or at a specific time."
|
||||
"label": "Time and location"
|
||||
},
|
||||
"other": {
|
||||
"label": "Other triggers"
|
||||
@@ -4198,7 +4203,7 @@
|
||||
"description": "All conditions added here need to be satisfied for the automation to run. A condition can be satisfied or not at any given time, for example: ''If {user} is home''. You can use building blocks to create more complex conditions.",
|
||||
"learn_more": "Learn more about conditions",
|
||||
"add": "Add condition",
|
||||
"search": "Search condition",
|
||||
"empty_search": "No conditions and blocks found for {term}",
|
||||
"add_building_block": "Add building block",
|
||||
"test": "Test",
|
||||
"testing_error": "Condition did not pass",
|
||||
@@ -4220,21 +4225,22 @@
|
||||
"condition": "Condition",
|
||||
"copied_to_clipboard": "Condition copied to clipboard",
|
||||
"cut_to_clipboard": "Condition cut to clipboard",
|
||||
"select": "Select a condition",
|
||||
"groups": {
|
||||
"device": {
|
||||
"label": "Device"
|
||||
},
|
||||
"entity": {
|
||||
"label": "Entity",
|
||||
"description": "If an entity is in a specific state."
|
||||
"label": "Entity"
|
||||
},
|
||||
"time_location": {
|
||||
"label": "Time and location",
|
||||
"description": "If someone is in a zone or if the current time is before or after a specified time."
|
||||
"label": "Time and location"
|
||||
},
|
||||
"other": {
|
||||
"label": "Other conditions"
|
||||
},
|
||||
"building_blocks": {
|
||||
"label": "Building blocks",
|
||||
"description": "Build more complex conditions."
|
||||
"label": "Building blocks"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
@@ -4365,7 +4371,7 @@
|
||||
"description": "All actions added here will be performed in sequence when the automation runs. An action usually controls one of your areas, devices, or entities, for example: 'Turn on the lights'. You can use building blocks to create more complex sequences of actions.",
|
||||
"learn_more": "Learn more about actions",
|
||||
"add": "Add action",
|
||||
"search": "Search action",
|
||||
"empty_search": "No actions and blocks found for {term}",
|
||||
"add_building_block": "Add building block",
|
||||
"invalid_action": "Invalid action",
|
||||
"run": "Run action",
|
||||
@@ -4389,7 +4395,11 @@
|
||||
"action": "Action",
|
||||
"copied_to_clipboard": "Action copied to clipboard",
|
||||
"cut_to_clipboard": "Action cut to clipboard",
|
||||
"select": "Select an action",
|
||||
"groups": {
|
||||
"device_id": {
|
||||
"label": "Device"
|
||||
},
|
||||
"helpers": {
|
||||
"label": "Helpers"
|
||||
},
|
||||
@@ -4397,8 +4407,7 @@
|
||||
"label": "Other actions"
|
||||
},
|
||||
"building_blocks": {
|
||||
"label": "Building blocks",
|
||||
"description": "Build more complex sequences of actions."
|
||||
"label": "Building blocks"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
@@ -8142,6 +8151,10 @@
|
||||
"media-player-playback": {
|
||||
"label": "Media player playback controls"
|
||||
},
|
||||
"media-player-volume-buttons": {
|
||||
"label": "Media player volume buttons",
|
||||
"step": "Step size"
|
||||
},
|
||||
"media-player-volume-slider": {
|
||||
"label": "Media player volume slider"
|
||||
},
|
||||
|
||||
@@ -3,21 +3,8 @@ import { render } from "lit";
|
||||
|
||||
export const removeLaunchScreen = () => {
|
||||
const launchScreenElement = document.getElementById("ha-launch-screen");
|
||||
if (!launchScreenElement?.parentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use View Transition API if available and user doesn't prefer reduced motion
|
||||
if (
|
||||
document.startViewTransition &&
|
||||
!window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
||||
) {
|
||||
document.startViewTransition(() => {
|
||||
launchScreenElement.parentElement?.removeChild(launchScreenElement);
|
||||
});
|
||||
} else {
|
||||
// Fallback: Direct removal without transition
|
||||
launchScreenElement.parentElement.removeChild(launchScreenElement);
|
||||
if (launchScreenElement) {
|
||||
launchScreenElement.parentElement!.removeChild(launchScreenElement);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ describe("floorCompare", () => {
|
||||
});
|
||||
|
||||
describe("floorCompare(entries)", () => {
|
||||
it("sorts by level, then by name", () => {
|
||||
it("sorts by level descending (highest to lowest), then by name", () => {
|
||||
const entries = {
|
||||
floor1: { name: "Ground Floor", level: 0 } as FloorRegistryEntry,
|
||||
floor2: { name: "First Floor", level: 1 } as FloorRegistryEntry,
|
||||
@@ -35,13 +35,13 @@ describe("floorCompare", () => {
|
||||
const floors = ["floor1", "floor2", "floor3"];
|
||||
|
||||
expect(floors.sort(floorCompare(entries))).toEqual([
|
||||
"floor3",
|
||||
"floor1",
|
||||
"floor2",
|
||||
"floor1",
|
||||
"floor3",
|
||||
]);
|
||||
});
|
||||
|
||||
it("treats null level as 9999, placing it at the end", () => {
|
||||
it("treats null level as -9999, placing it at the end", () => {
|
||||
const entries = {
|
||||
floor1: { name: "Ground Floor", level: 0 } as FloorRegistryEntry,
|
||||
floor2: { name: "First Floor", level: 1 } as FloorRegistryEntry,
|
||||
@@ -50,8 +50,8 @@ describe("floorCompare", () => {
|
||||
const floors = ["floor2", "floor3", "floor1"];
|
||||
|
||||
expect(floors.sort(floorCompare(entries))).toEqual([
|
||||
"floor1",
|
||||
"floor2",
|
||||
"floor1",
|
||||
"floor3",
|
||||
]);
|
||||
});
|
||||
|
||||
156
yarn.lock
156
yarn.lock
@@ -4966,106 +4966,106 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/eslint-plugin@npm:8.46.1":
|
||||
version: 8.46.1
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.46.1"
|
||||
"@typescript-eslint/eslint-plugin@npm:8.46.2":
|
||||
version: 8.46.2
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.46.2"
|
||||
dependencies:
|
||||
"@eslint-community/regexpp": "npm:^4.10.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.46.1"
|
||||
"@typescript-eslint/type-utils": "npm:8.46.1"
|
||||
"@typescript-eslint/utils": "npm:8.46.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.46.1"
|
||||
"@typescript-eslint/scope-manager": "npm:8.46.2"
|
||||
"@typescript-eslint/type-utils": "npm:8.46.2"
|
||||
"@typescript-eslint/utils": "npm:8.46.2"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.46.2"
|
||||
graphemer: "npm:^1.4.0"
|
||||
ignore: "npm:^7.0.0"
|
||||
natural-compare: "npm:^1.4.0"
|
||||
ts-api-utils: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
"@typescript-eslint/parser": ^8.46.1
|
||||
"@typescript-eslint/parser": ^8.46.2
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/9fd8c279584e11c7dcfcac6dddc4dde8719f8fe79349f5a2d0473ffcee198dd543a5311b24c601228ae03cc1a47b29118261bcf45f7f697c8ba1e4289fda4096
|
||||
checksum: 10/00c659fcc04c185e6cdfb6c7e52beae1935f1475fef4079193a719f93858b6255e07b4764fc7104e9524a4d0b7652e63616b93e7f112f1cba4e983d10383e224
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/parser@npm:8.46.1":
|
||||
version: 8.46.1
|
||||
resolution: "@typescript-eslint/parser@npm:8.46.1"
|
||||
"@typescript-eslint/parser@npm:8.46.2":
|
||||
version: 8.46.2
|
||||
resolution: "@typescript-eslint/parser@npm:8.46.2"
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager": "npm:8.46.1"
|
||||
"@typescript-eslint/types": "npm:8.46.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.46.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.46.1"
|
||||
"@typescript-eslint/scope-manager": "npm:8.46.2"
|
||||
"@typescript-eslint/types": "npm:8.46.2"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.46.2"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.46.2"
|
||||
debug: "npm:^4.3.4"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/4edcb49bb001e9a0e72155c4181f941be00c603bf277c283d4185dca528e9642da927032e8d2671c444ca1904c7f51743029b4b48c12e94d39df2dac49d7d3ff
|
||||
checksum: 10/2ee394d880b5a9372ecf50ddbf70f66e9ecc16691a210dd40b5b152310a539005dfed13105e0adc81f1a9f49d86f7b78ddf3bf8d777fe84c179eb6a8be2fa56c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/project-service@npm:8.46.1":
|
||||
version: 8.46.1
|
||||
resolution: "@typescript-eslint/project-service@npm:8.46.1"
|
||||
"@typescript-eslint/project-service@npm:8.46.2":
|
||||
version: 8.46.2
|
||||
resolution: "@typescript-eslint/project-service@npm:8.46.2"
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.46.1"
|
||||
"@typescript-eslint/types": "npm:^8.46.1"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.46.2"
|
||||
"@typescript-eslint/types": "npm:^8.46.2"
|
||||
debug: "npm:^4.3.4"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/d63cbb88524be85ba626c4969bdec1cd5c1ab64b6ebdd565a45698e700efb764f192db1cdc3322f4d63d3acd8d0a36e2685b89bdfa2edf50fda3c2d0cb6efdd7
|
||||
checksum: 10/76ba446f86e83b4afd6dacbebc9a0737b5a3e0500a0712b37fea4f0141dcf4c9238e8e5a9a649cf609a4624cc575431506a2a56432aaa18d4c3a8cf2df9d1480
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/scope-manager@npm:8.46.1":
|
||||
version: 8.46.1
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.46.1"
|
||||
"@typescript-eslint/scope-manager@npm:8.46.2":
|
||||
version: 8.46.2
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.46.2"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.46.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.46.1"
|
||||
checksum: 10/3d73812087a17be84184cc68143d4dca7602b8cd4bf5ad334e541d4b3acf5c65c58935369dcf66ab81b38014fe0c6bc57ac2f655fdd69b3e24161a827b86bd34
|
||||
"@typescript-eslint/types": "npm:8.46.2"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.46.2"
|
||||
checksum: 10/6a8a9b644ff57ca9e992348553f19f6e010d76ff4872d972d333a16952e93cce4bf5096a1fefe1af8b452bce963fde6c78410d15817e673b75176ec3241949e9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.46.1, @typescript-eslint/tsconfig-utils@npm:^8.46.1":
|
||||
version: 8.46.1
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.46.1"
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.46.2, @typescript-eslint/tsconfig-utils@npm:^8.46.2":
|
||||
version: 8.46.2
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.46.2"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/f033d68a53f62c7cc4c09e5697dd9b7fa34a3c3e79133e0b14ca582821869b77e81d3942b91535f6ef789ffaaad31eef1e1ace20518e7de0935a55a16120fae7
|
||||
checksum: 10/e459d131ca646cca6ad164593ca7e8c45ad3daa103a24e1e57fd47b5c1e5b5418948b749f02baa42e61103a496fc80d32ddd1841c11495bbcf37808b88bb0ef4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/type-utils@npm:8.46.1":
|
||||
version: 8.46.1
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.46.1"
|
||||
"@typescript-eslint/type-utils@npm:8.46.2":
|
||||
version: 8.46.2
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.46.2"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.46.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.46.1"
|
||||
"@typescript-eslint/utils": "npm:8.46.1"
|
||||
"@typescript-eslint/types": "npm:8.46.2"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.46.2"
|
||||
"@typescript-eslint/utils": "npm:8.46.2"
|
||||
debug: "npm:^4.3.4"
|
||||
ts-api-utils: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/db989c1f55624b34da24eaf0dc230ee696a1f2a614ea95a8dd3b8635ad47d748140be2345ed7afcee844dfabd41129f5a8ca583b1a4d6ecc7d581f89c5e508e2
|
||||
checksum: 10/db5d3d782b44d31f828ebdbec44550c6f94fdcfac1164f59e3922f6413feed749d93df3977625fd5949aaff5c691cf4603a7cd93eaf7b19b9cf6fd91537fb8c7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:8.46.1, @typescript-eslint/types@npm:^8.46.1":
|
||||
version: 8.46.1
|
||||
resolution: "@typescript-eslint/types@npm:8.46.1"
|
||||
checksum: 10/d162ddf6d77d8c9bdfca942da5de5fb4ba80efa740b14077482b5a71282f1d05e1b1dd393ae810eb2923ca9c845bd26b4a9d2dbf25d43dd5d9cb6e20c2a1db46
|
||||
"@typescript-eslint/types@npm:8.46.2, @typescript-eslint/types@npm:^8.46.2":
|
||||
version: 8.46.2
|
||||
resolution: "@typescript-eslint/types@npm:8.46.2"
|
||||
checksum: 10/c641453c868b730ef64bd731cc47b19e1a5e45c090dfe9542ecd15b24c5a7b6dc94a8ef4e548b976aabcd1ca9dec1b766e417454b98ea59079795eb008226b38
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:8.46.1":
|
||||
version: 8.46.1
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.46.1"
|
||||
"@typescript-eslint/typescript-estree@npm:8.46.2":
|
||||
version: 8.46.2
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.46.2"
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service": "npm:8.46.1"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.46.1"
|
||||
"@typescript-eslint/types": "npm:8.46.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.46.1"
|
||||
"@typescript-eslint/project-service": "npm:8.46.2"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.46.2"
|
||||
"@typescript-eslint/types": "npm:8.46.2"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.46.2"
|
||||
debug: "npm:^4.3.4"
|
||||
fast-glob: "npm:^3.3.2"
|
||||
is-glob: "npm:^4.0.3"
|
||||
@@ -5074,32 +5074,32 @@ __metadata:
|
||||
ts-api-utils: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/af068a14d6d0b4849e9f0e52b7ddcd24c266f099528c7b62ff2bebebc0fb82d07439bf6dc565b27cf2fed0af0aaae618aae220676d0fb041c93ec2a8163f0da1
|
||||
checksum: 10/4d2149ad97e7f7e2e4cf466932f52f38e90414d47341c5938e497fd0826d403db9896bbd5cc08e7488ad0d0ffb3817e6f18e9f0c623d8a8cda09af204f81aab8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/utils@npm:8.46.1":
|
||||
version: 8.46.1
|
||||
resolution: "@typescript-eslint/utils@npm:8.46.1"
|
||||
"@typescript-eslint/utils@npm:8.46.2":
|
||||
version: 8.46.2
|
||||
resolution: "@typescript-eslint/utils@npm:8.46.2"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": "npm:^4.7.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.46.1"
|
||||
"@typescript-eslint/types": "npm:8.46.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.46.1"
|
||||
"@typescript-eslint/scope-manager": "npm:8.46.2"
|
||||
"@typescript-eslint/types": "npm:8.46.2"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.46.2"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/a8fed8aebd34a559c5abd780649edd6be632531e4930b19642f0fdc862b77bff463ef200e8ced48ba489c3fceee7443b6735c87b918b97b98e95e842cd8a38b5
|
||||
checksum: 10/91f6216f858161c3f59b2e035e0abce68fcdc9fbe45cb693a111c11ce5352c42fe0b1145a91e538c5459ff81b5e3741a4b38189b97e0e1a756567b6467c7b6c9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:8.46.1":
|
||||
version: 8.46.1
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.46.1"
|
||||
"@typescript-eslint/visitor-keys@npm:8.46.2":
|
||||
version: 8.46.2
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.46.2"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.46.1"
|
||||
"@typescript-eslint/types": "npm:8.46.2"
|
||||
eslint-visitor-keys: "npm:^4.2.1"
|
||||
checksum: 10/eed1c5ce08d2743bd2ec95a33f2118a67596b1b9fa5bf6a3d84ed09ca66e09af3cc91ef3e302c2222e5882e13576340532b586030b3652ce046eb218cd4508b7
|
||||
checksum: 10/4352629a33bc1619dc78d55eaec382be4c7e1059af02660f62bfdb22933021deaf98504d4030b8db74ec122e6d554e9015341f87aed729fb70fae613f12f55a4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9422,7 +9422,7 @@ __metadata:
|
||||
leaflet: "npm:1.9.4"
|
||||
leaflet-draw: "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
leaflet.markercluster: "npm:1.5.3"
|
||||
lint-staged: "npm:16.2.4"
|
||||
lint-staged: "npm:16.2.5"
|
||||
lit: "npm:3.3.1"
|
||||
lit-analyzer: "npm:2.0.3"
|
||||
lit-html: "npm:3.3.1"
|
||||
@@ -9452,7 +9452,7 @@ __metadata:
|
||||
tinykeys: "npm:3.0.0"
|
||||
ts-lit-plugin: "npm:2.0.2"
|
||||
typescript: "npm:5.9.3"
|
||||
typescript-eslint: "npm:8.46.1"
|
||||
typescript-eslint: "npm:8.46.2"
|
||||
ua-parser-js: "npm:2.0.6"
|
||||
vite-tsconfig-paths: "npm:5.1.4"
|
||||
vitest: "npm:3.2.4"
|
||||
@@ -10761,9 +10761,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lint-staged@npm:16.2.4":
|
||||
version: 16.2.4
|
||||
resolution: "lint-staged@npm:16.2.4"
|
||||
"lint-staged@npm:16.2.5":
|
||||
version: 16.2.5
|
||||
resolution: "lint-staged@npm:16.2.5"
|
||||
dependencies:
|
||||
commander: "npm:^14.0.1"
|
||||
listr2: "npm:^9.0.4"
|
||||
@@ -10774,7 +10774,7 @@ __metadata:
|
||||
yaml: "npm:^2.8.1"
|
||||
bin:
|
||||
lint-staged: bin/lint-staged.js
|
||||
checksum: 10/e4ce8e6b07fc2c1d96962dafaab483271b2359b6f22f74324ba0f827ad6383caa2800651379f36b2570cfa74b27354e0db9316be69795636a0c53fa3fd599b79
|
||||
checksum: 10/9a1019d2d95646bb7acb0a43ec1f89427f2066defa7fc89f82c71b25fdc55d1c16e1ac44afbed1440c37615c4bbaeafd687bfb0adf1a08ae5bc9d1015325ca88
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -14435,18 +14435,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript-eslint@npm:8.46.1":
|
||||
version: 8.46.1
|
||||
resolution: "typescript-eslint@npm:8.46.1"
|
||||
"typescript-eslint@npm:8.46.2":
|
||||
version: 8.46.2
|
||||
resolution: "typescript-eslint@npm:8.46.2"
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.46.1"
|
||||
"@typescript-eslint/parser": "npm:8.46.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.46.1"
|
||||
"@typescript-eslint/utils": "npm:8.46.1"
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.46.2"
|
||||
"@typescript-eslint/parser": "npm:8.46.2"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.46.2"
|
||||
"@typescript-eslint/utils": "npm:8.46.2"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10/ba6914cc4006390908de9e3de295c2f7110461175a818608d198e2d1529e726c32d778fe9e224ea30464ba2c4a43c05f534d2dbc5aabf297354a2aa49a2e1cd6
|
||||
checksum: 10/cd1bbc5d33c0369f70032165224badf1a8a9f95f39c891e4f71c78ceea9e7b2d71e0516d8b38177a11217867f387788f3fa126381418581409e7a76cdfdfe909
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user