home-assistant-frontend/src/panels/config/entities/ha-config-entities.ts

1009 lines
32 KiB
TypeScript

import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
import {
mdiAlertCircle,
mdiCancel,
mdiDelete,
mdiEyeOff,
mdiFilterVariant,
mdiPencilOff,
mdiPlus,
mdiRestoreAlert,
mdiUndo,
} from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip";
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map";
import memoize from "memoize-one";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { navigate } from "../../../common/navigate";
import { LocalizeFunc } from "../../../common/translations/localize";
import { computeRTL } from "../../../common/util/compute_rtl";
import type {
DataTableColumnContainer,
RowClickedEvent,
SelectionChangedEvent,
} from "../../../components/data-table/ha-data-table";
import "../../../components/ha-button-menu";
import "../../../components/ha-check-list-item";
import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import {
AreaRegistryEntry,
subscribeAreaRegistry,
} from "../../../data/area_registry";
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
import {
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../../../data/device_registry";
import { UNAVAILABLE } from "../../../data/entity";
import {
computeEntityRegistryName,
EntityRegistryEntry,
removeEntityRegistryEntry,
subscribeEntityRegistry,
updateEntityRegistryEntry,
} from "../../../data/entity_registry";
import { domainToName } from "../../../data/integration";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
import {
hideMoreInfoDialog,
showMoreInfoDialog,
} from "../../../dialogs/more-info/show-ha-more-info-dialog";
import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage-data-table";
import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant, Route } from "../../../types";
import { configSections } from "../ha-panel-config";
import "../integrations/ha-integration-overflow-menu";
export interface StateEntity
extends Omit<EntityRegistryEntry, "id" | "unique_id"> {
readonly?: boolean;
selectable?: boolean;
id?: string;
unique_id?: string;
}
export interface EntityRow extends StateEntity {
entity?: HassEntity;
unavailable: boolean;
restored: boolean;
status: string;
area?: string;
}
@customElement("ha-config-entities")
export class HaConfigEntities extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public isWide!: boolean;
@property({ type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public route!: Route;
@state() private _entities?: EntityRegistryEntry[];
@state() private _devices?: DeviceRegistryEntry[];
@state() private _areas: AreaRegistryEntry[] = [];
@state() private _stateEntities: StateEntity[] = [];
@property() public _entries?: ConfigEntry[];
@state() private _showDisabled = false;
@state() private _showHidden = false;
@state() private _showUnavailable = true;
@state() private _showReadOnly = true;
@state() private _filter: string = history.state?.filter || "";
@state() private _numHiddenEntities = 0;
@state() private _searchParms = new URLSearchParams(window.location.search);
@state() private _selectedEntities: string[] = [];
@query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable;
private _activeFilters = memoize(
(
filters: URLSearchParams,
localize: LocalizeFunc,
entries?: ConfigEntry[]
): string[] | undefined => {
const filterTexts: string[] = [];
filters.forEach((value, key) => {
switch (key) {
case "config_entry": {
// If we are requested to show the entities for a given config entry,
// also show the disabled ones by default.
this._showDisabled = true;
if (!entries) {
this._loadConfigEntries();
break;
}
const configEntry = entries.find(
(entry) => entry.entry_id === value
);
if (!configEntry) {
break;
}
const integrationName = domainToName(localize, configEntry.domain);
filterTexts.push(
`${this.hass.localize(
"ui.panel.config.integrations.integration"
)} "${integrationName}${
integrationName !== configEntry.title
? `: ${configEntry.title}`
: ""
}"`
);
break;
}
}
});
return filterTexts.length ? filterTexts : undefined;
}
);
private _columns = memoize(
(narrow, _language, showDisabled): DataTableColumnContainer<EntityRow> => ({
icon: {
title: "",
label: this.hass.localize(
"ui.panel.config.entities.picker.headers.state_icon"
),
type: "icon",
template: (_, entry: EntityRow) => html`
<ha-state-icon
title=${ifDefined(entry.entity?.state)}
slot="item-icon"
.state=${entry.entity}
></ha-state-icon>
`,
},
name: {
main: true,
title: this.hass.localize(
"ui.panel.config.entities.picker.headers.name"
),
sortable: true,
filterable: true,
direction: "asc",
grows: true,
template: narrow
? (name, entity: EntityRow) =>
html`
${name}<br />
<div class="secondary">
${entity.entity_id} |
${this.hass.localize(`component.${entity.platform}.title`) ||
entity.platform}
</div>
`
: undefined,
},
entity_id: {
title: this.hass.localize(
"ui.panel.config.entities.picker.headers.entity_id"
),
hidden: narrow,
sortable: true,
filterable: true,
width: "25%",
},
platform: {
title: this.hass.localize(
"ui.panel.config.entities.picker.headers.integration"
),
hidden: narrow,
sortable: true,
filterable: true,
width: "20%",
template: (platform) =>
this.hass.localize(`component.${platform}.title`) || platform,
},
area: {
title: this.hass.localize(
"ui.panel.config.entities.picker.headers.area"
),
sortable: true,
hidden: narrow,
filterable: true,
width: "15%",
},
disabled_by: {
title: this.hass.localize(
"ui.panel.config.entities.picker.headers.disabled_by"
),
sortable: true,
hidden: narrow || !showDisabled,
filterable: true,
width: "15%",
template: (disabled_by: EntityRegistryEntry["disabled_by"]) =>
disabled_by === null
? "—"
: this.hass.localize(`config_entry.disabled_by.${disabled_by}`),
},
status: {
title: this.hass.localize(
"ui.panel.config.entities.picker.headers.status"
),
type: "icon",
sortable: true,
filterable: true,
width: "68px",
template: (_status, entity: EntityRow) =>
entity.unavailable ||
entity.disabled_by ||
entity.hidden_by ||
entity.readonly
? html`
<div
tabindex="0"
style="display:inline-block; position: relative;"
>
<ha-svg-icon
style=${styleMap({
color: entity.unavailable ? "var(--error-color)" : "",
})}
.path=${entity.restored
? mdiRestoreAlert
: entity.unavailable
? mdiAlertCircle
: entity.disabled_by
? mdiCancel
: entity.hidden_by
? mdiEyeOff
: mdiPencilOff}
></ha-svg-icon>
<paper-tooltip animation-delay="0" position="left">
${entity.restored
? this.hass.localize(
"ui.panel.config.entities.picker.status.restored"
)
: entity.unavailable
? this.hass.localize(
"ui.panel.config.entities.picker.status.unavailable"
)
: entity.disabled_by
? this.hass.localize(
"ui.panel.config.entities.picker.status.disabled"
)
: entity.hidden_by
? this.hass.localize(
"ui.panel.config.entities.picker.status.hidden"
)
: this.hass.localize(
"ui.panel.config.entities.picker.status.readonly"
)}
</paper-tooltip>
</div>
`
: "—",
},
})
);
private _filteredEntitiesAndDomains = memoize(
(
entities: StateEntity[],
devices: DeviceRegistryEntry[] | undefined,
areas: AreaRegistryEntry[] | undefined,
stateEntities: StateEntity[],
filters: URLSearchParams,
showDisabled: boolean,
showUnavailable: boolean,
showReadOnly: boolean,
showHidden: boolean,
entries?: ConfigEntry[]
) => {
const result: EntityRow[] = [];
// If nothing gets filtered, this is our correct count of entities
let startLength = entities.length + stateEntities.length;
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
const deviceLookup: { [deviceId: string]: DeviceRegistryEntry } = {};
if (areas) {
for (const area of areas) {
areaLookup[area.area_id] = area;
}
if (devices) {
for (const device of devices) {
deviceLookup[device.id] = device;
}
}
}
let filteredEntities = showReadOnly
? entities.concat(stateEntities)
: entities;
const filteredDomains: string[] = [];
filters.forEach((value, key) => {
if (key === "config_entry") {
filteredEntities = filteredEntities.filter(
(entity) => entity.config_entry_id === value
);
// If we have an active filter and `showReadOnly` is true, the length of `entities` is correct.
// If however, the read-only entities were not added before, we need to check how many would
// have matched the active filter and add that number to the count.
startLength = filteredEntities.length;
if (!showReadOnly) {
startLength += stateEntities.filter(
(entity) => entity.config_entry_id === value
).length;
}
if (!entries) {
this._loadConfigEntries();
return;
}
const configEntry = entries.find((entry) => entry.entry_id === value);
if (configEntry) {
filteredDomains.push(configEntry.domain);
}
}
});
if (!showDisabled) {
filteredEntities = filteredEntities.filter(
(entity) => !entity.disabled_by
);
}
if (!showHidden) {
filteredEntities = filteredEntities.filter(
(entity) => !entity.hidden_by
);
}
for (const entry of filteredEntities) {
const entity = this.hass.states[entry.entity_id];
const unavailable = entity?.state === UNAVAILABLE;
const restored = entity?.attributes.restored === true;
const areaId = entry.area_id ?? deviceLookup[entry.device_id!]?.area_id;
const area = areaId ? areaLookup[areaId] : undefined;
if (!showUnavailable && unavailable) {
continue;
}
result.push({
...entry,
entity,
name: computeEntityRegistryName(
this.hass!,
entry as EntityRegistryEntry
),
unavailable,
restored,
area: area ? area.name : "—",
status: restored
? this.hass.localize(
"ui.panel.config.entities.picker.status.restored"
)
: unavailable
? this.hass.localize(
"ui.panel.config.entities.picker.status.unavailable"
)
: entry.disabled_by
? this.hass.localize(
"ui.panel.config.entities.picker.status.disabled"
)
: this.hass.localize("ui.panel.config.entities.picker.status.ok"),
});
}
this._numHiddenEntities = startLength - result.length;
return { filteredEntities: result, filteredDomains: filteredDomains };
}
);
public constructor() {
super();
window.addEventListener("location-changed", () => {
if (
window.location.search.substring(1) !== this._searchParms.toString()
) {
this._searchParms = new URLSearchParams(window.location.search);
}
});
window.addEventListener("popstate", () => {
if (
window.location.search.substring(1) !== this._searchParms.toString()
) {
this._searchParms = new URLSearchParams(window.location.search);
}
});
}
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entities = entities;
}),
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
this._devices = devices;
}),
subscribeAreaRegistry(this.hass.connection, (areas) => {
this._areas = areas;
}),
];
}
public disconnectedCallback() {
super.disconnectedCallback();
hideMoreInfoDialog(this);
}
protected render(): TemplateResult {
if (!this.hass || this._entities === undefined) {
return html` <hass-loading-screen></hass-loading-screen> `;
}
const activeFilters = this._activeFilters(
this._searchParms,
this.hass.localize,
this._entries
);
const { filteredEntities, filteredDomains } =
this._filteredEntitiesAndDomains(
this._entities,
this._devices,
this._areas,
this._stateEntities,
this._searchParms,
this._showDisabled,
this._showUnavailable,
this._showReadOnly,
this._showHidden,
this._entries
);
const includeZHAFab = filteredDomains.includes("zha");
return html`
<hass-tabs-subpage-data-table
.hass=${this.hass}
.narrow=${this.narrow}
.backPath=${this._searchParms.has("historyBack")
? undefined
: "/config"}
.route=${this.route}
.tabs=${configSections.devices}
.columns=${this._columns(
this.narrow,
this.hass.language,
this._showDisabled
)}
.data=${filteredEntities}
.activeFilters=${activeFilters}
.numHidden=${this._numHiddenEntities}
.hideFilterMenu=${this._selectedEntities.length > 0}
.searchLabel=${this.hass.localize(
"ui.panel.config.entities.picker.search"
)}
.hiddenLabel=${this.hass.localize(
"ui.panel.config.entities.picker.filter.hidden_entities",
"number",
this._numHiddenEntities
)}
.filter=${this._filter}
selectable
clickable
@selection-changed=${this._handleSelectionChanged}
@clear-filter=${this._clearFilter}
@search-changed=${this._handleSearchChange}
@row-click=${this._openEditEntry}
id="entity_id"
.hasFab=${includeZHAFab}
>
<ha-integration-overflow-menu
.hass=${this.hass}
slot="toolbar-icon"
></ha-integration-overflow-menu>
${this._selectedEntities.length
? html`
<div
class=${classMap({
"header-toolbar": this.narrow,
"table-header": !this.narrow,
})}
slot="header"
>
<p class="selected-txt">
${this.hass.localize(
"ui.panel.config.entities.picker.selected",
"number",
this._selectedEntities.length
)}
</p>
<div class="header-btns">
${!this.narrow
? html`
<mwc-button @click=${this._enableSelected}
>${this.hass.localize(
"ui.panel.config.entities.picker.enable_selected.button"
)}</mwc-button
>
<mwc-button @click=${this._disableSelected}
>${this.hass.localize(
"ui.panel.config.entities.picker.disable_selected.button"
)}</mwc-button
>
<mwc-button @click=${this._hideSelected}
>${this.hass.localize(
"ui.panel.config.entities.picker.hide_selected.button"
)}</mwc-button
>
<mwc-button
@click=${this._removeSelected}
class="warning"
>${this.hass.localize(
"ui.panel.config.entities.picker.remove_selected.button"
)}</mwc-button
>
`
: html`
<ha-icon-button
id="enable-btn"
@click=${this._enableSelected}
.path=${mdiUndo}
.label=${this.hass.localize("ui.common.enable")}
></ha-icon-button>
<paper-tooltip animation-delay="0" for="enable-btn">
${this.hass.localize(
"ui.panel.config.entities.picker.enable_selected.button"
)}
</paper-tooltip>
<ha-icon-button
id="disable-btn"
@click=${this._disableSelected}
.path=${mdiCancel}
.label=${this.hass.localize("ui.common.disable")}
></ha-icon-button>
<paper-tooltip animation-delay="0" for="disable-btn">
${this.hass.localize(
"ui.panel.config.entities.picker.disable_selected.button"
)}
</paper-tooltip>
<ha-icon-button
id="hide-btn"
@click=${this._hideSelected}
.path=${mdiEyeOff}
.label=${this.hass.localize("ui.common.hide")}
></ha-icon-button>
<paper-tooltip animation-delay="0" for="hide-btn">
${this.hass.localize(
"ui.panel.config.entities.picker.hide_selected.button"
)}
</paper-tooltip>
<ha-icon-button
class="warning"
id="remove-btn"
@click=${this._removeSelected}
.path=${mdiDelete}
.label=${this.hass.localize("ui.common.remove")}
></ha-icon-button>
<paper-tooltip animation-delay="0" for="remove-btn">
${this.hass.localize(
"ui.panel.config.entities.picker.remove_selected.button"
)}
</paper-tooltip>
`}
</div>
</div>
`
: html`
<ha-button-menu slot="filter-menu" corner="BOTTOM_START" multi>
<ha-icon-button
slot="trigger"
.label=${this.hass!.localize(
"ui.panel.config.entities.picker.filter.filter"
)}
.path=${mdiFilterVariant}
></ha-icon-button>
${this.narrow && activeFilters?.length
? html`<mwc-list-item @click=${this._clearFilter}
>${this.hass.localize(
"ui.components.data-table.filtering_by"
)}
${activeFilters.join(", ")}
<span class="clear">Clear</span></mwc-list-item
>`
: ""}
<ha-check-list-item
@request-selected=${this._showDisabledChanged}
.selected=${this._showDisabled}
left
>
${this.hass!.localize(
"ui.panel.config.entities.picker.filter.show_disabled"
)}
</ha-check-list-item>
<ha-check-list-item
@request-selected=${this._showHiddenChanged}
.selected=${this._showHidden}
left
>
${this.hass!.localize(
"ui.panel.config.entities.picker.filter.show_hidden"
)}
</ha-check-list-item>
<ha-check-list-item
@request-selected=${this._showRestoredChanged}
graphic="control"
.selected=${this._showUnavailable}
left
>
${this.hass!.localize(
"ui.panel.config.entities.picker.filter.show_unavailable"
)}
</ha-check-list-item>
<ha-check-list-item
@request-selected=${this._showReadOnlyChanged}
graphic="control"
.selected=${this._showReadOnly}
left
>
${this.hass!.localize(
"ui.panel.config.entities.picker.filter.show_readonly"
)}
</ha-check-list-item>
</ha-button-menu>
`}
${includeZHAFab
? html`<a href="/config/zha/add" slot="fab">
<ha-fab
.label=${this.hass.localize("ui.panel.config.zha.add_device")}
extended
?rtl=${computeRTL(this.hass)}
>
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
</ha-fab>
</a>`
: html``}
</hass-tabs-subpage-data-table>
`;
}
public willUpdate(changedProps): void {
super.willUpdate(changedProps);
const oldHass = changedProps.get("hass");
let changed = false;
if (!this.hass || !this._entities) {
return;
}
if (changedProps.has("hass") || changedProps.has("_entities")) {
const stateEntities: StateEntity[] = [];
const regEntityIds = new Set(
this._entities.map((entity) => entity.entity_id)
);
for (const entityId of Object.keys(this.hass.states)) {
if (regEntityIds.has(entityId)) {
continue;
}
if (
!oldHass ||
this.hass.states[entityId] !== oldHass.states[entityId]
) {
changed = true;
}
stateEntities.push({
name: computeStateName(this.hass.states[entityId]),
entity_id: entityId,
platform: computeDomain(entityId),
disabled_by: null,
hidden_by: null,
area_id: null,
config_entry_id: null,
device_id: null,
icon: null,
readonly: true,
selectable: false,
entity_category: null,
has_entity_name: false,
});
}
if (changed) {
this._stateEntities = stateEntities;
}
}
}
private _showDisabledChanged(ev: CustomEvent<RequestSelectedDetail>) {
if (ev.detail.source !== "property") {
return;
}
this._showDisabled = ev.detail.selected;
}
private _showHiddenChanged(ev: CustomEvent<RequestSelectedDetail>) {
if (ev.detail.source !== "property") {
return;
}
this._showHidden = ev.detail.selected;
}
private _showRestoredChanged(ev: CustomEvent<RequestSelectedDetail>) {
if (ev.detail.source !== "property") {
return;
}
this._showUnavailable = ev.detail.selected;
}
private _showReadOnlyChanged(ev: CustomEvent<RequestSelectedDetail>) {
if (ev.detail.source !== "property") {
return;
}
this._showReadOnly = ev.detail.selected;
}
private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value;
history.replaceState({ filter: this._filter }, "");
}
private _handleSelectionChanged(
ev: HASSDomEvent<SelectionChangedEvent>
): void {
this._selectedEntities = ev.detail.value;
}
private async _enableSelected() {
showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.entities.picker.enable_selected.confirm_title",
"number",
this._selectedEntities.length
),
text: this.hass.localize(
"ui.panel.config.entities.picker.enable_selected.confirm_text"
),
confirmText: this.hass.localize("ui.common.enable"),
dismissText: this.hass.localize("ui.common.cancel"),
confirm: async () => {
let require_restart = false;
let reload_delay = 0;
await Promise.all(
this._selectedEntities.map(async (entity) => {
const result = await updateEntityRegistryEntry(this.hass, entity, {
disabled_by: null,
});
if (result.require_restart) {
require_restart = true;
}
if (result.reload_delay) {
reload_delay = Math.max(reload_delay, result.reload_delay);
}
})
);
this._clearSelection();
// If restart is required by any entity, show a dialog.
// Otherwise, show a dialog explaining that some patience is needed
if (require_restart) {
showAlertDialog(this, {
text: this.hass.localize(
"ui.dialogs.entity_registry.editor.enabled_restart_confirm"
),
});
} else if (reload_delay) {
showAlertDialog(this, {
text: this.hass.localize(
"ui.dialogs.entity_registry.editor.enabled_delay_confirm",
"delay",
reload_delay
),
});
}
},
});
}
private _disableSelected() {
showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.entities.picker.disable_selected.confirm_title",
"number",
this._selectedEntities.length
),
text: this.hass.localize(
"ui.panel.config.entities.picker.disable_selected.confirm_text"
),
confirmText: this.hass.localize("ui.common.disable"),
dismissText: this.hass.localize("ui.common.cancel"),
confirm: () => {
this._selectedEntities.forEach((entity) =>
updateEntityRegistryEntry(this.hass, entity, {
disabled_by: "user",
})
);
this._clearSelection();
},
});
}
private _hideSelected() {
showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.entities.picker.hide_selected.confirm_title",
"number",
this._selectedEntities.length
),
text: this.hass.localize(
"ui.panel.config.entities.picker.hide_selected.confirm_text"
),
confirmText: this.hass.localize("ui.common.hide"),
dismissText: this.hass.localize("ui.common.cancel"),
confirm: () => {
this._selectedEntities.forEach((entity) =>
updateEntityRegistryEntry(this.hass, entity, {
hidden_by: "user",
})
);
this._clearSelection();
},
});
}
private _removeSelected() {
const removeableEntities = this._selectedEntities.filter((entity) => {
const stateObj = this.hass.states[entity];
return stateObj?.attributes.restored;
});
showConfirmationDialog(this, {
title: this.hass.localize(
`ui.panel.config.entities.picker.remove_selected.confirm_${
removeableEntities.length !== this._selectedEntities.length
? "partly_"
: ""
}title`,
"number",
removeableEntities.length
),
text:
removeableEntities.length === this._selectedEntities.length
? this.hass.localize(
"ui.panel.config.entities.picker.remove_selected.confirm_text"
)
: this.hass.localize(
"ui.panel.config.entities.picker.remove_selected.confirm_partly_text",
"removable",
removeableEntities.length,
"selected",
this._selectedEntities.length
),
confirmText: this.hass.localize("ui.common.remove"),
dismissText: this.hass.localize("ui.common.cancel"),
confirm: () => {
removeableEntities.forEach((entity) =>
removeEntityRegistryEntry(this.hass, entity)
);
this._clearSelection();
},
});
}
private _clearSelection() {
this._dataTable.clearSelection();
}
private _openEditEntry(ev: CustomEvent): void {
const entityId = (ev.detail as RowClickedEvent).id;
showMoreInfoDialog(this, {
entityId,
tab: "settings",
});
}
private async _loadConfigEntries() {
this._entries = await getConfigEntries(this.hass);
}
private _clearFilter() {
if (
this._activeFilters(this._searchParms, this.hass.localize, this._entries)
) {
navigate(window.location.pathname, { replace: true });
}
this._showDisabled = true;
this._showReadOnly = true;
this._showUnavailable = true;
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
hass-loading-screen {
--app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color);
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
height: 56px;
background-color: var(--mdc-text-field-fill-color, whitesmoke);
border-bottom: 1px solid
var(--mdc-text-field-idle-line-color, rgba(0, 0, 0, 0.42));
box-sizing: border-box;
}
.header-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
color: var(--secondary-text-color);
position: relative;
top: -4px;
}
.selected-txt {
font-weight: bold;
padding-left: 16px;
padding-inline-start: 16px;
direction: var(--direction);
}
.table-header .selected-txt {
margin-top: 20px;
}
.header-toolbar .selected-txt {
font-size: 16px;
}
.header-toolbar .header-btns {
margin-right: -12px;
margin-inline-end: -12px;
direction: var(--direction);
}
.header-btns {
display: flex;
}
.header-btns > mwc-button,
.header-btns > ha-icon-button {
margin: 8px;
}
ha-button-menu {
margin-left: 8px;
}
.clear {
color: var(--primary-color);
padding-left: 8px;
padding-inline-start: 8px;
text-transform: uppercase;
direction: var(--direction);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-entities": HaConfigEntities;
}
}