mirror of
https://github.com/home-assistant/frontend.git
synced 2026-02-27 03:47:41 +00:00
Compare commits
10 Commits
ha-input
...
dialog-nex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1db214fd80 | ||
|
|
7581b7eb38 | ||
|
|
ab002ffc06 | ||
|
|
de59a558a7 | ||
|
|
28e562b98f | ||
|
|
7f175680ea | ||
|
|
f6a4c2cd33 | ||
|
|
43dba5fade | ||
|
|
7fd221d996 | ||
|
|
8107aa0ce2 |
@@ -8,6 +8,7 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockFloorRegistry } from "../../../../demo/src/stubs/floor_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry";
|
||||
import type { HASSDomEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-formfield";
|
||||
import "../../../../src/components/ha-selector/ha-selector";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
@@ -16,7 +17,10 @@ import type { BlueprintInput } from "../../../../src/data/blueprint";
|
||||
import type { DeviceRegistryEntry } from "../../../../src/data/device/device_registry";
|
||||
import type { FloorRegistryEntry } from "../../../../src/data/floor_registry";
|
||||
import type { LabelRegistryEntry } from "../../../../src/data/label/label_registry";
|
||||
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import {
|
||||
showDialog,
|
||||
type ShowDialogParams,
|
||||
} from "../../../../src/dialogs/make-dialog-manager";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import type { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
|
||||
@@ -611,14 +615,15 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
};
|
||||
};
|
||||
|
||||
private _dialogManager = (e) => {
|
||||
const { dialogTag, dialogImport, dialogParams, addHistory } = e.detail;
|
||||
private _dialogManager = (e: HASSDomEvent<ShowDialogParams<unknown>>) => {
|
||||
const { dialogTag, dialogImport, dialogParams, addHistory, parentElement } =
|
||||
e.detail;
|
||||
showDialog(
|
||||
this,
|
||||
this.shadowRoot!,
|
||||
dialogTag,
|
||||
dialogParams,
|
||||
dialogImport,
|
||||
parentElement,
|
||||
addHistory
|
||||
);
|
||||
};
|
||||
|
||||
@@ -118,7 +118,7 @@ class HaLandingPage extends LandingPageBaseElement {
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
makeDialogManager(this, this.shadowRoot!);
|
||||
makeDialogManager(this);
|
||||
|
||||
if (window.innerWidth > 450) {
|
||||
import("../../src/resources/particles");
|
||||
|
||||
@@ -69,7 +69,7 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
await this.updateComplete;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (this.hass && isIosApp(this.hass)) {
|
||||
if (this.hass && isIosApp(this.hass.auth.external)) {
|
||||
const element = this.renderRoot.querySelector("[autofocus]");
|
||||
if (element !== null) {
|
||||
if (!element.id) {
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import { mdiInvertColorsOff, mdiPalette } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeCssColor, THEME_COLORS } from "../common/color/compute-color";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { LocalizeKeys } from "../common/translations/localize";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import { localizeContext } from "../data/context";
|
||||
import type { ValueChangedEvent } from "../types";
|
||||
import "./ha-generic-picker";
|
||||
import type { PickerComboBoxItem } from "./ha-picker-combo-box";
|
||||
import type { PickerValueRenderer } from "./ha-picker-field";
|
||||
|
||||
@customElement("ha-color-picker")
|
||||
export class HaColorPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
@@ -34,12 +34,15 @@ export class HaColorPicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@state()
|
||||
@consume({ context: localizeContext, subscribe: true })
|
||||
private localize!: ContextType<typeof localizeContext>;
|
||||
|
||||
render() {
|
||||
const effectiveValue = this.value ?? this.defaultColor ?? "";
|
||||
|
||||
return html`
|
||||
<ha-generic-picker
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.hideClearIcon=${!this.value && !!this.defaultColor}
|
||||
@@ -50,7 +53,7 @@ export class HaColorPicker extends LitElement {
|
||||
.rowRenderer=${this._rowRenderer}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
@value-changed=${this._valueChanged}
|
||||
.notFoundLabel=${this.hass.localize(
|
||||
.notFoundLabel=${this.localize?.(
|
||||
"ui.components.color-picker.no_colors_found"
|
||||
)}
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
@@ -78,7 +81,9 @@ export class HaColorPicker extends LitElement {
|
||||
return [
|
||||
{
|
||||
id: searchString,
|
||||
primary: this.hass.localize("ui.components.color-picker.custom_color"),
|
||||
primary:
|
||||
this.localize?.("ui.components.color-picker.custom_color") ||
|
||||
"Custom color",
|
||||
secondary: searchString,
|
||||
},
|
||||
];
|
||||
@@ -101,16 +106,15 @@ export class HaColorPicker extends LitElement {
|
||||
): PickerComboBoxItem[] => {
|
||||
const items: PickerComboBoxItem[] = [];
|
||||
|
||||
const defaultSuffix = this.hass.localize(
|
||||
"ui.components.color-picker.default"
|
||||
);
|
||||
const defaultSuffix =
|
||||
this.localize?.("ui.components.color-picker.default") || "Default";
|
||||
|
||||
const addDefaultSuffix = (label: string, isDefault: boolean) =>
|
||||
isDefault && defaultSuffix ? `${label} (${defaultSuffix})` : label;
|
||||
|
||||
if (includeNone) {
|
||||
const noneLabel =
|
||||
this.hass.localize("ui.components.color-picker.none") || "None";
|
||||
this.localize?.("ui.components.color-picker.none") || "None";
|
||||
items.push({
|
||||
id: "none",
|
||||
primary: addDefaultSuffix(noneLabel, defaultColor === "none"),
|
||||
@@ -120,7 +124,7 @@ export class HaColorPicker extends LitElement {
|
||||
|
||||
if (includeState) {
|
||||
const stateLabel =
|
||||
this.hass.localize("ui.components.color-picker.state") || "State";
|
||||
this.localize?.("ui.components.color-picker.state") || "State";
|
||||
items.push({
|
||||
id: "state",
|
||||
primary: addDefaultSuffix(stateLabel, defaultColor === "state"),
|
||||
@@ -130,7 +134,7 @@ export class HaColorPicker extends LitElement {
|
||||
|
||||
Array.from(THEME_COLORS).forEach((color) => {
|
||||
const themeLabel =
|
||||
this.hass.localize(
|
||||
this.localize?.(
|
||||
`ui.components.color-picker.colors.${color}` as LocalizeKeys
|
||||
) || color;
|
||||
items.push({
|
||||
@@ -184,7 +188,7 @@ export class HaColorPicker extends LitElement {
|
||||
return html`
|
||||
<ha-svg-icon slot="start" .path=${mdiInvertColorsOff}></ha-svg-icon>
|
||||
<span slot="headline">
|
||||
${this.hass.localize("ui.components.color-picker.none")}
|
||||
${this.localize?.("ui.components.color-picker.none") || "None"}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
@@ -192,7 +196,7 @@ export class HaColorPicker extends LitElement {
|
||||
return html`
|
||||
<ha-svg-icon slot="start" .path=${mdiPalette}></ha-svg-icon>
|
||||
<span slot="headline">
|
||||
${this.hass.localize("ui.components.color-picker.state")}
|
||||
${this.localize?.("ui.components.color-picker.state") || "State"}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
@@ -200,7 +204,7 @@ export class HaColorPicker extends LitElement {
|
||||
return html`
|
||||
<span slot="start">${this._renderColorCircle(value)}</span>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
${this.localize?.(
|
||||
`ui.components.color-picker.colors.${value}` as LocalizeKeys
|
||||
) || value}
|
||||
</span>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import "@home-assistant/webawesome/dist/components/dialog/dialog";
|
||||
import type WaDialog from "@home-assistant/webawesome/dist/components/dialog/dialog";
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import {
|
||||
@@ -11,9 +12,9 @@ import {
|
||||
} from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { authContext, localizeContext } from "../data/context";
|
||||
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { isIosApp } from "../util/is_ios";
|
||||
import "./ha-dialog-header";
|
||||
import "./ha-icon-button";
|
||||
@@ -77,8 +78,6 @@ type DialogHideEvent = CustomEvent<{ source?: Element }>;
|
||||
*/
|
||||
@customElement("ha-dialog")
|
||||
export class HaDialog extends ScrollableFadeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: "aria-labelledby" })
|
||||
public ariaLabelledBy?: string;
|
||||
|
||||
@@ -117,6 +116,14 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
@query(".body") public bodyContainer!: HTMLDivElement;
|
||||
|
||||
@state()
|
||||
@consume({ context: localizeContext, subscribe: true })
|
||||
private localize!: ContextType<typeof localizeContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: authContext, subscribe: true })
|
||||
private auth?: ContextType<typeof authContext>;
|
||||
|
||||
@state()
|
||||
private _bodyScrolled = false;
|
||||
|
||||
@@ -162,7 +169,7 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
|
||||
<slot name="headerNavigationIcon" slot="navigationIcon">
|
||||
<ha-icon-button
|
||||
data-dialog="close"
|
||||
.label=${this.hass?.localize("ui.common.close") ?? "Close"}
|
||||
.label=${this.localize?.("ui.common.close") ?? "Close"}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
</slot>
|
||||
@@ -196,13 +203,13 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
|
||||
await this.updateComplete;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (this.hass && isIosApp(this.hass)) {
|
||||
if (this.auth?.external && isIosApp(this.auth.external)) {
|
||||
const element = this.querySelector("[autofocus]");
|
||||
if (element !== null) {
|
||||
if (!element.id) {
|
||||
element.id = "ha-dialog-autofocus";
|
||||
}
|
||||
this.hass?.auth.external?.fireMessage({
|
||||
this.auth.external.fireMessage({
|
||||
type: "focus_element",
|
||||
payload: {
|
||||
element_id: element.id,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import "@home-assistant/webawesome/dist/components/popover/popover";
|
||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import { mdiPlaylistPlus } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
@@ -13,10 +14,9 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { tinykeys } from "tinykeys";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { throttle } from "../common/util/throttle";
|
||||
import { authContext } from "../data/context";
|
||||
import { PickerMixin } from "../mixins/picker-mixin";
|
||||
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { isIosApp } from "../util/is_ios";
|
||||
import "./ha-bottom-sheet";
|
||||
import "./ha-button";
|
||||
@@ -33,8 +33,6 @@ import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-generic-picker")
|
||||
export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, attribute: "allow-custom-value" })
|
||||
public allowCustomValue;
|
||||
|
||||
@@ -113,6 +111,10 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
|
||||
@query("ha-picker-combo-box") private _comboBox?: HaPickerComboBox;
|
||||
|
||||
@state()
|
||||
@consume({ context: authContext, subscribe: true })
|
||||
private auth?: ContextType<typeof authContext>;
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@state() private _pickerWrapperOpen = false;
|
||||
@@ -142,10 +144,6 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
protected willUpdate(changedProperties: PropertyValues) {
|
||||
if (changedProperties.has("value")) {
|
||||
this._setUnknownValue();
|
||||
return;
|
||||
}
|
||||
if (changedProperties.has("hass")) {
|
||||
this._throttleUnknownValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +250,6 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
return html`
|
||||
<ha-picker-combo-box
|
||||
id="combo-box"
|
||||
.hass=${this.hass}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
.label=${this.searchLabel}
|
||||
.value=${this.value}
|
||||
@@ -291,13 +288,6 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
);
|
||||
};
|
||||
|
||||
private _throttleUnknownValue = throttle(
|
||||
this._setUnknownValue,
|
||||
1000,
|
||||
true,
|
||||
false
|
||||
);
|
||||
|
||||
private _renderHelper() {
|
||||
const showError = this.invalid && this.errorMessage;
|
||||
const showHelper = !showError && this.helper;
|
||||
@@ -321,8 +311,8 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
this._comboBox?.setFieldValue(this._initialFieldValue);
|
||||
this._initialFieldValue = undefined;
|
||||
}
|
||||
if (this.hass && isIosApp(this.hass)) {
|
||||
this.hass.auth.external!.fireMessage({
|
||||
if (this.auth?.external && isIosApp(this.auth.external)) {
|
||||
this.auth.external.fireMessage({
|
||||
type: "focus_element",
|
||||
payload: {
|
||||
element_id: "combo-box",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { customIcons } from "../data/custom_icons";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import type { ValueChangedEvent } from "../types";
|
||||
import "./ha-combo-box-item";
|
||||
import "./ha-generic-picker";
|
||||
import "./ha-icon";
|
||||
@@ -88,8 +88,6 @@ const rowRenderer: RenderItemFunction<PickerComboBoxItem> = (item) => html`
|
||||
|
||||
@customElement("ha-icon-picker")
|
||||
export class HaIconPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
@@ -111,7 +109,6 @@ export class HaIconPicker extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-generic-picker
|
||||
.hass=${this.hass}
|
||||
allow-custom-value
|
||||
.getItems=${this._getIconPickerItems}
|
||||
.helper=${this.helper}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { LitVirtualizer } from "@lit-labs/virtualizer";
|
||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import { mdiClose, mdiMagnify, mdiMinusBoxOutline, mdiPlus } from "@mdi/js";
|
||||
import Fuse from "fuse.js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
@@ -14,6 +15,7 @@ import memoizeOne from "memoize-one";
|
||||
import { tinykeys } from "tinykeys";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import { localeContext, localizeContext } from "../data/context";
|
||||
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
|
||||
import {
|
||||
multiTermSortedSearch,
|
||||
@@ -21,7 +23,6 @@ import {
|
||||
} from "../resources/fuseMultiTerm";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import { loadVirtualizer } from "../resources/virtualizer";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { isTouch } from "../util/is_touch";
|
||||
import "./chips/ha-chip-set";
|
||||
import "./chips/ha-filter-chip";
|
||||
@@ -90,8 +91,6 @@ export type PickerComboBoxSearchFn<T extends PickerComboBoxItem> = (
|
||||
|
||||
@customElement("ha-picker-combo-box")
|
||||
export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
// eslint-disable-next-line lit/no-native-attributes
|
||||
@property({ type: Boolean }) public autofocus = false;
|
||||
|
||||
@@ -162,6 +161,14 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
@query("ha-textfield") private _searchFieldElement?: HaTextField;
|
||||
|
||||
@state()
|
||||
@consume({ context: localizeContext, subscribe: true })
|
||||
private localize!: ContextType<typeof localizeContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: localeContext, subscribe: true })
|
||||
private locale!: ContextType<typeof localeContext>;
|
||||
|
||||
@state() private _items: PickerComboBoxItem[] = [];
|
||||
|
||||
@state() private _selectedSection?: string;
|
||||
@@ -215,9 +222,9 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
const searchLabel =
|
||||
this.label ??
|
||||
(this.allowCustomValue
|
||||
? (this.hass?.localize("ui.components.combo-box.search_or_custom") ??
|
||||
? (this.localize?.("ui.components.combo-box.search_or_custom") ??
|
||||
"Search | Add custom value")
|
||||
: (this.hass?.localize("ui.common.search") ?? "Search"));
|
||||
: (this.localize?.("ui.common.search") ?? "Search"));
|
||||
|
||||
return html`<ha-textfield
|
||||
.label=${searchLabel}
|
||||
@@ -228,7 +235,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
<ha-icon-button
|
||||
@click=${this._clearSearch}
|
||||
slot="trailingIcon"
|
||||
.label=${this.hass?.localize("ui.common.clear") || "Clear"}
|
||||
.label=${this.localize?.("ui.common.clear") || "Clear"}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
</ha-textfield>
|
||||
@@ -350,7 +357,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
return caseInsensitiveStringCompare(
|
||||
sortLabelA,
|
||||
sortLabelB,
|
||||
this.hass?.locale.language ?? navigator.language
|
||||
this.locale?.language ?? navigator.language
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -367,7 +374,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
id: this._search,
|
||||
primary:
|
||||
this.customValueLabel ??
|
||||
this.hass?.localize("ui.components.combo-box.add_custom_item") ??
|
||||
this.localize?.("ui.components.combo-box.add_custom_item") ??
|
||||
"Add custom item",
|
||||
secondary: `"${this._search}"`,
|
||||
icon_path: mdiPlus,
|
||||
@@ -401,10 +408,10 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
? typeof this.notFoundLabel === "function"
|
||||
? this.notFoundLabel(this._search)
|
||||
: this.notFoundLabel ||
|
||||
this.hass?.localize("ui.components.combo-box.no_match") ||
|
||||
this.localize?.("ui.components.combo-box.no_match") ||
|
||||
"No matching items found"
|
||||
: this.emptyLabel ||
|
||||
this.hass?.localize("ui.components.combo-box.no_items") ||
|
||||
this.localize?.("ui.components.combo-box.no_items") ||
|
||||
"No items available"}</span
|
||||
>
|
||||
</ha-combo-box-item>
|
||||
@@ -507,7 +514,7 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
id: searchString,
|
||||
primary:
|
||||
this.customValueLabel ??
|
||||
this.hass?.localize("ui.components.combo-box.add_custom_item") ??
|
||||
this.localize?.("ui.components.combo-box.add_custom_item") ??
|
||||
"Add custom item",
|
||||
secondary: `"${searchString}"`,
|
||||
icon_path: mdiPlus,
|
||||
|
||||
@@ -34,3 +34,5 @@ export const labelsContext = createContext<LabelRegistryEntry[]>("labels");
|
||||
|
||||
export const configEntriesContext =
|
||||
createContext<ConfigEntry[]>("configEntries");
|
||||
|
||||
export const authContext = createContext<HomeAssistant["auth"]>("auth");
|
||||
|
||||
55
src/dialogs/dialog-mixin.ts
Normal file
55
src/dialogs/dialog-mixin.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { LitElement } from "lit";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { HaDialog } from "../components/ha-dialog";
|
||||
import type { Constructor } from "../types";
|
||||
import type { HassDialogNext } from "./make-dialog-manager";
|
||||
|
||||
export const DialogMixin = <
|
||||
P = unknown,
|
||||
T extends Constructor<LitElement> = Constructor<LitElement>,
|
||||
>(
|
||||
superClass: T
|
||||
) =>
|
||||
class extends superClass implements HassDialogNext<P> {
|
||||
declare public params?: P;
|
||||
|
||||
private _closePromise?: Promise<boolean>;
|
||||
|
||||
private _closeResolve?: (value: boolean) => void;
|
||||
|
||||
public closeDialog(_historyState?: any): Promise<boolean> | boolean {
|
||||
if (this._closePromise) {
|
||||
return this._closePromise;
|
||||
}
|
||||
|
||||
const dialogElement = this.shadowRoot?.querySelector(
|
||||
"ha-dialog"
|
||||
) as HaDialog | null;
|
||||
if (dialogElement) {
|
||||
this._closePromise = new Promise<boolean>((resolve) => {
|
||||
this._closeResolve = resolve;
|
||||
});
|
||||
dialogElement.open = false;
|
||||
}
|
||||
return this._closePromise || true;
|
||||
}
|
||||
|
||||
private _removeDialog = (ev) => {
|
||||
ev.stopPropagation();
|
||||
this._closeResolve?.(true);
|
||||
this._closePromise = undefined;
|
||||
this._closeResolve = undefined;
|
||||
this.remove();
|
||||
};
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.addEventListener("closed", this._removeDialog, { once: true });
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
this.removeEventListener("closed", this._removeDialog);
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { LitElement } from "lit";
|
||||
import { ancestorsWithProperty } from "../common/dom/ancestors-with-property";
|
||||
import { deepActiveElement } from "../common/dom/deep-active-element";
|
||||
import type { HASSDomEvent, ValidHassDomEvent } from "../common/dom/fire_event";
|
||||
import type { HASSDomEvent } from "../common/dom/fire_event";
|
||||
import { mainWindow } from "../common/dom/get_main_window";
|
||||
import { nextRender } from "../common/util/render-status";
|
||||
import type { ProvideHassElement } from "../mixins/provide-hass-lit-mixin";
|
||||
@@ -19,18 +20,22 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export interface HassDialog<
|
||||
T = HASSDomEvents[ValidHassDomEvent],
|
||||
> extends HTMLElement {
|
||||
export interface HassDialog<T = unknown> extends HTMLElement {
|
||||
showDialog(params: T);
|
||||
closeDialog?: (historyState?: any) => boolean;
|
||||
closeDialog?: (historyState?: any) => Promise<boolean> | boolean;
|
||||
}
|
||||
|
||||
interface ShowDialogParams<T> {
|
||||
export interface HassDialogNext<T = unknown> extends HTMLElement {
|
||||
params?: T;
|
||||
closeDialog?: (historyState?: any) => Promise<boolean> | boolean;
|
||||
}
|
||||
|
||||
export interface ShowDialogParams<T> {
|
||||
dialogTag: keyof HTMLElementTagNameMap;
|
||||
dialogImport: () => Promise<unknown>;
|
||||
dialogParams: T;
|
||||
dialogParams?: T;
|
||||
addHistory?: boolean;
|
||||
parentElement?: LitElement;
|
||||
}
|
||||
|
||||
export interface DialogClosedParams {
|
||||
@@ -39,7 +44,6 @@ export interface DialogClosedParams {
|
||||
|
||||
export interface DialogState {
|
||||
element: HTMLElement & ProvideHassElement;
|
||||
root: ShadowRoot | HTMLElement;
|
||||
dialogTag: string;
|
||||
dialogParams: unknown;
|
||||
dialogImport?: () => Promise<unknown>;
|
||||
@@ -47,7 +51,7 @@ export interface DialogState {
|
||||
}
|
||||
|
||||
interface LoadedDialogInfo {
|
||||
element: Promise<HassDialog>;
|
||||
element: Promise<HassDialogNext | HassDialog> | null;
|
||||
closedFocusTargets?: Set<Element>;
|
||||
}
|
||||
|
||||
@@ -57,12 +61,24 @@ const LOADED: LoadedDialogsDict = {};
|
||||
const OPEN_DIALOG_STACK: DialogState[] = [];
|
||||
export const FOCUS_TARGET = Symbol.for("HA focus target");
|
||||
|
||||
/**
|
||||
* Shows a dialog element, lazy-loading it if needed, and optionally integrates
|
||||
* dialog open/close behavior with browser history.
|
||||
*
|
||||
* @param element The host element that can provide `hass` and receives the dialog by default.
|
||||
* @param dialogTag The custom element tag name of the dialog.
|
||||
* @param dialogParams The params passed to the dialog via `showDialog()` or `params`.
|
||||
* @param dialogImport Optional lazy import used when the dialog has not been loaded yet.
|
||||
* @param parentElement Optional parent to append the dialog to instead of root element.
|
||||
* @param addHistory Whether to add/update browser history so back navigation closes dialogs.
|
||||
* @returns `true` if the dialog was shown (or could be shown), `false` if it could not be loaded.
|
||||
*/
|
||||
export const showDialog = async (
|
||||
element: HTMLElement & ProvideHassElement,
|
||||
root: ShadowRoot | HTMLElement,
|
||||
element: LitElement & ProvideHassElement,
|
||||
dialogTag: string,
|
||||
dialogParams: unknown,
|
||||
dialogImport?: () => Promise<unknown>,
|
||||
parentElement?: LitElement,
|
||||
addHistory = true
|
||||
): Promise<boolean> => {
|
||||
if (!(dialogTag in LOADED)) {
|
||||
@@ -77,10 +93,18 @@ export const showDialog = async (
|
||||
}
|
||||
LOADED[dialogTag] = {
|
||||
element: dialogImport().then(() => {
|
||||
const dialogEl = document.createElement(dialogTag) as HassDialog;
|
||||
element.provideHass(dialogEl);
|
||||
const dialogEl = document.createElement(dialogTag) as
|
||||
| HassDialogNext
|
||||
| HassDialog;
|
||||
|
||||
if ("showDialog" in dialogEl) {
|
||||
// provide hass for legacy persistent dialogs
|
||||
element.provideHass(dialogEl);
|
||||
}
|
||||
|
||||
dialogEl.addEventListener("dialog-closed", _handleClosed);
|
||||
dialogEl.addEventListener("dialog-closed", _handleClosedFocus);
|
||||
|
||||
return dialogEl;
|
||||
}),
|
||||
};
|
||||
@@ -96,10 +120,10 @@ export const showDialog = async (
|
||||
});
|
||||
return showDialog(
|
||||
element,
|
||||
root,
|
||||
dialogTag,
|
||||
dialogParams,
|
||||
dialogImport,
|
||||
parentElement,
|
||||
addHistory
|
||||
);
|
||||
}
|
||||
@@ -111,7 +135,6 @@ export const showDialog = async (
|
||||
}
|
||||
OPEN_DIALOG_STACK.push({
|
||||
element,
|
||||
root,
|
||||
dialogTag,
|
||||
dialogParams,
|
||||
dialogImport,
|
||||
@@ -134,12 +157,24 @@ export const showDialog = async (
|
||||
FOCUS_TARGET
|
||||
);
|
||||
|
||||
const dialogElement = await LOADED[dialogTag].element;
|
||||
let dialogElement: HassDialogNext | HassDialog | null;
|
||||
|
||||
// Append it again so it's the last element in the root,
|
||||
// so it's guaranteed to be on top of the other elements
|
||||
root.appendChild(dialogElement);
|
||||
dialogElement.showDialog(dialogParams);
|
||||
if (LOADED[dialogTag] && LOADED[dialogTag].element === null) {
|
||||
dialogElement = document.createElement(dialogTag) as HassDialogNext;
|
||||
dialogElement.addEventListener("dialog-closed", _handleClosed);
|
||||
dialogElement.addEventListener("dialog-closed", _handleClosedFocus);
|
||||
LOADED[dialogTag].element = Promise.resolve(dialogElement);
|
||||
} else {
|
||||
dialogElement = await LOADED[dialogTag].element;
|
||||
}
|
||||
|
||||
if ("showDialog" in dialogElement!) {
|
||||
dialogElement.showDialog(dialogParams);
|
||||
} else {
|
||||
dialogElement!.params = dialogParams;
|
||||
}
|
||||
|
||||
(parentElement || element).shadowRoot!.appendChild(dialogElement!);
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -152,7 +187,7 @@ export const closeDialog = async (
|
||||
return true;
|
||||
}
|
||||
const dialogElement = await LOADED[dialogTag].element;
|
||||
if (dialogElement.closeDialog) {
|
||||
if (dialogElement && dialogElement.closeDialog) {
|
||||
return dialogElement.closeDialog(historyState) !== false;
|
||||
}
|
||||
return true;
|
||||
@@ -214,22 +249,34 @@ const _handleClosed = (ev: HASSDomEvent<DialogClosedParams>) => {
|
||||
mainWindow.history.back();
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup element
|
||||
if (ev.currentTarget && "params" in ev.currentTarget) {
|
||||
const dialogElement = ev.currentTarget as HassDialogNext;
|
||||
dialogElement.removeEventListener("dialog-closed", _handleClosed);
|
||||
dialogElement.removeEventListener("dialog-closed", _handleClosedFocus);
|
||||
LOADED[ev.detail.dialog].element = null;
|
||||
}
|
||||
};
|
||||
|
||||
export const makeDialogManager = (
|
||||
element: HTMLElement & ProvideHassElement,
|
||||
root: ShadowRoot | HTMLElement
|
||||
) => {
|
||||
export const makeDialogManager = (element: LitElement & ProvideHassElement) => {
|
||||
element.addEventListener(
|
||||
"show-dialog",
|
||||
(e: HASSDomEvent<ShowDialogParams<unknown>>) => {
|
||||
const { dialogTag, dialogImport, dialogParams, addHistory } = e.detail;
|
||||
const {
|
||||
dialogTag,
|
||||
dialogImport,
|
||||
dialogParams,
|
||||
addHistory,
|
||||
parentElement,
|
||||
} = e.detail;
|
||||
|
||||
showDialog(
|
||||
element,
|
||||
root,
|
||||
dialogTag,
|
||||
dialogParams,
|
||||
dialogImport,
|
||||
parentElement,
|
||||
addHistory
|
||||
);
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ export interface MoreInfoDialogParams {
|
||||
tab?: View;
|
||||
large?: boolean;
|
||||
data?: Record<string, any>;
|
||||
parentElement?: LitElement;
|
||||
}
|
||||
|
||||
type View = "info" | "history" | "settings" | "related" | "add_to";
|
||||
|
||||
@@ -146,7 +146,7 @@ export class QuickBar extends LitElement {
|
||||
private _dialogOpened = async () => {
|
||||
this._opened = true;
|
||||
requestAnimationFrame(() => {
|
||||
if (this.hass && isIosApp(this.hass)) {
|
||||
if (this.hass && isIosApp(this.hass.auth.external)) {
|
||||
this.hass.auth.external!.fireMessage({
|
||||
type: "focus_element",
|
||||
payload: {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import type { LocalizeKeys } from "../../common/translations/localize";
|
||||
import "../../components/ha-alert";
|
||||
import "../../components/ha-svg-icon";
|
||||
import "../../components/ha-dialog";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../../components/ha-svg-icon";
|
||||
import { localizeContext } from "../../data/context";
|
||||
import { isMac } from "../../util/is_mac";
|
||||
import { DialogMixin } from "../dialog-mixin";
|
||||
|
||||
interface Text {
|
||||
textTranslationKey: LocalizeKeys;
|
||||
@@ -165,24 +166,10 @@ const _SHORTCUTS: Section[] = [
|
||||
];
|
||||
|
||||
@customElement("dialog-shortcuts")
|
||||
class DialogShortcuts extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
public async showDialog(): Promise<void> {
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
private _dialogClosed() {
|
||||
this._open = false;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
public async closeDialog() {
|
||||
this._open = false;
|
||||
return true;
|
||||
}
|
||||
class DialogShortcuts extends DialogMixin(LitElement) {
|
||||
@state()
|
||||
@consume({ context: localizeContext, subscribe: true })
|
||||
private localize!: ContextType<typeof localizeContext>;
|
||||
|
||||
private _renderShortcut(
|
||||
shortcutKeys: ShortcutString[],
|
||||
@@ -196,15 +183,13 @@ class DialogShortcuts extends LitElement {
|
||||
>${shortcutKey === CTRL_CMD
|
||||
? isMac
|
||||
? "⌘"
|
||||
: this.hass.localize("ui.dialogs.shortcuts.keys.ctrl")
|
||||
: this.localize("ui.dialogs.shortcuts.keys.ctrl")
|
||||
: typeof shortcutKey === "string"
|
||||
? shortcutKey
|
||||
: this.hass.localize(
|
||||
shortcutKey.shortcutTranslationKey
|
||||
)}</span
|
||||
: this.localize(shortcutKey.shortcutTranslationKey)}</span
|
||||
>`
|
||||
)}
|
||||
${this.hass.localize(descriptionKey)}
|
||||
${this.localize(descriptionKey)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -212,14 +197,13 @@ class DialogShortcuts extends LitElement {
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
@closed=${this._dialogClosed}
|
||||
.headerTitle=${this.hass.localize("ui.dialogs.shortcuts.title")}
|
||||
open
|
||||
.headerTitle=${this.localize("ui.dialogs.shortcuts.title")}
|
||||
>
|
||||
<div class="content">
|
||||
${_SHORTCUTS.map(
|
||||
(section) => html`
|
||||
<h3>${this.hass.localize(section.titleTranslationKey)}</h3>
|
||||
<h3>${this.localize(section.titleTranslationKey)}</h3>
|
||||
<div class="items">
|
||||
${section.items.map((item) => {
|
||||
if ("shortcut" in item) {
|
||||
@@ -229,7 +213,7 @@ class DialogShortcuts extends LitElement {
|
||||
);
|
||||
}
|
||||
return html`<p>
|
||||
${this.hass.localize((item as Text).textTranslationKey)}
|
||||
${this.localize((item as Text).textTranslationKey)}
|
||||
</p>`;
|
||||
})}
|
||||
</div>
|
||||
@@ -238,9 +222,9 @@ class DialogShortcuts extends LitElement {
|
||||
</div>
|
||||
|
||||
<ha-alert slot="footer">
|
||||
${this.hass.localize("ui.dialogs.shortcuts.enable_shortcuts_hint", {
|
||||
${this.localize("ui.dialogs.shortcuts.enable_shortcuts_hint", {
|
||||
user_profile: html`<a href="/profile/general#shortcuts"
|
||||
>${this.hass.localize(
|
||||
>${this.localize(
|
||||
"ui.dialogs.shortcuts.enable_shortcuts_hint_user_profile"
|
||||
)}</a
|
||||
>`,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { LitElement } from "lit";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
export const showShortcutsDialog = (element: HTMLElement) =>
|
||||
export const showShortcutsDialog = (element: LitElement) =>
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-shortcuts",
|
||||
dialogImport: () => import("./dialog-shortcuts"),
|
||||
dialogParams: {},
|
||||
});
|
||||
|
||||
@@ -226,7 +226,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
) {
|
||||
import("../resources/particles");
|
||||
}
|
||||
makeDialogManager(this, this.shadowRoot!);
|
||||
makeDialogManager(this);
|
||||
import("../components/ha-language-picker");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dialog";
|
||||
import "../../../components/ha-dialog-footer";
|
||||
import "../../../components/ha-icon-picker";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-textfield";
|
||||
import type {
|
||||
CategoryRegistryEntry,
|
||||
CategoryRegistryEntryMutableParams,
|
||||
} from "../../../data/category_registry";
|
||||
import { localizeContext } from "../../../data/context";
|
||||
import { DialogMixin } from "../../../dialogs/dialog-mixin";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { CategoryRegistryDetailDialogParams } from "./show-dialog-category-registry-detail";
|
||||
|
||||
@customElement("dialog-category-registry-detail")
|
||||
class DialogCategoryDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
class DialogCategoryDetail extends DialogMixin<CategoryRegistryDetailDialogParams>(
|
||||
LitElement
|
||||
) {
|
||||
@state()
|
||||
@consume({ context: localizeContext, subscribe: true })
|
||||
private localize!: ContextType<typeof localizeContext>;
|
||||
|
||||
@state() private _name!: string;
|
||||
|
||||
@@ -26,53 +31,32 @@ class DialogCategoryDetail extends LitElement {
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _params?: CategoryRegistryDetailDialogParams;
|
||||
|
||||
@state() private _submitting?: boolean;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
public async showDialog(
|
||||
params: CategoryRegistryDetailDialogParams
|
||||
): Promise<void> {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
this._open = true;
|
||||
if (this._params.entry) {
|
||||
this._name = this._params.entry.name || "";
|
||||
this._icon = this._params.entry.icon || null;
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (this.params?.entry) {
|
||||
this._name = this.params.entry.name || "";
|
||||
this._icon = this.params.entry.icon || null;
|
||||
} else {
|
||||
this._name = this._params.suggestedName || "";
|
||||
this._name = this.params?.suggestedName || "";
|
||||
this._icon = null;
|
||||
}
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this._error = "";
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
if (!this.params) {
|
||||
return nothing;
|
||||
}
|
||||
const entry = this._params.entry;
|
||||
const entry = this.params.entry;
|
||||
const nameInvalid = !this._isNameValid();
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
open
|
||||
header-title=${entry
|
||||
? this.hass.localize("ui.panel.config.category.editor.edit")
|
||||
: this.hass.localize("ui.panel.config.category.editor.create")}
|
||||
? this.localize("ui.panel.config.category.editor.edit")
|
||||
: this.localize("ui.panel.config.category.editor.create")}
|
||||
prevent-scrim-close
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
@@ -81,8 +65,8 @@ class DialogCategoryDetail extends LitElement {
|
||||
<ha-textfield
|
||||
.value=${this._name}
|
||||
@input=${this._nameChanged}
|
||||
.label=${this.hass.localize("ui.panel.config.category.editor.name")}
|
||||
.validationMessage=${this.hass.localize(
|
||||
.label=${this.localize("ui.panel.config.category.editor.name")}
|
||||
.validationMessage=${this.localize(
|
||||
"ui.panel.config.category.editor.required_error_msg"
|
||||
)}
|
||||
required
|
||||
@@ -90,10 +74,9 @@ class DialogCategoryDetail extends LitElement {
|
||||
></ha-textfield>
|
||||
|
||||
<ha-icon-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._icon}
|
||||
.value=${this._icon ?? undefined}
|
||||
@value-changed=${this._iconChanged}
|
||||
.label=${this.hass.localize("ui.panel.config.category.editor.icon")}
|
||||
.label=${this.localize("ui.panel.config.category.editor.icon")}
|
||||
></ha-icon-picker>
|
||||
</div>
|
||||
<ha-dialog-footer slot="footer">
|
||||
@@ -102,7 +85,7 @@ class DialogCategoryDetail extends LitElement {
|
||||
appearance="plain"
|
||||
@click=${this.closeDialog}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
${this.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@@ -110,8 +93,8 @@ class DialogCategoryDetail extends LitElement {
|
||||
.disabled=${nameInvalid || !!this._submitting}
|
||||
>
|
||||
${entry
|
||||
? this.hass.localize("ui.common.save")
|
||||
: this.hass.localize("ui.common.add")}
|
||||
? this.localize("ui.common.save")
|
||||
: this.localize("ui.common.add")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-dialog>
|
||||
@@ -133,7 +116,7 @@ class DialogCategoryDetail extends LitElement {
|
||||
}
|
||||
|
||||
private async _updateEntry() {
|
||||
const create = !this._params!.entry;
|
||||
const create = !this.params!.entry;
|
||||
this._submitting = true;
|
||||
let newValue: CategoryRegistryEntry | undefined;
|
||||
try {
|
||||
@@ -142,15 +125,15 @@ class DialogCategoryDetail extends LitElement {
|
||||
icon: this._icon || (create ? undefined : null),
|
||||
};
|
||||
if (create) {
|
||||
newValue = await this._params!.createEntry!(values);
|
||||
newValue = await this.params!.createEntry!(values);
|
||||
} else {
|
||||
newValue = await this._params!.updateEntry!(values);
|
||||
newValue = await this.params!.updateEntry!(values);
|
||||
}
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
this._error =
|
||||
err.message ||
|
||||
this.hass.localize("ui.panel.config.category.editor.unknown_error");
|
||||
this.localize("ui.panel.config.category.editor.unknown_error");
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-color-picker";
|
||||
import "../../../components/ha-dialog";
|
||||
import "../../../components/ha-dialog-footer";
|
||||
import "../../../components/ha-icon-picker";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/ha-dialog";
|
||||
import "../../../components/ha-textarea";
|
||||
import "../../../components/ha-textfield";
|
||||
import { localizeContext } from "../../../data/context";
|
||||
import type { LabelRegistryEntryMutableParams } from "../../../data/label/label_registry";
|
||||
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
|
||||
import { DialogMixin } from "../../../dialogs/dialog-mixin";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LabelDetailDialogParams } from "./show-dialog-label-detail";
|
||||
|
||||
@customElement("dialog-label-detail")
|
||||
class DialogLabelDetail
|
||||
extends LitElement
|
||||
implements HassDialog<LabelDetailDialogParams>
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
class DialogLabelDetail extends DialogMixin<LabelDetailDialogParams>(
|
||||
LitElement
|
||||
) {
|
||||
@state()
|
||||
@consume({ context: localizeContext, subscribe: true })
|
||||
private localize!: ContextType<typeof localizeContext>;
|
||||
|
||||
@state() private _name!: string;
|
||||
|
||||
@@ -34,53 +35,35 @@ class DialogLabelDetail
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _params?: LabelDetailDialogParams;
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
public showDialog(params: LabelDetailDialogParams): void {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
if (this._params.entry) {
|
||||
this._name = this._params.entry.name || "";
|
||||
this._icon = this._params.entry.icon || "";
|
||||
this._color = this._params.entry.color || "";
|
||||
this._description = this._params.entry.description || "";
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (this.params?.entry) {
|
||||
this._name = this.params.entry.name || "";
|
||||
this._icon = this.params.entry.icon || "";
|
||||
this._color = this.params.entry.color || "";
|
||||
this._description = this.params.entry.description || "";
|
||||
} else {
|
||||
this._name = this._params.suggestedName || "";
|
||||
this._name = this.params?.suggestedName || "";
|
||||
this._icon = "";
|
||||
this._color = "";
|
||||
this._description = "";
|
||||
}
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._open = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
if (!this.params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this._params.entry
|
||||
? this._params.entry.name || this._params.entry.label_id
|
||||
: this.hass!.localize("ui.dialogs.label-detail.new_label")}
|
||||
open
|
||||
header-title=${this.params.entry
|
||||
? this.params.entry.name || this.params.entry.label_id
|
||||
: this.localize("ui.dialogs.label-detail.new_label")}
|
||||
prevent-scrim-close
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<div>
|
||||
${this._error
|
||||
@@ -92,39 +75,35 @@ class DialogLabelDetail
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@input=${this._input}
|
||||
.label=${this.hass!.localize("ui.dialogs.label-detail.name")}
|
||||
.validationMessage=${this.hass!.localize(
|
||||
.label=${this.localize("ui.dialogs.label-detail.name")}
|
||||
.validationMessage=${this.localize(
|
||||
"ui.dialogs.label-detail.required_error_msg"
|
||||
)}
|
||||
required
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.value=${this._icon}
|
||||
.hass=${this.hass}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize("ui.dialogs.label-detail.icon")}
|
||||
.label=${this.localize("ui.dialogs.label-detail.icon")}
|
||||
></ha-icon-picker>
|
||||
<ha-color-picker
|
||||
.value=${this._color}
|
||||
.configValue=${"color"}
|
||||
.hass=${this.hass}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize("ui.dialogs.label-detail.color")}
|
||||
.label=${this.localize("ui.dialogs.label-detail.color")}
|
||||
></ha-color-picker>
|
||||
<ha-textarea
|
||||
.value=${this._description}
|
||||
.configValue=${"description"}
|
||||
@input=${this._input}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.label-detail.description"
|
||||
)}
|
||||
.label=${this.localize("ui.dialogs.label-detail.description")}
|
||||
></ha-textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ha-dialog-footer slot="footer">
|
||||
${this._params.entry && this._params.removeEntry
|
||||
${this.params.entry && this.params.removeEntry
|
||||
? html`
|
||||
<ha-button
|
||||
slot="secondaryAction"
|
||||
@@ -133,7 +112,7 @@ class DialogLabelDetail
|
||||
@click=${this._deleteEntry}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass!.localize("ui.common.delete")}
|
||||
${this.localize("ui.common.delete")}
|
||||
</ha-button>
|
||||
`
|
||||
: html`
|
||||
@@ -142,7 +121,7 @@ class DialogLabelDetail
|
||||
slot="secondaryAction"
|
||||
@click=${this.closeDialog}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
${this.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
`}
|
||||
<ha-button
|
||||
@@ -150,9 +129,9 @@ class DialogLabelDetail
|
||||
@click=${this._updateEntry}
|
||||
.disabled=${this._submitting || !this._name}
|
||||
>
|
||||
${this._params.entry
|
||||
? this.hass!.localize("ui.common.update")
|
||||
: this.hass!.localize("ui.common.create")}
|
||||
${this.params.entry
|
||||
? this.localize("ui.common.update")
|
||||
: this.localize("ui.common.create")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-dialog>
|
||||
@@ -184,10 +163,10 @@ class DialogLabelDetail
|
||||
color: this._color.trim() || null,
|
||||
description: this._description.trim() || null,
|
||||
};
|
||||
if (this._params!.entry) {
|
||||
await this._params!.updateEntry!(values);
|
||||
if (this.params!.entry) {
|
||||
await this.params!.updateEntry!(values);
|
||||
} else {
|
||||
await this._params!.createEntry!(values);
|
||||
await this.params!.createEntry!(values);
|
||||
}
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
@@ -200,8 +179,8 @@ class DialogLabelDetail
|
||||
private async _deleteEntry() {
|
||||
this._submitting = true;
|
||||
try {
|
||||
if (await this._params!.removeEntry!()) {
|
||||
this._params = undefined;
|
||||
if (await this.params!.removeEntry!()) {
|
||||
this.params = undefined;
|
||||
}
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ContextProvider } from "@lit/context";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
areasContext,
|
||||
authContext,
|
||||
configContext,
|
||||
connectionContext,
|
||||
devicesContext,
|
||||
@@ -101,6 +102,10 @@ export const contextMixin = <T extends Constructor<HassBaseEl>>(
|
||||
context: labelsContext,
|
||||
initialValue: [],
|
||||
}),
|
||||
auth: new ContextProvider(this, {
|
||||
context: authContext,
|
||||
initialValue: this.hass?.auth,
|
||||
}),
|
||||
};
|
||||
|
||||
protected hassConnected() {
|
||||
|
||||
@@ -32,7 +32,7 @@ export const dialogManagerMixin = <T extends Constructor<HassBaseEl>>(
|
||||
this.addEventListener("register-dialog", (e) =>
|
||||
this.registerDialog(e.detail)
|
||||
);
|
||||
makeDialogManager(this, this.shadowRoot!);
|
||||
makeDialogManager(this);
|
||||
}
|
||||
|
||||
protected registerDialog({
|
||||
@@ -44,10 +44,10 @@ export const dialogManagerMixin = <T extends Constructor<HassBaseEl>>(
|
||||
this.addEventListener(dialogShowEvent, (showEv) => {
|
||||
showDialog(
|
||||
this,
|
||||
this.shadowRoot!,
|
||||
dialogTag,
|
||||
(showEv as HASSDomEvent<unknown>).detail,
|
||||
dialogImport,
|
||||
undefined,
|
||||
addHistory
|
||||
);
|
||||
});
|
||||
|
||||
@@ -28,7 +28,6 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
||||
private async _handleMoreInfo(ev: HASSDomEvent<MoreInfoDialogParams>) {
|
||||
showDialog(
|
||||
this,
|
||||
this.shadowRoot!,
|
||||
"ha-more-info-dialog",
|
||||
{
|
||||
entityId: ev.detail.entityId,
|
||||
@@ -42,7 +41,8 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
|
||||
: false),
|
||||
data: ev.detail.data,
|
||||
},
|
||||
() => import("../dialogs/more-info/ha-more-info-dialog")
|
||||
() => import("../dialogs/more-info/ha-more-info-dialog"),
|
||||
ev.detail.parentElement
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { isSafari } from "./is_safari";
|
||||
|
||||
export const isIosApp = (hass: HomeAssistant): boolean =>
|
||||
!!hass.auth.external && isSafari;
|
||||
export const isIosApp = (
|
||||
authExternal: HomeAssistant["auth"]["external"]
|
||||
): boolean => !!authExternal && isSafari;
|
||||
|
||||
Reference in New Issue
Block a user