mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-29 00:53:50 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b37560aaa9 | ||
|
|
4ff8ca4f83 | ||
|
|
68d6f80f2b | ||
|
|
4fe356576a |
No files matched your search
@@ -0,0 +1,117 @@
|
||||
import type { ContextType } from "@lit/context";
|
||||
import { mdiContentCopy } from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { consume } from "../../common/decorators/consume";
|
||||
import { consumeEntityState } from "../../common/decorators/consume-context-entry";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../common/util/copy-clipboard";
|
||||
import {
|
||||
formattersContext,
|
||||
internationalizationContext,
|
||||
} from "../../data/context";
|
||||
import { showMoreInfoDialog } from "../../dialogs/more-info/show-ha-more-info-dialog";
|
||||
import { showToast } from "../../util/toast";
|
||||
import { HaActionBar } from "../ha-action-bar";
|
||||
import "../ha-button";
|
||||
import "../ha-icon-next";
|
||||
import "../ha-svg-icon";
|
||||
|
||||
/**
|
||||
* @element ha-entity-action-bar
|
||||
*
|
||||
* @summary Action bar for a selected entity, placed below the list of an
|
||||
* entity or target picker. Opens the more info dialog of the entity or copies
|
||||
* its entity ID.
|
||||
*
|
||||
* @cssprop --ha-action-bar-border-top - Top border of the bar. Defaults to `var(--ha-border-width-sm) solid var(--divider-color)`.
|
||||
*
|
||||
* @fires close-picker - Fired after an action, so the picker around the bar closes.
|
||||
*/
|
||||
@customElement("ha-entity-action-bar")
|
||||
export class HaEntityActionBar extends HaActionBar {
|
||||
@property({ attribute: "entity-id" }) public entityId?: string;
|
||||
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: ContextType<typeof internationalizationContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: formattersContext, subscribe: true })
|
||||
private _formatters!: ContextType<typeof formattersContext>;
|
||||
|
||||
@state()
|
||||
@consumeEntityState({ entityIdPath: ["entityId"] })
|
||||
private _stateObj?: HassEntity;
|
||||
|
||||
protected render() {
|
||||
const name = this._stateObj
|
||||
? this._formatters.formatEntityName(this._stateObj)
|
||||
: undefined;
|
||||
const viewLabel = name
|
||||
? this._i18n.localize(
|
||||
"ui.components.entity.entity-action-bar.view_entity",
|
||||
{ name }
|
||||
)
|
||||
: nothing;
|
||||
const copyIdLabel = name
|
||||
? this._i18n.localize(
|
||||
"ui.components.entity.entity-action-bar.copy_entity_id",
|
||||
{ name }
|
||||
)
|
||||
: nothing;
|
||||
|
||||
return html`
|
||||
<ha-button
|
||||
appearance="filled"
|
||||
variant="neutral"
|
||||
size="s"
|
||||
title=${viewLabel}
|
||||
accessible-label=${viewLabel}
|
||||
@click=${this._showMoreInfo}
|
||||
>
|
||||
${this._i18n.localize("ui.components.entity.entity-action-bar.view")}
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-button>
|
||||
<ha-button
|
||||
appearance="filled"
|
||||
variant="neutral"
|
||||
size="s"
|
||||
title=${copyIdLabel}
|
||||
accessible-label=${copyIdLabel}
|
||||
@click=${this._copyEntityId}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
${this._i18n.localize("ui.components.entity.entity-action-bar.copy_id")}
|
||||
</ha-button>
|
||||
`;
|
||||
}
|
||||
|
||||
private _showMoreInfo() {
|
||||
if (!this.entityId) {
|
||||
return;
|
||||
}
|
||||
const entityId = this.entityId;
|
||||
fireEvent(this, "close-picker", {
|
||||
afterClose: () => showMoreInfoDialog(this, { entityId }),
|
||||
});
|
||||
}
|
||||
|
||||
private async _copyEntityId() {
|
||||
if (!this.entityId) {
|
||||
return;
|
||||
}
|
||||
await copyToClipboard(this.entityId);
|
||||
showToast(this, {
|
||||
message: this._i18n.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
fireEvent(this, "close-picker");
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-entity-action-bar": HaEntityActionBar;
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import type { HaGenericPicker } from "../ha-generic-picker";
|
||||
import type { PickerComboBoxSearchFn } from "../ha-picker-combo-box";
|
||||
import type { PickerValueRenderer } from "../ha-picker-field";
|
||||
import "../ha-svg-icon";
|
||||
import "./ha-entity-action-bar";
|
||||
import "./state-badge";
|
||||
|
||||
const CREATE_ID = "___create-new-entity___";
|
||||
@@ -481,6 +482,14 @@ export class HaEntityPicker extends LitElement {
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
${
|
||||
!this.addButton && this.value && this._states[this.value]
|
||||
? html`<ha-entity-action-bar
|
||||
slot="footer"
|
||||
.entityId=${this.value}
|
||||
></ha-entity-action-bar>`
|
||||
: nothing
|
||||
}
|
||||
</ha-generic-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { tinykeys } from "tinykeys";
|
||||
import { consume } from "../common/decorators/consume";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { fireEvent, type HASSDomEvent } from "../common/dom/fire_event";
|
||||
import { configContext } from "../data/context";
|
||||
import { PickerMixin } from "../mixins/picker-mixin";
|
||||
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
|
||||
@@ -34,6 +34,11 @@ import type {
|
||||
import "./ha-picker-field";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
export interface ClosePickerParams {
|
||||
// Runs after the picker is closed
|
||||
afterClose?: () => void;
|
||||
}
|
||||
|
||||
@customElement("ha-generic-picker")
|
||||
export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
@property({ attribute: "aria-label" })
|
||||
@@ -134,6 +139,9 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
|
||||
@state() private _openedNarrow = false;
|
||||
|
||||
// Runs once the picker is closed and the browser has restored the focus
|
||||
private _afterClose?: () => void;
|
||||
|
||||
@state() private _unknownValue = false;
|
||||
|
||||
@state() private _selectedValue?: string;
|
||||
@@ -236,7 +244,7 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
aria-modal="true"
|
||||
aria-label=${this.ariaLabel || this.label || "Select option"}
|
||||
>
|
||||
${this._renderComboBox(true)}
|
||||
${this._renderComboBox(true)} ${this._renderFooter()}
|
||||
</ha-bottom-sheet>
|
||||
`
|
||||
: html`
|
||||
@@ -260,7 +268,7 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
aria-modal="true"
|
||||
aria-label=${this.ariaLabel || this.label || "Select option"}
|
||||
>
|
||||
${this._renderComboBox()}
|
||||
${this._renderComboBox()} ${this._renderFooter()}
|
||||
</wa-popover>
|
||||
`
|
||||
: nothing
|
||||
@@ -298,6 +306,13 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
// The wrapper keeps the bottom sheet's footer styles off the slotted content.
|
||||
private _renderFooter() {
|
||||
return html`<div>
|
||||
<slot name="footer" @close-picker=${this._handleClosePicker}></slot>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private _setUnknownValue = () => {
|
||||
const items = this.getItems();
|
||||
if (
|
||||
@@ -384,6 +399,9 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
this._selectedValue = undefined;
|
||||
this._unsubscribeTinyKeys?.();
|
||||
fireEvent(this, "picker-closed");
|
||||
const afterClose = this._afterClose;
|
||||
this._afterClose = undefined;
|
||||
afterClose?.();
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
@@ -425,6 +443,16 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
|
||||
});
|
||||
}
|
||||
|
||||
public close() {
|
||||
this._pickerWrapperOpen = false;
|
||||
}
|
||||
|
||||
private _handleClosePicker(ev: HASSDomEvent<ClosePickerParams | undefined>) {
|
||||
ev.stopPropagation();
|
||||
this._afterClose = ev.detail?.afterClose;
|
||||
this.close();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._handleResize();
|
||||
@@ -540,5 +568,6 @@ declare global {
|
||||
interface HASSDomEvents {
|
||||
"picker-closed": undefined;
|
||||
"picker-opened": undefined;
|
||||
"close-picker": ClosePickerParams | undefined;
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,7 @@ import {
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import { brandsUrl } from "../util/brands-url";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import "./entity/ha-entity-action-bar";
|
||||
import "./ha-button";
|
||||
import "./ha-generic-picker";
|
||||
import type { HaGenericPicker } from "./ha-generic-picker";
|
||||
@@ -556,6 +557,15 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
>
|
||||
${
|
||||
this._replaceTarget?.type === "entity" &&
|
||||
this.hass.states[this._replaceTarget.id]
|
||||
? html`<ha-entity-action-bar
|
||||
slot="footer"
|
||||
.entityId=${this._replaceTarget.id}
|
||||
></ha-entity-action-bar>`
|
||||
: nothing
|
||||
}
|
||||
</ha-generic-picker>
|
||||
${
|
||||
showExclude
|
||||
|
||||
@@ -843,6 +843,12 @@
|
||||
"create_helper": "Create a new {domain, select, \n undefined {} \n other {{domain} }\n }helper",
|
||||
"unknown": "Unknown entity selected"
|
||||
},
|
||||
"entity-action-bar": {
|
||||
"view": "View",
|
||||
"view_entity": "View {name}",
|
||||
"copy_id": "Copy ID",
|
||||
"copy_entity_id": "Copy ID of {name}"
|
||||
},
|
||||
"entity-name-picker": {
|
||||
"types": {
|
||||
"floor": "Floor",
|
||||
|
||||
Reference in new issue
Block a user