Compare commits

..

1 Commits

Author SHA1 Message Date
Paul Bottein c080ebbf46 Add user selector with multiple and system option 2023-10-31 11:19:16 +01:00
244 changed files with 8503 additions and 9352 deletions
+1 -1
View File
@@ -1 +1 @@
lts/iron
18
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+874
View File
File diff suppressed because one or more lines are too long
-893
View File
File diff suppressed because one or more lines are too long
+7 -5
View File
@@ -1,9 +1,11 @@
compressionLevel: mixed
defaultSemverRangePrefix: ""
enableGlobalCache: false
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.0.1.cjs
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: "@yarnpkg/plugin-typescript"
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-3.6.4.cjs
+5 -1
View File
@@ -12,7 +12,11 @@ module.exports.sourceMapURL = () => {
};
// Files from NPM Packages that should not be imported
module.exports.ignorePackages = () => [];
// eslint-disable-next-line unused-imports/no-unused-vars
module.exports.ignorePackages = ({ latestBuild }) => [
// Part of yaml.js and only used for !!js functions that we don't use
require.resolve("esprima"),
];
// Files from NPM packages that we should replace with empty file
module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
+12 -16
View File
@@ -3,7 +3,7 @@ import { mdiCast, mdiCastConnected } from "@mdi/js";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-listbox/paper-listbox";
import { Auth, Connection } from "home-assistant-js-websocket";
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { CastManager } from "../../../../src/cast/cast_manager";
import {
@@ -22,9 +22,8 @@ import "../../../../src/components/ha-svg-icon";
import {
getLegacyLovelaceCollection,
getLovelaceCollection,
LovelaceConfig,
} from "../../../../src/data/lovelace";
import { isStrategyDashboard } from "../../../../src/data/lovelace/config/types";
import { LovelaceViewConfig } from "../../../../src/data/lovelace/config/view";
import "../../../../src/layouts/hass-loading-screen";
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
import "./hc-layout";
@@ -39,10 +38,10 @@ class HcCast extends LitElement {
@state() private askWrite = false;
@state() private lovelaceViews?: LovelaceViewConfig[] | null;
@state() private lovelaceConfig?: LovelaceConfig | null;
protected render(): TemplateResult {
if (this.lovelaceViews === undefined) {
if (this.lovelaceConfig === undefined) {
return html`<hass-loading-screen no-toolbar></hass-loading-screen>`;
}
@@ -87,10 +86,9 @@ class HcCast extends LitElement {
attr-for-selected="data-path"
.selected=${this.castManager.status.lovelacePath || ""}
>
${(
this.lovelaceViews ?? [
generateDefaultViewConfig({}, {}, {}, {}, () => ""),
]
${(this.lovelaceConfig
? this.lovelaceConfig.views
: [generateDefaultViewConfig({}, {}, {}, {}, () => "")]
).map(
(view, idx) => html`
<paper-icon-item
@@ -138,15 +136,11 @@ class HcCast extends LitElement {
llColl.refresh().then(
() => {
llColl.subscribe((config) => {
if (isStrategyDashboard(config)) {
this.lovelaceViews = null;
} else {
this.lovelaceViews = config.views;
}
this.lovelaceConfig = config;
});
},
async () => {
this.lovelaceViews = null;
this.lovelaceConfig = null;
}
);
@@ -165,7 +159,9 @@ class HcCast extends LitElement {
toggleAttribute(
this,
"hide-icons",
this.lovelaceViews ? !this.lovelaceViews.some((view) => view.icon) : true
this.lovelaceConfig
? !this.lovelaceConfig.views.some((view) => view.icon)
: true
);
}
+4 -2
View File
@@ -1,5 +1,7 @@
import { LovelaceCardConfig } from "../../../../src/data/lovelace/config/card";
import { LovelaceConfig } from "../../../../src/data/lovelace/config/types";
import {
LovelaceCardConfig,
LovelaceConfig,
} from "../../../../src/data/lovelace";
import { castContext } from "../cast_context";
export const castDemoLovelace: () => LovelaceConfig = () => {
+1 -1
View File
@@ -1,7 +1,7 @@
import { html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { mockHistory } from "../../../../demo/src/stubs/history";
import { LovelaceConfig } from "../../../../src/data/lovelace/config/types";
import { LovelaceConfig } from "../../../../src/data/lovelace";
import {
MockHomeAssistant,
provideHass,
+2 -3
View File
@@ -1,7 +1,7 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { LovelaceConfig } from "../../../../src/data/lovelace/config/types";
import { LovelaceConfig } from "../../../../src/data/lovelace";
import { Lovelace } from "../../../../src/panels/lovelace/types";
import "../../../../src/panels/lovelace/views/hui-view";
import { HomeAssistant } from "../../../../src/types";
@@ -14,8 +14,7 @@ import "./hc-launch-screen";
class HcLovelace extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false })
public lovelaceConfig!: LovelaceConfig;
@property({ attribute: false }) public lovelaceConfig!: LovelaceConfig;
@property() public viewPath?: string | number;
+12 -29
View File
@@ -21,26 +21,18 @@ import {
import { atLeastVersion } from "../../../../src/common/config/version";
import { isNavigationClick } from "../../../../src/common/dom/is-navigation-click";
import {
fetchResources,
getLegacyLovelaceCollection,
getLovelaceCollection,
} from "../../../../src/data/lovelace";
import {
isStrategyDashboard,
LegacyLovelaceConfig,
LovelaceConfig,
LovelaceDashboardStrategyConfig,
} from "../../../../src/data/lovelace/config/types";
import { fetchResources } from "../../../../src/data/lovelace/resource";
} from "../../../../src/data/lovelace";
import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/load-resources";
import { HassElement } from "../../../../src/state/hass-element";
import { castContext } from "../cast_context";
import "./hc-launch-screen";
const DEFAULT_CONFIG: LovelaceDashboardStrategyConfig = {
strategy: {
type: "original-states",
},
};
const DEFAULT_STRATEGY = "original-states";
let resourcesLoaded = false;
@customElement("hc-main")
@@ -101,7 +93,7 @@ export class HcMain extends HassElement {
.lovelaceConfig=${this._lovelaceConfig}
.viewPath=${this._lovelacePath}
.urlPath=${this._urlPath}
@config-refresh=${this._generateDefaultLovelaceConfig}
@config-refresh=${this._generateLovelaceConfig}
></hc-lovelace>
`;
}
@@ -292,20 +284,9 @@ export class HcMain extends HassElement {
// configuration.
try {
await llColl.refresh();
this._unsubLovelace = llColl.subscribe(async (rawConfig) => {
if (isStrategyDashboard(rawConfig)) {
const { generateLovelaceDashboardStrategy } = await import(
"../../../../src/panels/lovelace/strategies/get-strategy"
);
const config = await generateLovelaceDashboardStrategy(
rawConfig.strategy,
this.hass!
);
this._handleNewLovelaceConfig(config);
} else {
this._handleNewLovelaceConfig(rawConfig);
}
});
this._unsubLovelace = llColl.subscribe((lovelaceConfig) =>
this._handleNewLovelaceConfig(lovelaceConfig)
);
} catch (err: any) {
if (
atLeastVersion(this.hass.connection.haVersion, 0, 107) &&
@@ -319,7 +300,7 @@ export class HcMain extends HassElement {
}
// Generate a Lovelace config.
this._unsubLovelace = () => undefined;
await this._generateDefaultLovelaceConfig();
await this._generateLovelaceConfig();
}
}
if (!resourcesLoaded) {
@@ -335,13 +316,15 @@ export class HcMain extends HassElement {
this._sendStatus();
}
private async _generateDefaultLovelaceConfig() {
private async _generateLovelaceConfig() {
const { generateLovelaceDashboardStrategy } = await import(
"../../../../src/panels/lovelace/strategies/get-strategy"
);
this._handleNewLovelaceConfig(
await generateLovelaceDashboardStrategy(
DEFAULT_CONFIG.strategy,
{
type: DEFAULT_STRATEGY,
},
this.hass!
)
);
+1 -1
View File
@@ -1,5 +1,5 @@
import { LocalizeFunc } from "../../../src/common/translations/localize";
import { LovelaceConfig } from "../../../src/data/lovelace/config/types";
import { LovelaceConfig } from "../../../src/data/lovelace";
import { Entity } from "../../../src/fake_data/entity";
export interface DemoConfig {
+1 -1
View File
@@ -4,7 +4,7 @@ import { customElement, property, state } from "lit/decorators";
import { until } from "lit/directives/until";
import "../../../src/components/ha-card";
import "../../../src/components/ha-circular-progress";
import { LovelaceCardConfig } from "../../../src/data/lovelace/config/card";
import { LovelaceCardConfig } from "../../../src/data/lovelace";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
import { Lovelace, LovelaceCard } from "../../../src/panels/lovelace/types";
import {
+13 -3
View File
@@ -74,9 +74,19 @@
<body>
<div id="ha-launch-screen">
<div class="ha-launch-screen-spacer"></div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
<path fill="#18BCF2" d="M240 224.762a15 15 0 0 1-15 15H15a15 15 0 0 1-15-15v-90c0-8.25 4.77-19.769 10.61-25.609l98.78-98.7805c5.83-5.83 15.38-5.83 21.21 0l98.79 98.7895c5.83 5.83 10.61 17.36 10.61 25.61v90-.01Z"/>
<path fill="#F2F4F9" d="m107.27 239.762-40.63-40.63c-2.09.72-4.32 1.13-6.64 1.13-11.3 0-20.5-9.2-20.5-20.5s9.2-20.5 20.5-20.5 20.5 9.2 20.5 20.5c0 2.33-.41 4.56-1.13 6.65l31.63 31.63v-115.88c-6.8-3.3395-11.5-10.3195-11.5-18.3895 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5c0 8.07-4.7 15.05-11.5 18.3895v81.27l31.46-31.46c-.62-1.96-.96-4.04-.96-6.2 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5-9.2 20.5-20.5 20.5c-2.5 0-4.88-.47-7.09-1.29L129 208.892v30.88z"/>
<svg
viewBox="0 0 240 240"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M240 224.762C240 233.012 233.25 239.762 225 239.762H15C6.75 239.762 0 233.012 0 224.762V134.762C0 126.512 4.77 114.993 10.61 109.153L109.39 10.3725C115.22 4.5425 124.77 4.5425 130.6 10.3725L229.39 109.162C235.22 114.992 240 126.522 240 134.772V224.772V224.762Z"
fill="#F2F4F9"
/>
<path
d="M229.39 109.153L130.61 10.3725C124.78 4.5425 115.23 4.5425 109.4 10.3725L10.61 109.153C4.78 114.983 0 126.512 0 134.762V224.762C0 233.012 6.75 239.762 15 239.762H107.27L66.64 199.132C64.55 199.852 62.32 200.262 60 200.262C48.7 200.262 39.5 191.062 39.5 179.762C39.5 168.462 48.7 159.262 60 159.262C71.3 159.262 80.5 168.462 80.5 179.762C80.5 182.092 80.09 184.322 79.37 186.412L111 218.042V102.162C104.2 98.8225 99.5 91.8425 99.5 83.7725C99.5 72.4725 108.7 63.2725 120 63.2725C131.3 63.2725 140.5 72.4725 140.5 83.7725C140.5 91.8425 135.8 98.8225 129 102.162V183.432L160.46 151.972C159.84 150.012 159.5 147.932 159.5 145.772C159.5 134.472 168.7 125.272 180 125.272C191.3 125.272 200.5 134.472 200.5 145.772C200.5 157.072 191.3 166.272 180 166.272C177.5 166.272 175.12 165.802 172.91 164.982L129 208.892V239.772H225C233.25 239.772 240 233.022 240 224.772V134.772C240 126.522 235.23 115.002 229.39 109.162V109.153Z"
fill="#18BCF2"
/>
</svg>
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div>
</div>
+27 -65
View File
@@ -1,10 +1,8 @@
import { css, html, LitElement, TemplateResult, nothing } from "lit";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/chips/ha-chip-set";
import "../../../../src/components/chips/ha-assist-chip";
import "../../../../src/components/chips/ha-input-chip";
import "../../../../src/components/chips/ha-filter-chip";
import "../../../../src/components/ha-chip";
import "../../../../src/components/ha-chip-set";
import "../../../../src/components/ha-svg-icon";
import { mdiHomeAssistant } from "../../../../src/resources/home-assistant-logo-svg";
@@ -12,6 +10,10 @@ const chips: {
icon?: string;
content?: string;
}[] = [
{},
{
icon: mdiHomeAssistant,
},
{
content: "Content",
},
@@ -27,73 +29,31 @@ export class DemoHaChips extends LitElement {
return html`
<ha-card header="ha-chip demo">
<div class="card-content">
<p>Action chip</p>
${chips.map(
(chip) => html`
<ha-chip .hasIcon=${chip.icon !== undefined}>
${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>`
: ""}
${chip.content}
</ha-chip>
`
)}
</div>
</ha-card>
<ha-card header="ha-chip-set demo">
<div class="card-content">
<ha-chip-set>
${chips.map(
(chip) => html`
<ha-assist-chip .label=${chip.content}>
${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>`
: nothing}
</ha-assist-chip>
`
)}
${chips.map(
(chip) => html`
<ha-assist-chip .label=${chip.content} selected>
${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>`
: nothing}
</ha-assist-chip>
`
)}
</ha-chip-set>
<p>Filter chip</p>
<ha-chip-set>
${chips.map(
(chip) => html`
<ha-filter-chip .label=${chip.content}>
${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>`
: nothing}
</ha-filter-chip>
`
)}
${chips.map(
(chip) => html`
<ha-filter-chip .label=${chip.content} selected>
${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>`
: nothing}
</ha-filter-chip>
`
)}
</ha-chip-set>
<p>Input chip</p>
<ha-chip-set>
${chips.map(
(chip) => html`
<ha-input-chip .label=${chip.content}>
<ha-chip .hasIcon=${chip.icon !== undefined}>
${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>`
: ""}
${chip.content}
</ha-input-chip>
`
)}
${chips.map(
(chip) => html`
<ha-input-chip .label=${chip.content} selected>
${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>`
: nothing}
</ha-input-chip>
</ha-chip>
`
)}
</ha-chip-set>
@@ -108,10 +68,12 @@ export class DemoHaChips extends LitElement {
max-width: 600px;
margin: 24px auto;
}
ha-chip {
margin-bottom: 4px;
}
.card-content {
display: flex;
flex-direction: column;
align-items: flex-start;
}
`;
}
@@ -11,7 +11,6 @@ const buttons: {
min?: number;
max?: number;
step?: number;
unit?: string;
class?: string;
}[] = [
{
@@ -30,11 +29,6 @@ const buttons: {
label: "Custom",
class: "custom",
},
{
id: "unit",
label: "With unit",
unit: "m",
},
];
@customElement("demo-components-ha-control-number-buttons")
@@ -56,7 +50,6 @@ export class DemoHarControlNumberButtons extends LitElement {
<pre>Config: ${JSON.stringify(config)}</pre>
<ha-control-number-buttons
.value=${this.value}
.unit=${config.unit}
.min=${config.min}
.max=${config.max}
.step=${config.step}
@@ -9,7 +9,6 @@ const sliders: {
id: string;
label: string;
mode?: "start" | "end" | "cursor";
unit?: string;
class?: string;
}[] = [
{
@@ -32,21 +31,18 @@ const sliders: {
label: "Slider (start mode) and custom style",
mode: "start",
class: "custom",
unit: "mm",
},
{
id: "slider-end-custom",
label: "Slider (end mode) and custom style",
mode: "end",
class: "custom",
unit: "mm",
},
{
id: "slider-cursor-custom",
label: "Slider (cursor mode) and custom style",
mode: "cursor",
class: "custom",
unit: "mm",
},
];
@@ -97,7 +93,6 @@ export class DemoHaBarSlider extends LitElement {
@value-changed=${this.handleValueChanged}
@slider-moved=${this.handleSliderMoved}
aria-labelledby=${id}
.unit=${config.unit}
>
</ha-control-slider>
</div>
@@ -119,7 +114,6 @@ export class DemoHaBarSlider extends LitElement {
@value-changed=${this.handleValueChanged}
@slider-moved=${this.handleSliderMoved}
aria-label=${label}
.unit=${config.unit}
>
</ha-control-slider>
`;
@@ -5,22 +5,9 @@ subtitle: Dialogs provide important prompts in a user flow.
# Material Design 3
Our dialogs are based on the latest version of Material Design. Please note that we have made some well-considered adjustments to these guideliness. Specs and guidelines can be found on its [website](https://m3.material.io/components/dialogs/overview).
Our dialogs are based on the latest version of Material Design. Specs and guidelines can be found on its [website](https://m3.material.io/components/dialogs/overview).
# Guidelines
## Design
- Dialogs have a max width of 560px. Alert and confirmation dialogs got a fixed width of 320px. If you need more width, consider a dedicated page instead.
- The close X-icon is on the top left, on all screen sizes. Except for alert and confirmation dialogs, they only have buttons and no X-icon. This is different compared to the Material guideliness.
- Dialogs can't be closed with ESC or clicked outside of the dialog when there is a form that the user needs to fill out. Instead it will animate "no" by a little shake.
- Extra icon buttons are on the top right, for example help, settings and expand dialog. More than 2 icon buttons, they will be in an overflow menu.
- The submit button is grouped with a cancel button at the bottom right, on all screen sizes. Fullscreen mobile dialogs have them sticky at the bottom.
- Keep the labels short, for example `Save`, `Delete`, `Enable`.
- Dialog with actions must always have a discard button. On desktop a `Cancel` button and X-icon, on mobile only the X-icon.
- Destructive actions should be a red warning button.
- Alert or confirmation dialogs only have buttons and no X-icon.
- Try to avoid three buttons in one dialog. Especially when you leave the dialog task unfinished.
# Highlighted guidelines
## Content
@@ -30,6 +17,14 @@ Our dialogs are based on the latest version of Material Design. Please note that
- If users become unsure, they read the description. Make sure this explains what will happen.
- Strive for minimalism.
## Buttons and X-icon
- Keep the labels short, for example `Save`, `Delete`, `Enable`.
- Dialog with actions must always have a discard button. On desktop a `Cancel` button and X-icon, on mobile only the X-icon.
- Destructive actions should be a red warning button.
- Alert or confirmation dialogs only have buttons and no X-icon.
- Try to avoid three buttons in one dialog. Especially when you leave the dialog task unfinished.
## Example
### Confirmation dialog
+1
View File
@@ -10,6 +10,7 @@ import { computeStateDisplay } from "../../../../src/common/entity/compute_state
import "../../../../src/components/data-table/ha-data-table";
import type { DataTableColumnContainer } from "../../../../src/components/data-table/ha-data-table";
import "../../../../src/components/entity/state-badge";
import "../../../../src/components/ha-chip";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../../src/types";
+1 -1
View File
@@ -2,7 +2,7 @@ import "@material/mwc-button";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../../src/components/ha-card";
import { ActionHandlerEvent } from "../../../../src/data/lovelace/action_handler";
import { ActionHandlerEvent } from "../../../../src/data/lovelace";
import { actionHandler } from "../../../../src/panels/lovelace/common/directives/action-handler-directive";
@customElement("demo-misc-util-long-press")
+91 -150
View File
@@ -31,8 +31,8 @@ import { navigate } from "../../../../src/common/navigate";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card";
import "../../../../src/components/chips/ha-chip-set";
import "../../../../src/components/chips/ha-assist-chip";
import "../../../../src/components/ha-chip";
import "../../../../src/components/ha-chip-set";
import "../../../../src/components/ha-markdown";
import "../../../../src/components/ha-settings-row";
import "../../../../src/components/ha-svg-icon";
@@ -78,7 +78,6 @@ import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-has
import { hassioStyle } from "../../resources/hassio-style";
import "../../update-available/update-available-card";
import { addonArchIsSupported, extractChangelog } from "../../util/addon";
import { capitalizeFirstLetter } from "../../../../src/common/string/capitalize-first-letter";
const STAGE_ICON = {
stable: mdiCheckCircle,
@@ -235,32 +234,28 @@ class HassioAddonInfo extends LitElement {
<ha-chip-set class="capabilities">
${this.addon.stage !== "stable"
? html`
<ha-assist-chip
filled
class=${classMap({
yellow: this.addon.stage === "experimental",
red: this.addon.stage === "deprecated",
})}
@click=${this._showMoreInfo}
id="stage"
.label=${capitalizeFirstLetter(
this.supervisor.localize(
`addon.dashboard.capability.stages.${this.addon.stage}`
)
)}
? html` <ha-chip
hasIcon
class=${classMap({
yellow: this.addon.stage === "experimental",
red: this.addon.stage === "deprecated",
})}
@click=${this._showMoreInfo}
id="stage"
>
<ha-svg-icon
slot="icon"
.path=${STAGE_ICON[this.addon.stage]}
>
<ha-svg-icon
slot="icon"
.path=${STAGE_ICON[this.addon.stage]}
>
</ha-svg-icon>
</ha-assist-chip>
`
</ha-svg-icon>
${this.supervisor.localize(
`addon.dashboard.capability.stages.${this.addon.stage}`
)}
</ha-chip>`
: ""}
<ha-assist-chip
filled
<ha-chip
hasIcon
class=${classMap({
green: Number(this.addon.rating) >= 6,
yellow: [3, 4, 5].includes(Number(this.addon.rating)),
@@ -268,183 +263,138 @@ class HassioAddonInfo extends LitElement {
})}
@click=${this._showMoreInfo}
id="rating"
.label=${capitalizeFirstLetter(
this.supervisor.localize(
"addon.dashboard.capability.label.rating"
)
)}
>
<ha-svg-icon slot="icon" .path=${RATING_ICON[this.addon.rating]}>
</ha-svg-icon>
</ha-assist-chip>
${this.supervisor.localize(
"addon.dashboard.capability.label.rating"
)}
</ha-chip>
${this.addon.host_network
? html`
<ha-assist-chip
filled
<ha-chip
hasIcon
@click=${this._showMoreInfo}
id="host_network"
.label=${capitalizeFirstLetter(
this.supervisor.localize(
"addon.dashboard.capability.label.host"
)
)}
>
<ha-svg-icon slot="icon" .path=${mdiNetwork}> </ha-svg-icon>
</ha-assist-chip>
${this.supervisor.localize(
"addon.dashboard.capability.label.host"
)}
</ha-chip>
`
: ""}
${this.addon.full_access
? html`
<ha-assist-chip
filled
<ha-chip
hasIcon
@click=${this._showMoreInfo}
id="full_access"
.label=${capitalizeFirstLetter(
this.supervisor.localize(
"addon.dashboard.capability.label.hardware"
)
)}
>
<ha-svg-icon slot="icon" .path=${mdiChip}></ha-svg-icon>
</ha-assist-chip>
${this.supervisor.localize(
"addon.dashboard.capability.label.hardware"
)}
</ha-chip>
`
: ""}
${this.addon.homeassistant_api
? html`
<ha-assist-chip
filled
<ha-chip
hasIcon
@click=${this._showMoreInfo}
id="homeassistant_api"
.label=${capitalizeFirstLetter(
this.supervisor.localize(
"addon.dashboard.capability.label.core"
)
)}
>
<ha-svg-icon
slot="icon"
.path=${mdiHomeAssistant}
></ha-svg-icon>
</ha-assist-chip>
${this.supervisor.localize(
"addon.dashboard.capability.label.core"
)}
</ha-chip>
`
: ""}
${this._computeHassioApi
? html`
<ha-assist-chip
filled
@click=${this._showMoreInfo}
id="hassio_api"
.label=${capitalizeFirstLetter(
this.supervisor.localize(
`addon.dashboard.capability.role.${this.addon.hassio_role}`
) || this.addon.hassio_role
)}
>
<ha-chip hasIcon @click=${this._showMoreInfo} id="hassio_api">
<ha-svg-icon
slot="icon"
.path=${mdiHomeAssistant}
></ha-svg-icon>
</ha-assist-chip>
${this.supervisor.localize(
`addon.dashboard.capability.role.${this.addon.hassio_role}`
) || this.addon.hassio_role}
</ha-chip>
`
: ""}
${this.addon.docker_api
? html`
<ha-assist-chip
filled
@click=${this._showMoreInfo}
id="docker_api"
.label=${capitalizeFirstLetter(
this.supervisor.localize(
"addon.dashboard.capability.label.docker"
)
)}
>
<ha-chip hasIcon @click=${this._showMoreInfo} id="docker_api">
<ha-svg-icon slot="icon" .path=${mdiDocker}></ha-svg-icon>
</ha-assist-chip>
${this.supervisor.localize(
"addon.dashboard.capability.label.docker"
)}
</ha-chip>
`
: ""}
${this.addon.host_pid
? html`
<ha-assist-chip
filled
@click=${this._showMoreInfo}
id="host_pid"
.label=${capitalizeFirstLetter(
this.supervisor.localize(
"addon.dashboard.capability.label.host_pid"
)
)}
>
<ha-chip hasIcon @click=${this._showMoreInfo} id="host_pid">
<ha-svg-icon slot="icon" .path=${mdiPound}></ha-svg-icon>
</ha-assist-chip>
${this.supervisor.localize(
"addon.dashboard.capability.label.host_pid"
)}
</ha-chip>
`
: ""}
${this.addon.apparmor !== "default"
? html`
<ha-assist-chip
filled
<ha-chip
hasIcon
@click=${this._showMoreInfo}
class=${this._computeApparmorClassName}
id="apparmor"
.label=${capitalizeFirstLetter(
this.supervisor.localize(
"addon.dashboard.capability.label.apparmor"
)
)}
>
<ha-svg-icon slot="icon" .path=${mdiShield}></ha-svg-icon>
</ha-assist-chip>
${this.supervisor.localize(
"addon.dashboard.capability.label.apparmor"
)}
</ha-chip>
`
: ""}
${this.addon.auth_api
? html`
<ha-assist-chip
filled
@click=${this._showMoreInfo}
id="auth_api"
.label=${capitalizeFirstLetter(
this.supervisor.localize(
"addon.dashboard.capability.label.auth"
)
)}
>
<ha-chip hasIcon @click=${this._showMoreInfo} id="auth_api">
<ha-svg-icon slot="icon" .path=${mdiKey}></ha-svg-icon>
</ha-assist-chip>
${this.supervisor.localize(
"addon.dashboard.capability.label.auth"
)}
</ha-chip>
`
: ""}
${this.addon.ingress
? html`
<ha-assist-chip
filled
@click=${this._showMoreInfo}
id="ingress"
.label=${capitalizeFirstLetter(
this.supervisor.localize(
"addon.dashboard.capability.label.ingress"
)
)}
>
<ha-chip hasIcon @click=${this._showMoreInfo} id="ingress">
<ha-svg-icon
slot="icon"
.path=${mdiCursorDefaultClickOutline}
></ha-svg-icon>
</ha-assist-chip>
${this.supervisor.localize(
"addon.dashboard.capability.label.ingress"
)}
</ha-chip>
`
: ""}
${this.addon.signed
? html`
<ha-assist-chip
filled
@click=${this._showMoreInfo}
id="signed"
.label=${capitalizeFirstLetter(
this.supervisor.localize(
"addon.dashboard.capability.label.signed"
)
)}
>
<ha-chip hasIcon @click=${this._showMoreInfo} id="signed">
<ha-svg-icon slot="icon" .path=${mdiLinkLock}></ha-svg-icon>
</ha-assist-chip>
${this.supervisor.localize(
"addon.dashboard.capability.label.signed"
)}
</ha-chip>
`
: ""}
</ha-chip-set>
@@ -1235,35 +1185,23 @@ class HassioAddonInfo extends LitElement {
.description a {
color: var(--primary-color);
}
ha-assist-chip {
--md-sys-color-primary: var(--text-primary-color);
--md-sys-color-on-surface: var(--text-primary-color);
--ha-assist-chip-filled-container-color: var(--primary-color);
ha-chip {
text-transform: capitalize;
--ha-chip-text-color: var(--text-primary-color);
--ha-chip-background-color: var(--primary-color);
}
.red {
--ha-assist-chip-filled-container-color: var(
--label-badge-red,
#df4c1e
);
--ha-chip-background-color: var(--label-badge-red, #df4c1e);
}
.blue {
--ha-assist-chip-filled-container-color: var(
--label-badge-blue,
#039be5
);
--ha-chip-background-color: var(--label-badge-blue, #039be5);
}
.green {
--ha-assist-chip-filled-container-color: var(
--label-badge-green,
#0da035
);
--ha-chip-background-color: var(--label-badge-green, #0da035);
}
.yellow {
--ha-assist-chip-filled-container-color: var(
--label-badge-yellow,
#f4b400
);
--ha-chip-background-color: var(--label-badge-yellow, #f4b400);
}
.capabilities {
margin-bottom: 16px;
@@ -1322,6 +1260,9 @@ class HassioAddonInfo extends LitElement {
}
@media (max-width: 720px) {
ha-chip {
line-height: 36px;
}
.addon-options {
max-width: 100%;
}
@@ -1,9 +1,8 @@
import Fuse from "fuse.js";
import type { IFuseOptions } from "fuse.js";
import { StoreAddon } from "../../../src/data/supervisor/store";
export function filterAndSort(addons: StoreAddon[], filter: string) {
const options: IFuseOptions<StoreAddon> = {
const options: Fuse.IFuseOptions<StoreAddon> = {
keys: ["name", "description", "slug"],
isCaseSensitive: false,
minMatchCharLength: 2,
+54 -54
View File
@@ -27,22 +27,22 @@
"dependencies": {
"@babel/runtime": "7.23.2",
"@braintree/sanitize-url": "6.0.4",
"@codemirror/autocomplete": "6.11.0",
"@codemirror/autocomplete": "6.10.2",
"@codemirror/commands": "6.3.0",
"@codemirror/language": "6.9.2",
"@codemirror/legacy-modes": "6.3.3",
"@codemirror/search": "6.5.4",
"@codemirror/state": "6.3.1",
"@codemirror/view": "6.22.0",
"@codemirror/view": "6.21.4",
"@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "6.11.2",
"@formatjs/intl-displaynames": "6.6.2",
"@formatjs/intl-datetimeformat": "6.11.1",
"@formatjs/intl-displaynames": "6.6.1",
"@formatjs/intl-getcanonicallocales": "2.3.0",
"@formatjs/intl-listformat": "7.5.1",
"@formatjs/intl-locale": "3.4.1",
"@formatjs/intl-numberformat": "8.8.1",
"@formatjs/intl-pluralrules": "5.2.8",
"@formatjs/intl-relativetimeformat": "11.2.8",
"@formatjs/intl-listformat": "7.5.0",
"@formatjs/intl-locale": "3.4.0",
"@formatjs/intl-numberformat": "8.8.0",
"@formatjs/intl-pluralrules": "5.2.7",
"@formatjs/intl-relativetimeformat": "11.2.7",
"@fullcalendar/core": "6.1.9",
"@fullcalendar/daygrid": "6.1.9",
"@fullcalendar/interaction": "6.1.9",
@@ -51,9 +51,9 @@
"@fullcalendar/timegrid": "6.1.9",
"@lezer/highlight": "1.1.6",
"@lit-labs/context": "0.4.1",
"@lit-labs/motion": "1.0.6",
"@lit-labs/observers": "2.0.2",
"@lit-labs/virtualizer": "2.0.10",
"@lit-labs/motion": "1.0.4",
"@lit-labs/observers": "2.0.1",
"@lit-labs/virtualizer": "2.0.7",
"@lrnwebcomponents/simple-tooltip": "7.0.18",
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
@@ -94,8 +94,8 @@
"@polymer/paper-toast": "3.0.1",
"@polymer/polymer": "3.5.1",
"@thomasloven/round-slider": "0.6.0",
"@vaadin/combo-box": "24.2.2",
"@vaadin/vaadin-themable-mixin": "24.2.2",
"@vaadin/combo-box": "24.2.1",
"@vaadin/vaadin-themable-mixin": "24.2.1",
"@vibrant/color": "3.2.1-alpha.1",
"@vibrant/core": "3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
@@ -105,28 +105,28 @@
"app-datepicker": "5.1.1",
"chart.js": "4.4.0",
"comlink": "4.4.1",
"core-js": "3.33.2",
"core-js": "3.33.1",
"cropperjs": "1.6.1",
"date-fns": "2.30.0",
"date-fns-tz": "2.0.0",
"deep-clone-simple": "1.1.1",
"deep-freeze": "0.0.1",
"fuse.js": "7.0.0",
"fuse.js": "6.6.2",
"google-timezones-json": "1.2.0",
"hls.js": "1.4.12",
"home-assistant-js-websocket": "9.1.0",
"idb-keyval": "6.2.1",
"intl-messageformat": "10.5.5",
"intl-messageformat": "10.5.4",
"js-yaml": "4.1.0",
"leaflet": "1.9.4",
"leaflet-draw": "1.0.4",
"lit": "2.8.0",
"luxon": "3.4.3",
"marked": "9.1.6",
"marked": "9.1.2",
"memoize-one": "6.0.0",
"node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "0.3.2",
"punycode": "2.3.1",
"punycode": "2.3.0",
"qr-scanner": "1.4.2",
"qrcode": "1.5.3",
"resize-observer-polyfill": "1.5.1",
@@ -138,10 +138,10 @@
"tinykeys": "2.1.0",
"tsparticles-engine": "2.12.0",
"tsparticles-preset-links": "2.12.0",
"ua-parser-js": "1.0.37",
"ua-parser-js": "1.0.36",
"unfetch": "5.0.0",
"vis-data": "7.1.8",
"vis-network": "9.1.9",
"vis-data": "7.1.7",
"vis-network": "9.1.8",
"vue": "2.7.15",
"vue2-daterange-picker": "0.6.8",
"weekstart": "2.0.0",
@@ -154,12 +154,12 @@
"xss": "1.0.14"
},
"devDependencies": {
"@babel/core": "7.23.3",
"@babel/plugin-proposal-decorators": "7.23.3",
"@babel/plugin-transform-runtime": "7.23.3",
"@babel/preset-env": "7.23.3",
"@babel/preset-typescript": "7.23.3",
"@bundle-stats/plugin-webpack-filter": "4.8.0",
"@babel/core": "7.23.2",
"@babel/plugin-proposal-decorators": "7.23.2",
"@babel/plugin-transform-runtime": "7.23.2",
"@babel/preset-env": "7.23.2",
"@babel/preset-typescript": "7.23.2",
"@bundle-stats/plugin-webpack-filter": "4.7.8",
"@koa/cors": "4.0.0",
"@lokalise/node-api": "12.0.0",
"@octokit/auth-oauth-device": "6.0.1",
@@ -170,32 +170,33 @@
"@rollup/plugin-commonjs": "25.0.7",
"@rollup/plugin-json": "6.0.1",
"@rollup/plugin-node-resolve": "15.2.3",
"@rollup/plugin-replace": "5.0.5",
"@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.12",
"@types/chromecast-caf-sender": "1.0.8",
"@rollup/plugin-replace": "5.0.4",
"@types/babel__plugin-transform-runtime": "7.9.4",
"@types/chromecast-caf-receiver": "6.0.11",
"@types/chromecast-caf-sender": "1.0.7",
"@types/esprima": "4.0.5",
"@types/glob": "8.1.0",
"@types/html-minifier-terser": "7.0.2",
"@types/js-yaml": "4.0.9",
"@types/leaflet": "1.9.8",
"@types/leaflet-draw": "1.0.10",
"@types/luxon": "3.3.4",
"@types/mocha": "10.0.4",
"@types/qrcode": "1.5.5",
"@types/serve-handler": "6.1.4",
"@types/sortablejs": "1.15.5",
"@types/tar": "6.1.9",
"@types/ua-parser-js": "0.7.39",
"@types/html-minifier-terser": "7.0.1",
"@types/js-yaml": "4.0.8",
"@types/leaflet": "1.9.7",
"@types/leaflet-draw": "1.0.9",
"@types/luxon": "3.3.3",
"@types/mocha": "10.0.3",
"@types/qrcode": "1.5.4",
"@types/serve-handler": "6.1.3",
"@types/sortablejs": "1.15.4",
"@types/tar": "6.1.7",
"@types/ua-parser-js": "0.7.38",
"@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "6.10.0",
"@typescript-eslint/parser": "6.10.0",
"@typescript-eslint/eslint-plugin": "6.9.0",
"@typescript-eslint/parser": "6.9.0",
"@web/dev-server": "0.1.38",
"@web/dev-server-rollup": "0.4.1",
"babel-loader": "9.1.3",
"babel-plugin-template-html-minifier": "4.1.0",
"chai": "4.3.10",
"del": "7.1.0",
"eslint": "8.53.0",
"eslint": "8.52.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-airbnb-typescript": "17.1.0",
"eslint-config-prettier": "9.0.0",
@@ -203,9 +204,10 @@
"eslint-plugin-disable": "2.0.3",
"eslint-plugin-import": "2.29.0",
"eslint-plugin-lit": "1.10.1",
"eslint-plugin-lit-a11y": "4.1.1",
"eslint-plugin-lit-a11y": "4.1.0",
"eslint-plugin-unused-imports": "3.0.0",
"eslint-plugin-wc": "2.0.4",
"esprima": "4.0.1",
"fancy-log": "2.0.0",
"fs-extra": "11.1.1",
"glob": "10.3.10",
@@ -219,7 +221,7 @@
"husky": "8.0.3",
"instant-mocha": "1.5.2",
"jszip": "3.10.1",
"lint-staged": "15.1.0",
"lint-staged": "15.0.2",
"lit-analyzer": "2.0.1",
"lodash.template": "4.5.0",
"magic-string": "0.30.5",
@@ -234,12 +236,12 @@
"rollup-plugin-terser": "7.0.2",
"rollup-plugin-visualizer": "5.9.2",
"serve-handler": "6.1.5",
"sinon": "17.0.1",
"sinon": "17.0.0",
"source-map-url": "0.4.1",
"systemjs": "6.14.2",
"tar": "6.2.0",
"terser-webpack-plugin": "5.3.9",
"ts-lit-plugin": "2.0.1",
"ts-lit-plugin": "2.0.0",
"typescript": "5.2.2",
"vinyl-buffer": "1.0.1",
"vinyl-source-stream": "2.0.0",
@@ -255,11 +257,9 @@
"resolutions": {
"@polymer/polymer": "patch:@polymer/polymer@3.5.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
"@material/mwc-button@^0.25.3": "^0.27.0",
"lit": "2.8.0",
"clean-css": "5.3.2",
"@lit/reactive-element": "1.6.3",
"lit@^2.7.4 || ^3.0.0": "^2.7.4",
"sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch",
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
},
"packageManager": "yarn@4.0.1"
"packageManager": "yarn@3.6.4"
}
+23 -1
View File
@@ -1 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="640" viewBox="0 0 240 240"><path d="M120.001 0c-3.787 0-7.573 1.499-10.444 4.49L12.211 105.905a25.921 25.921 0 0 0-2.098 2.501 35.25 35.25 0 0 0-1.96 2.942c-3.01 5.021-5.285 11.318-6.074 16.898-.03.21-.088.429-.11.636a27.355 27.355 0 0 0-.213 3.317v93.023a14.78 14.78 90 0 0 14.78 14.78h90.92L67.422 198.29a20.2 20.2 90 1 1 12.542-13.06l31.17 32.474V98.726a20.2 20.2 90 1 1 17.734 0v83.44l31.001-32.299a20.2 20.2 90 1 1 12.267 13.357l-43.269 45.082V240h94.9a14.479 14.479 90 0 0 14.478-14.479v-93.314c0-1.059-.069-2.168-.214-3.314-.7-5.73-3.06-12.327-6.183-17.537a35.801 35.801 0 0 0-1.955-2.937 26.271 26.271 0 0 0-2.102-2.506L130.444 4.486C127.573 1.494 123.786-.002 120.001 0"/></svg>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="480.000000pt" height="480.000000pt" viewBox="0 0 480.000000 480.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,480.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M2313 4666 c-23 -7 -56 -23 -75 -34 -47 -30 -2059 -2048 -2095 -2102
-45 -67 -77 -135 -109 -230 l-29 -85 0 -995 0 -995 27 -51 c31 -59 93 -118
152 -145 39 -18 83 -19 1001 -19 l960 0 -406 405 c-395 395 -406 406 -433 395
-15 -5 -63 -10 -107 -10 -429 0 -566 577 -181 767 67 34 86 38 164 42 105 4
165 -13 246 -67 113 -74 175 -190 176 -327 1 -44 -3 -96 -7 -115 l-8 -35 316
-315 315 -315 0 1160 -1 1160 -51 35 c-260 177 -226 567 62 704 82 39 209 48
293 21 239 -78 354 -352 242 -575 -32 -63 -89 -125 -141 -156 l-44 -26 0 -811
0 -812 315 315 c218 217 313 320 309 330 -14 35 -16 134 -4 190 26 122 111
227 230 284 82 39 209 48 293 21 115 -38 214 -130 258 -242 19 -46 23 -78 24
-153 0 -86 -3 -101 -32 -163 -40 -84 -118 -163 -198 -202 -49 -23 -77 -29
-150 -33 -50 -2 -108 1 -130 7 l-40 11 -437 -438 -438 -437 0 -307 0 -308 998
0 c981 0 998 1 1042 21 58 26 115 81 148 144 l27 50 0 995 0 995 -33 95 c-72
209 -6 135 -1147 1278 -840 843 -1040 1037 -1082 1059 -64 31 -159 39 -220 19z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 747 B

After

Width:  |  Height:  |  Size: 1.4 KiB

+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20231030.0"
version = "20231027.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
+1 -13
View File
@@ -1,6 +1,5 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"configMigration": true,
"extends": [
":ignoreModulesAndTests",
":label(Dependencies)",
@@ -11,7 +10,7 @@
"group:recommended",
"npm:unpublishSafe"
],
"enabledManagers": ["npm", "nvm"],
"enabledManagers": ["npm"],
"postUpdateOptions": ["yarnDedupeHighest"],
"lockFileMaintenance": {
"description": ["Run after patch releases but before next beta"],
@@ -29,22 +28,11 @@
"matchPackageNames": ["vue"],
"allowedVersions": "< 3"
},
{
"description": "Group MDI packages",
"groupName": "Material Design Icons",
"matchPackageNames": ["@mdi/js", "@mdi/svg"]
},
{
"description": "Group tsparticles engine and presets",
"groupName": "tsparticles",
"matchPackageNames": ["tsparticles-engine"],
"matchPackagePrefixes": ["tsparticles-preset-"]
},
{
"description": "Group and temporarily disable WDS packages",
"groupName": "Web Dev Server",
"matchPackagePrefixes": ["@web/dev-server"],
"enabled": false
}
]
}
+23 -5
View File
@@ -8,6 +8,7 @@ import "../components/ha-alert";
import "../components/ha-checkbox";
import { computeInitialHaFormData } from "../components/ha-form/compute-initial-ha-form-data";
import "../components/ha-formfield";
import "../components/ha-markdown";
import { AuthProvider, autocompleteLoginFields } from "../data/auth";
import {
DataEntryFlowStep,
@@ -181,13 +182,24 @@ export class HaAuthFlow extends LitElement {
case "abort":
return html`
${this.localize("ui.panel.page-authorize.abort_intro")}:
${this.localize(
`ui.panel.page-authorize.form.providers.${step.handler[0]}.abort.${step.reason}`
)}
<ha-markdown
allowsvg
breaks
.content=${this.localize(
`ui.panel.page-authorize.form.providers.${step.handler[0]}.abort.${step.reason}`
)}
></ha-markdown>
`;
case "form":
return html`
${this._computeStepDescription(step)}
${this._computeStepDescription(step)
? html`
<ha-markdown
breaks
.content=${this._computeStepDescription(step)}
></ha-markdown>
`
: nothing}
<ha-auth-form
.data=${this._stepData}
.schema=${autocompleteLoginFields(step.data_schema)}
@@ -302,7 +314,13 @@ export class HaAuthFlow extends LitElement {
private _computeStepDescription(step: DataEntryFlowStepForm) {
const resourceKey =
`ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.description` as const;
return this.localize(resourceKey, step.description_placeholders);
const args: string[] = [];
const placeholders = step.description_placeholders || {};
Object.keys(placeholders).forEach((key) => {
args.push(key);
args.push(placeholders[key]);
});
return this.localize(resourceKey, ...args);
}
private _computeLabelCallback(step: DataEntryFlowStepForm) {
+2 -2
View File
@@ -58,10 +58,10 @@ const matchMaxScale = (
return outputColors.map((value) => Math.round(value * factor));
};
export const mired2kelvin = (miredTemperature: number) =>
const mired2kelvin = (miredTemperature: number) =>
Math.floor(1000000 / miredTemperature);
export const kelvin2mired = (kelvintTemperature: number) =>
const kelvin2mired = (kelvintTemperature: number) =>
Math.floor(1000000 / kelvintTemperature);
export const rgbww2rgb = (
+11 -4
View File
@@ -18,7 +18,6 @@ import { blankBeforePercent } from "../translations/blank_before_percent";
import { LocalizeFunc } from "../translations/localize";
import { computeDomain } from "./compute_domain";
import { computeStateDomain } from "./compute_state_domain";
import { blankBeforeUnit } from "../translations/blank_before_unit";
export const computeAttributeValueDisplay = (
localize: LocalizeFunc,
@@ -56,12 +55,20 @@ export const computeAttributeValueDisplay = (
unit = getWeatherUnit(config, stateObj as WeatherEntity, attribute);
}
if (TEMPERATURE_ATTRIBUTES.has(attribute)) {
unit = config.unit_system.temperature;
if (unit === "%") {
return `${formattedValue}${blankBeforePercent(locale)}${unit}`;
}
if (unit === "°") {
return `${formattedValue}${unit}`;
}
if (unit) {
return `${formattedValue}${blankBeforeUnit(unit, locale)}${unit}`;
return `${formattedValue} ${unit}`;
}
if (TEMPERATURE_ATTRIBUTES.has(attribute)) {
return `${formattedValue} ${config.unit_system.temperature}`;
}
return formattedValue;
+10 -14
View File
@@ -3,13 +3,13 @@ import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
import { FrontendLocaleData, TimeZone } from "../../data/translation";
import {
UPDATE_SUPPORT_PROGRESS,
updateIsInstallingFromAttributes,
UPDATE_SUPPORT_PROGRESS,
} from "../../data/update";
import { HomeAssistant } from "../../types";
import {
UNIT_TO_MILLISECOND_CONVERT,
formatDuration,
UNIT_TO_MILLISECOND_CONVERT,
} from "../datetime/duration";
import { formatDate } from "../datetime/format_date";
import { formatDateTime } from "../datetime/format_date_time";
@@ -19,10 +19,10 @@ import {
getNumberFormatOptions,
isNumericFromAttributes,
} from "../number/format_number";
import { blankBeforePercent } from "../translations/blank_before_percent";
import { LocalizeFunc } from "../translations/localize";
import { computeDomain } from "./compute_domain";
import { supportsFeatureFromAttributes } from "./supports-feature";
import { blankBeforeUnit } from "../translations/blank_before_unit";
export const computeStateDisplaySingleEntity = (
localize: LocalizeFunc,
@@ -108,20 +108,16 @@ export const computeStateDisplayFromEntityAttributes = (
// fallback to default
}
}
const value = formatNumber(
const unit = !attributes.unit_of_measurement
? ""
: attributes.unit_of_measurement === "%"
? blankBeforePercent(locale) + "%"
: ` ${attributes.unit_of_measurement}`;
return `${formatNumber(
state,
locale,
getNumberFormatOptions({ state, attributes } as HassEntity, entity)
);
const unit = attributes.unit_of_measurement;
if (unit) {
return `${value}${blankBeforeUnit(unit)}${unit}`;
}
return value;
)}${unit}`;
}
const domain = computeDomain(entityId);
@@ -1,15 +0,0 @@
import { FrontendLocaleData } from "../../data/translation";
import { blankBeforePercent } from "./blank_before_percent";
export const blankBeforeUnit = (
unit: string,
localeOptions?: FrontendLocaleData
): string => {
if (unit === "°") {
return "";
}
if (localeOptions && unit === "%") {
return blankBeforePercent(localeOptions);
}
return " ";
};
-1
View File
@@ -23,7 +23,6 @@ export function computeDirectionStyles(isRTL: boolean, element: LitElement) {
}
export function setDirectionStyles(direction: string, element: LitElement) {
document.dir = direction;
element.style.direction = direction;
element.style.setProperty("--direction", direction);
element.style.setProperty(
+1 -1
View File
@@ -134,7 +134,7 @@ _adapters._date.override({
this.options.config
);
case "week":
return formatDateVeryShort(
return formatDate(
new Date(time),
this.options.locale,
this.options.config
+1 -15
View File
@@ -73,8 +73,6 @@ export class StatisticsChart extends LitElement {
@property({ type: Boolean }) public isLoadingData = false;
@property() public period?: string;
@state() private _chartData: ChartData = { datasets: [] };
@state() private _statisticIds: string[] = [];
@@ -94,12 +92,7 @@ export class StatisticsChart extends LitElement {
}
public willUpdate(changedProps: PropertyValues) {
if (
!this.hasUpdated ||
changedProps.has("unit") ||
changedProps.has("period") ||
changedProps.has("chartType")
) {
if (!this.hasUpdated || changedProps.has("unit")) {
this._createOptions();
}
if (
@@ -167,7 +160,6 @@ export class StatisticsChart extends LitElement {
},
},
ticks: {
source: this.chartType === "bar" ? "data" : undefined,
maxRotation: 0,
sampleSize: 5,
autoSkipPadding: 20,
@@ -181,12 +173,6 @@ export class StatisticsChart extends LitElement {
},
time: {
tooltipFormat: "datetime",
unit:
this.chartType === "bar" &&
this.period &&
["hour", "day", "week", "month"].includes(this.period)
? this.period
: undefined,
},
},
y: {
-53
View File
@@ -1,53 +0,0 @@
import { MdAssistChip } from "@material/web/chips/assist-chip";
import { css, html } from "lit";
import { customElement, property } from "lit/decorators";
@customElement("ha-assist-chip")
export class HaAssistChip extends MdAssistChip {
@property({ type: Boolean, reflect: true }) filled = false;
static override styles = [
...super.styles,
css`
:host {
--md-sys-color-primary: var(--primary-text-color);
--md-sys-color-on-surface: var(--primary-text-color);
--md-assist-chip-container-shape: 16px;
--md-assist-chip-outline-color: var(--outline-color);
--md-assist-chip-label-text-weight: 400;
--ha-assist-chip-filled-container-color: rgba(
var(--rgb-primary-text-color),
0.15
);
}
/** Material 3 doesn't have a filled chip, so we have to make our own **/
.filled {
display: flex;
pointer-events: none;
border-radius: inherit;
inset: 0;
position: absolute;
background-color: var(--ha-assist-chip-filled-container-color);
}
/** Set the size of mdc icons **/
::slotted([slot="icon"]) {
display: flex;
--mdc-icon-size: var(--md-input-chip-icon-size, 18px);
}
`,
];
protected override renderOutline() {
if (this.filled) {
return html`<span class="filled"></span>`;
}
return super.renderOutline();
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-assist-chip": HaAssistChip;
}
}
-11
View File
@@ -1,11 +0,0 @@
import { MdChipSet } from "@material/web/chips/chip-set";
import { customElement } from "lit/decorators";
@customElement("ha-chip-set")
export class HaChipSet extends MdChipSet {}
declare global {
interface HTMLElementTagNameMap {
"ha-chip-set": HaChipSet;
}
}
-41
View File
@@ -1,41 +0,0 @@
import { MdFilterChip } from "@material/web/chips/filter-chip";
import { css, html } from "lit";
import { customElement, property } from "lit/decorators";
@customElement("ha-filter-chip")
export class HaFilterChip extends MdFilterChip {
@property({ type: Boolean, reflect: true, attribute: "no-leading-icon" })
noLeadingIcon = false;
static override styles = [
...super.styles,
css`
:host {
--md-sys-color-primary: var(--primary-text-color);
--md-sys-color-on-surface: var(--primary-text-color);
--md-sys-color-on-surface-variant: var(--primary-text-color);
--md-sys-color-on-secondary-container: var(--primary-text-color);
--md-filter-chip-container-shape: 16px;
--md-filter-chip-outline-color: var(--outline-color);
--md-filter-chip-selected-container-color: rgba(
var(--rgb-primary-text-color),
0.15
);
}
`,
];
protected renderLeadingIcon() {
if (this.noLeadingIcon) {
// eslint-disable-next-line lit/prefer-nothing
return html``;
}
return super.renderLeadingIcon();
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-filter-chip": HaFilterChip;
}
}
-35
View File
@@ -1,35 +0,0 @@
import { MdInputChip } from "@material/web/chips/input-chip";
import { css } from "lit";
import { customElement } from "lit/decorators";
@customElement("ha-input-chip")
export class HaInputChip extends MdInputChip {
static override styles = [
...super.styles,
css`
:host {
--md-sys-color-primary: var(--primary-text-color);
--md-sys-color-on-surface: var(--primary-text-color);
--md-sys-color-on-surface-variant: var(--primary-text-color);
--md-sys-color-on-secondary-container: var(--primary-text-color);
--md-input-chip-container-shape: 16px;
--md-input-chip-outline-color: var(--outline-color);
--md-input-chip-selected-container-color: rgba(
var(--rgb-primary-text-color),
0.15
);
}
/** Set the size of mdc icons **/
::slotted([slot="icon"]) {
display: flex;
--mdc-icon-size: var(--md-input-chip-icon-size, 18px);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-input-chip": HaInputChip;
}
}
+2 -1
View File
@@ -71,7 +71,8 @@ class HaEntitiesPickerLight extends LitElement {
@property({ attribute: "picked-entity-label" })
public pickedEntityLabel?: string;
@property({ attribute: "pick-entity-label" }) public pickEntityLabel?: string;
@property({ attribute: "pick-entity-label" })
public pickEntityLabel?: string;
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
+2 -1
View File
@@ -21,7 +21,8 @@ export class HaButton extends Button {
display: flex;
}
.slot-container {
overflow: var(--button-slot-container-overflow, visible);
width: 100%;
overflow: hidden;
}
`,
];
+38
View File
@@ -0,0 +1,38 @@
// @ts-ignore
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
import {
css,
CSSResultGroup,
html,
LitElement,
TemplateResult,
unsafeCSS,
} from "lit";
import { customElement } from "lit/decorators";
@customElement("ha-chip-set")
export class HaChipSet extends LitElement {
protected render(): TemplateResult {
return html`
<div class="mdc-chip-set">
<slot></slot>
</div>
`;
}
static get styles(): CSSResultGroup {
return css`
${unsafeCSS(chipStyles)}
slot::slotted(ha-chip) {
margin: 4px 4px 4px 0;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-chip-set": HaChipSet;
}
}
+85
View File
@@ -0,0 +1,85 @@
// @ts-ignore
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
import { css, CSSResultGroup, html, LitElement, nothing, unsafeCSS } from "lit";
import { customElement, property } from "lit/decorators";
@customElement("ha-chip")
export class HaChip extends LitElement {
@property({ type: Boolean }) public hasIcon = false;
@property({ type: Boolean }) public hasTrailingIcon = false;
@property({ type: Boolean }) public noText = false;
protected render() {
return html`
<div class="mdc-chip ${this.noText ? "no-text" : ""}">
${this.hasIcon
? html`<div class="mdc-chip__icon mdc-chip__icon--leading">
<slot name="icon"></slot>
</div>`
: nothing}
<div class="mdc-chip__ripple"></div>
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text"><slot></slot></span>
</span>
</span>
${this.hasTrailingIcon
? html`<div class="mdc-chip__icon mdc-chip__icon--trailing">
<slot name="trailing-icon"></slot>
</div>`
: nothing}
</div>
`;
}
static get styles(): CSSResultGroup {
return css`
${unsafeCSS(chipStyles)}
.mdc-chip {
background-color: var(
--ha-chip-background-color,
rgba(var(--rgb-primary-text-color), 0.15)
);
color: var(--ha-chip-text-color, var(--primary-text-color));
}
.mdc-chip.no-text {
padding: 0 10px;
}
.mdc-chip:hover {
color: var(--ha-chip-text-color, var(--primary-text-color));
}
.mdc-chip__icon--leading,
.mdc-chip__icon--trailing {
--mdc-icon-size: 18px;
line-height: 14px;
color: var(--ha-chip-icon-color, var(--ha-chip-text-color));
}
.mdc-chip.mdc-chip--selected .mdc-chip__checkmark,
.mdc-chip .mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) {
margin-right: -4px;
margin-inline-start: -4px;
margin-inline-end: 4px;
direction: var(--direction);
}
span[role="gridcell"] {
line-height: 14px;
}
:host {
outline: none;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-chip": HaChip;
}
}
+5 -27
View File
@@ -1,19 +1,11 @@
import { mdiMinus, mdiPlus } from "@mdi/js";
import {
CSSResultGroup,
LitElement,
TemplateResult,
css,
html,
nothing,
} from "lit";
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
import { customElement, property, query } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../common/dom/fire_event";
import { conditionalClamp } from "../common/number/clamp";
import { formatNumber } from "../common/number/format_number";
import { blankBeforeUnit } from "../common/translations/blank_before_unit";
import { FrontendLocaleData } from "../data/translation";
import { fireEvent } from "../common/dom/fire_event";
const A11Y_KEY_CODES = new Set([
"ArrowRight",
@@ -42,8 +34,6 @@ export class HaControlNumberButton extends LitElement {
@property({ type: Number }) public max?: number;
@property() public unit?: string;
@property({ attribute: "false" })
public formatOptions: Intl.NumberFormatOptions = {};
@@ -124,28 +114,26 @@ export class HaControlNumberButton extends LitElement {
}
protected render(): TemplateResult {
const value =
const displayedValue =
this.value != null
? formatNumber(this.value, this.locale, this.formatOptions)
: "";
const unit = this.unit ? `${blankBeforeUnit(this.unit)}${this.unit}` : "";
return html`
<div class="container">
<div
id="input"
class="value"
role="spinbutton"
role="number-button"
.tabIndex=${this.disabled ? "-1" : "0"}
aria-valuenow=${this.value}
aria-valuetext=${`${value}${unit}`}
aria-valuemin=${this.min}
aria-valuemax=${this.max}
aria-label=${ifDefined(this.label)}
?disabled=${this.disabled}
@keydown=${this._handleKeyDown}
>
${value} ${unit ? html`<span class="unit">${unit}</span>` : nothing}
${displayedValue}
</div>
<button
class="button minus"
@@ -197,8 +185,6 @@ export class HaControlNumberButton extends LitElement {
position: relative;
width: 100%;
height: 100%;
container-type: inline-size;
container-name: container;
}
.value {
display: flex;
@@ -263,14 +249,6 @@ export class HaControlNumberButton extends LitElement {
.button.plus {
right: 0;
}
.unit {
white-space: pre;
}
@container container (max-width: 100px) {
.unit {
display: none;
}
}
`;
}
}
+30 -200
View File
@@ -4,17 +4,13 @@ import {
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { customElement, property, query } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import { fireEvent } from "../common/dom/fire_event";
import { FrontendLocaleData } from "../data/translation";
import { formatNumber } from "../common/number/format_number";
import { blankBeforeUnit } from "../common/translations/blank_before_unit";
declare global {
interface HASSDomEvents {
@@ -33,21 +29,13 @@ const A11Y_KEY_CODES = new Set([
"End",
]);
type TooltipPosition = "top" | "bottom" | "left" | "right";
type TooltipMode = "never" | "always" | "interaction";
type SliderMode = "start" | "end" | "cursor";
@customElement("ha-control-slider")
export class HaControlSlider extends LitElement {
@property({ attribute: false }) public locale?: FrontendLocaleData;
@property({ type: Boolean, reflect: true })
public disabled = false;
@property()
public mode?: SliderMode = "start";
public mode?: "start" | "end" | "cursor" = "start";
@property({ type: Boolean, reflect: true })
public vertical = false;
@@ -58,15 +46,6 @@ export class HaControlSlider extends LitElement {
@property({ type: Boolean, attribute: "inverted" })
public inverted = false;
@property({ attribute: "tooltip-position" })
public tooltipPosition?: TooltipPosition;
@property()
public unit?: string;
@property({ attribute: "tooltip-mode" })
public tooltipMode: TooltipMode = "interaction";
@property({ type: Number })
public value?: number;
@@ -79,14 +58,11 @@ export class HaControlSlider extends LitElement {
@property({ type: Number })
public max = 100;
@state()
public pressed = false;
@state()
public tooltipVisible = false;
private _mc?: HammerManager;
@property({ type: Boolean, reflect: true })
public pressed = false;
valueToPercentage(value: number) {
const percentage =
(this.boundedValue(value) - this.min) / (this.max - this.min);
@@ -122,7 +98,6 @@ export class HaControlSlider extends LitElement {
if (changedProps.has("value")) {
const valuenow = this.steppedValue(this.value ?? 0);
this.setAttribute("aria-valuenow", valuenow.toString());
this.setAttribute("aria-valuetext", this._formatValue(valuenow));
}
if (changedProps.has("min")) {
this.setAttribute("aria-valuemin", this.min.toString());
@@ -168,13 +143,11 @@ export class HaControlSlider extends LitElement {
this._mc.on("panstart", () => {
if (this.disabled) return;
this.pressed = true;
this._showTooltip();
savedValue = this.value;
});
this._mc.on("pancancel", () => {
if (this.disabled) return;
this.pressed = false;
this._hideTooltip();
this.value = savedValue;
});
this._mc.on("panmove", (e) => {
@@ -187,7 +160,6 @@ export class HaControlSlider extends LitElement {
this._mc.on("panend", (e) => {
if (this.disabled) return;
this.pressed = false;
this._hideTooltip();
const percentage = this._getPercentageFromEvent(e);
this.value = this.steppedValue(this.percentageToValue(percentage));
fireEvent(this, "slider-moved", { value: undefined });
@@ -219,21 +191,6 @@ export class HaControlSlider extends LitElement {
return Math.max(this.step, (this.max - this.min) / 10);
}
_showTooltip() {
if (this._tooltipTimeout != null) window.clearTimeout(this._tooltipTimeout);
this.tooltipVisible = true;
}
_hideTooltip(delay?: number) {
if (!delay) {
this.tooltipVisible = false;
return;
}
this._tooltipTimeout = window.setTimeout(() => {
this.tooltipVisible = false;
}, delay);
}
_handleKeyDown(e: KeyboardEvent) {
if (!A11Y_KEY_CODES.has(e.code)) return;
e.preventDefault();
@@ -263,16 +220,12 @@ export class HaControlSlider extends LitElement {
this.value = this.max;
break;
}
this._showTooltip();
fireEvent(this, "slider-moved", { value: this.value });
}
private _tooltipTimeout?: number;
_handleKeyUp(e: KeyboardEvent) {
if (!A11Y_KEY_CODES.has(e.code)) return;
e.preventDefault();
this._hideTooltip(500);
fireEvent(this, "value-changed", { value: this.value });
}
@@ -289,76 +242,36 @@ export class HaControlSlider extends LitElement {
return Math.max(Math.min(1, (x - offset) / total), 0);
};
private _formatValue(value: number) {
const formattedValue = formatNumber(value, this.locale);
const formattedUnit = this.unit
? `${blankBeforeUnit(this.unit, this.locale)}${this.unit}`
: "";
return `${formattedValue}${formattedUnit}`;
}
private _renderTooltip() {
if (this.tooltipMode === "never") return nothing;
const position = this.tooltipPosition ?? (this.vertical ? "left" : "top");
const visible =
this.tooltipMode === "always" ||
(this.tooltipVisible && this.tooltipMode === "interaction");
const value = this.steppedValue(this.value ?? 0);
return html`
<span
aria-hidden="true"
class="tooltip ${classMap({
visible,
[position]: true,
[this.mode ?? "start"]: true,
"show-handle": this.showHandle,
})}"
>
${this._formatValue(value)}
</span>
`;
}
protected render(): TemplateResult {
return html`
<div
class="container${classMap({
pressed: this.pressed,
})}"
id="slider"
class="slider"
style=${styleMap({
"--value": `${this.valueToPercentage(this.value ?? 0)}`,
})}
>
<div id="slider" class="slider">
<div class="slider-track-background"></div>
<slot name="background"></slot>
${this.mode === "cursor"
? this.value != null
? html`
<div
class=${classMap({
"slider-track-cursor": true,
})}
></div>
`
: null
: html`
<div class="slider-track-background"></div>
<slot name="background"></slot>
${this.mode === "cursor"
? this.value != null
? html`
<div
class=${classMap({
"slider-track-bar": true,
[this.mode ?? "start"]: true,
"show-handle": this.showHandle,
"slider-track-cursor": true,
})}
></div>
`}
</div>
${this._renderTooltip()}
`
: null
: html`
<div
class=${classMap({
"slider-track-bar": true,
[this.mode ?? "start"]: true,
"show-handle": this.showHandle,
})}
></div>
`}
</div>
`;
}
@@ -372,7 +285,6 @@ export class HaControlSlider extends LitElement {
--control-slider-background-opacity: 0.2;
--control-slider-thickness: 40px;
--control-slider-border-radius: 10px;
--control-slider-tooltip-font-size: 14px;
height: var(--control-slider-thickness);
width: 100%;
border-radius: var(--control-slider-border-radius);
@@ -386,89 +298,6 @@ export class HaControlSlider extends LitElement {
width: var(--control-slider-thickness);
height: 100%;
}
.container {
position: relative;
height: 100%;
width: 100%;
--handle-size: 4px;
--handle-margin: calc(var(--control-slider-thickness) / 8);
}
.tooltip {
pointer-events: none;
user-select: none;
position: absolute;
background-color: var(--clear-background-color);
color: var(--primary-text-color);
font-size: var(--control-slider-tooltip-font-size);
border-radius: 0.8em;
padding: 0.2em 0.4em;
opacity: 0;
white-space: nowrap;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
transition:
opacity 180ms ease-in-out,
left 180ms ease-in-out,
bottom 180ms ease-in-out;
--handle-spacing: calc(2 * var(--handle-margin) + var(--handle-size));
--slider-tooltip-margin: -4px;
--slider-tooltip-range: 100%;
--slider-tooltip-offset: 0px;
--slider-tooltip-position: calc(
min(
max(
var(--value) * var(--slider-tooltip-range) +
var(--slider-tooltip-offset),
0%
),
100%
)
);
}
.tooltip.start {
--slider-tooltip-offset: calc(-0.5 * (var(--handle-spacing)));
}
.tooltip.end {
--slider-tooltip-offset: calc(0.5 * (var(--handle-spacing)));
}
.tooltip.cursor {
--slider-tooltip-range: calc(100% - var(--handle-spacing));
--slider-tooltip-offset: calc(0.5 * (var(--handle-spacing)));
}
.tooltip.show-handle {
--slider-tooltip-range: calc(100% - var(--handle-spacing));
--slider-tooltip-offset: calc(0.5 * (var(--handle-spacing)));
}
.tooltip.visible {
opacity: 1;
}
.tooltip.top {
transform: translate3d(-50%, -100%, 0);
top: var(--slider-tooltip-margin);
left: 50%;
}
.tooltip.bottom {
transform: translate3d(-50%, 100%, 0);
bottom: var(--slider-tooltip-margin);
left: 50%;
}
.tooltip.left {
transform: translate3d(-100%, 50%, 0);
bottom: 50%;
left: var(--slider-tooltip-margin);
}
.tooltip.right {
transform: translate3d(100%, 50%, 0);
bottom: 50%;
right: var(--slider-tooltip-margin);
}
:host(:not([vertical])) .tooltip.top,
:host(:not([vertical])) .tooltip.bottom {
left: var(--slider-tooltip-position);
}
:host([vertical]) .tooltip.right,
:host([vertical]) .tooltip.left {
bottom: var(--slider-tooltip-position);
}
.slider {
position: relative;
height: 100%;
@@ -499,6 +328,8 @@ export class HaControlSlider extends LitElement {
}
.slider .slider-track-bar {
--border-radius: var(--control-slider-border-radius);
--handle-size: 4px;
--handle-margin: calc(var(--control-slider-thickness) / 8);
--slider-size: 100%;
position: absolute;
height: 100%;
@@ -601,6 +432,7 @@ export class HaControlSlider extends LitElement {
.slider .slider-track-cursor {
--cursor-size: calc(var(--control-slider-thickness) / 4);
--handle-size: 4px;
position: absolute;
background-color: white;
border-radius: var(--handle-size);
@@ -630,11 +462,9 @@ export class HaControlSlider extends LitElement {
height: var(--handle-size);
width: 50%;
}
.pressed .tooltip {
transition: opacity 180ms ease-in-out;
}
.pressed .slider-track-bar,
.pressed .slider-track-cursor {
:host([pressed]) .slider-track-bar,
:host([pressed]) .slider-track-cursor {
transition: none;
}
:host(:disabled) .slider {
+1 -8
View File
@@ -19,14 +19,11 @@ import type {
HaFormStringData,
HaFormStringSchema,
} from "./types";
import { HomeAssistant } from "../../types";
const MASKED_FIELDS = ["password", "secret", "token"];
@customElement("ha-form-string")
export class HaFormString extends LitElement implements HaFormElement {
@property() public hass?: HomeAssistant;
@property() public schema!: HaFormStringSchema;
@property() public data!: HaFormStringData;
@@ -81,11 +78,7 @@ export class HaFormString extends LitElement implements HaFormElement {
return html`
<ha-icon-button
toggles
.label=${this.hass?.localize(
this.unmaskedPassword
? "ui.components.selectors.text.hide_password"
: "ui.components.selectors.text.show_password"
) || (this.unmaskedPassword ? "Hide password" : "Show password")}
.label=${`${this.unmaskedPassword ? "Hide" : "Show"} password`}
@click=${this.toggleUnmaskedPassword}
.path=${this.unmaskedPassword ? mdiEyeOff : mdiEye}
></ha-icon-button>
-60
View File
@@ -1,60 +0,0 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
@customElement("ha-label")
class HaLabel extends LitElement {
protected render(): TemplateResult {
return html`
<span class="label">
<slot name="icon"></slot>
<slot></slot>
</div>
`;
}
static get styles(): CSSResultGroup {
return [
css`
:host {
--ha-label-text-color: var(--primary-text-color);
--ha-label-icon-color: var(--primary-text-color);
--ha-label-background-color: rgba(
var(--rgb-primary-text-color),
0.15
);
}
.label {
display: inline-flex;
flex-direction: row;
align-items: center;
font-size: 12px;
font-weight: 500;
line-height: 16px;
letter-spacing: 0.1px;
vertical-align: middle;
height: 32px;
padding: 0 16px;
border-radius: 18px;
background-color: var(--ha-label-background-color);
color: var(--ha-label-text-color);
--mdc-icon-size: 18px;
}
::slotted([slot="icon"]) {
margin-right: 8px;
margin-left: -8px;
display: flex;
color: var(--ha-label-icon-color);
}
span {
display: inline-flex;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-label": HaLabel;
}
}
+9 -5
View File
@@ -4,14 +4,18 @@ import { customElement } from "lit/decorators";
@customElement("ha-logo-svg")
export class HaLogoSvg extends LitElement {
protected render(): TemplateResult {
return html`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
return html`<svg
viewBox="0 0 240 240"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="#18BCF2"
d="M240 224.762a15 15 0 0 1-15 15H15a15 15 0 0 1-15-15v-90c0-8.25 4.77-19.769 10.61-25.609l98.78-98.7805c5.83-5.83 15.38-5.83 21.21 0l98.79 98.7895c5.83 5.83 10.61 17.36 10.61 25.61v90-.01Z"
d="M240 224.762C240 233.012 233.25 239.762 225 239.762H15C6.75 239.762 0 233.012 0 224.762V134.762C0 126.512 4.77 114.993 10.61 109.153L109.39 10.3725C115.22 4.5425 124.77 4.5425 130.6 10.3725L229.39 109.162C235.22 114.992 240 126.522 240 134.772V224.772V224.762Z"
fill="#F2F4F9"
/>
<path
fill="#F2F4F9"
d="m107.27 239.762-40.63-40.63c-2.09.72-4.32 1.13-6.64 1.13-11.3 0-20.5-9.2-20.5-20.5s9.2-20.5 20.5-20.5 20.5 9.2 20.5 20.5c0 2.33-.41 4.56-1.13 6.65l31.63 31.63v-115.88c-6.8-3.3395-11.5-10.3195-11.5-18.3895 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5c0 8.07-4.7 15.05-11.5 18.3895v81.27l31.46-31.46c-.62-1.96-.96-4.04-.96-6.2 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5-9.2 20.5-20.5 20.5c-2.5 0-4.88-.47-7.09-1.29L129 208.892v30.88z"
d="M229.39 109.153L130.61 10.3725C124.78 4.5425 115.23 4.5425 109.4 10.3725L10.61 109.153C4.78 114.983 0 126.512 0 134.762V224.762C0 233.012 6.75 239.762 15 239.762H107.27L66.64 199.132C64.55 199.852 62.32 200.262 60 200.262C48.7 200.262 39.5 191.062 39.5 179.762C39.5 168.462 48.7 159.262 60 159.262C71.3 159.262 80.5 168.462 80.5 179.762C80.5 182.092 80.09 184.322 79.37 186.412L111 218.042V102.162C104.2 98.8225 99.5 91.8425 99.5 83.7725C99.5 72.4725 108.7 63.2725 120 63.2725C131.3 63.2725 140.5 72.4725 140.5 83.7725C140.5 91.8425 135.8 98.8225 129 102.162V183.432L160.46 151.972C159.84 150.012 159.5 147.932 159.5 145.772C159.5 134.472 168.7 125.272 180 125.272C191.3 125.272 200.5 134.472 200.5 145.772C200.5 157.072 191.3 166.272 180 166.272C177.5 166.272 175.12 165.802 172.91 164.982L129 208.892V239.772H225C233.25 239.772 240 233.022 240 224.772V134.772C240 126.522 235.23 115.002 229.39 109.162V109.153Z"
fill="#18BCF2"
/>
</svg>`;
}
+9 -6
View File
@@ -3,9 +3,12 @@ import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { titleCase } from "../common/string/title-case";
import { fetchConfig } from "../data/lovelace/config/types";
import { LovelaceViewRawConfig } from "../data/lovelace/config/view";
import { HomeAssistant, PanelInfo, ValueChangedEvent } from "../types";
import {
fetchConfig,
LovelaceConfig,
LovelaceViewConfig,
} from "../data/lovelace";
import { ValueChangedEvent, HomeAssistant, PanelInfo } from "../types";
import "./ha-combo-box";
import type { HaComboBox } from "./ha-combo-box";
import "./ha-icon";
@@ -29,7 +32,7 @@ const rowRenderer: ComboBoxLitRenderer<NavigationItem> = (item) => html`
const createViewNavigationItem = (
prefix: string,
view: LovelaceViewRawConfig,
view: LovelaceViewConfig,
index: number
) => ({
path: `/${prefix}/${view.path ?? index}`,
@@ -118,7 +121,7 @@ export class HaNavigationPicker extends LitElement {
panel.url_path === "lovelace" ? null : panel.url_path,
true
)
.then((config) => [panel.id, config] as [string, typeof config])
.then((config) => [panel.id, config] as [string, LovelaceConfig])
.catch((_) => [panel.id, undefined] as [string, undefined])
)
);
@@ -132,7 +135,7 @@ export class HaNavigationPicker extends LitElement {
const config = panelViewConfig.get(panel.id);
if (!config || !("views" in config)) continue;
if (!config) continue;
config.views.forEach((view, index) =>
this.navigationItems.push(
@@ -1,13 +1,9 @@
import { html, LitElement } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
import type { ColorTempSelector } from "../../data/selector";
import type { HomeAssistant } from "../../types";
import "../ha-labeled-slider";
import { generateColorTemperatureGradient } from "../../dialogs/more-info/components/lights/light-color-temp-picker";
import { mired2kelvin } from "../../common/color/convert-light-color";
@customElement("ha-selector-color_temp")
export class HaColorTempSelector extends LitElement {
@@ -26,21 +22,13 @@ export class HaColorTempSelector extends LitElement {
@property({ type: Boolean }) public required = true;
protected render() {
const min = this.selector.color_temp?.min_mireds ?? 153;
const max = this.selector.color_temp?.max_mireds ?? 500;
const gradient = this._generateTemperatureGradient(min, max);
return html`
<ha-labeled-slider
style=${styleMap({
"--ha-slider-background": `linear-gradient( to var(--float-end), ${gradient})`,
})}
labeled
icon="hass:thermometer"
.caption=${this.label || ""}
.min=${min}
.max=${max}
.min=${this.selector.color_temp?.min_mireds ?? 153}
.max=${this.selector.color_temp?.max_mireds ?? 500}
.value=${this.value}
.disabled=${this.disabled}
.helper=${this.helper}
@@ -50,16 +38,22 @@ export class HaColorTempSelector extends LitElement {
`;
}
private _generateTemperatureGradient = memoizeOne(
(min: number, max: number) =>
generateColorTemperatureGradient(mired2kelvin(min), mired2kelvin(max))
);
private _valueChanged(ev: CustomEvent) {
fireEvent(this, "value-changed", {
value: Number((ev.detail as any).value),
});
}
static styles = css`
ha-labeled-slider {
--ha-slider-background: linear-gradient(
to var(--float-end),
rgb(255, 160, 0) 0%,
white 50%,
rgb(166, 209, 255) 100%
);
}
`;
}
declare global {
@@ -1,5 +1,5 @@
import "@material/mwc-list/mwc-list-item";
import { mdiDrag } from "@mdi/js";
import { mdiClose, mdiDrag } from "@mdi/js";
import { LitElement, PropertyValues, css, html, nothing } from "lit";
import { customElement, property, query } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
@@ -12,9 +12,9 @@ import type { SelectOption, SelectSelector } from "../../data/selector";
import { sortableStyles } from "../../resources/ha-sortable-style";
import { SortableInstance } from "../../resources/sortable";
import type { HomeAssistant } from "../../types";
import "../chips/ha-chip-set";
import "../chips/ha-input-chip";
import "../ha-checkbox";
import "../ha-chip";
import "../ha-chip-set";
import "../ha-combo-box";
import type { HaComboBox } from "../ha-combo-box";
import "../ha-formfield";
@@ -65,7 +65,7 @@ export class HaSelectSelector extends LitElement {
{
animation: 150,
fallbackClass: "sortable-fallback",
draggable: "ha-input-chip",
draggable: "ha-chip",
onChoose: (evt: SortableEvent) => {
(evt.item as any).placeholder =
document.createComment("sort-placeholder");
@@ -199,31 +199,30 @@ export class HaSelectSelector extends LitElement {
${repeat(
value,
(item) => item,
(item, idx) => {
const label =
options.find((option) => option.value === item)?.label ||
item;
return html`
<ha-input-chip
(item, idx) => html`
<ha-chip
hasTrailingIcon
.hasIcon=${this.selector.select?.reorder}
>
${this.selector.select?.reorder
? html`
<ha-svg-icon
slot="icon"
.path=${mdiDrag}
data-handle
></ha-svg-icon>
`
: nothing}
${options.find((option) => option.value === item)
?.label || item}
<ha-svg-icon
slot="trailing-icon"
.path=${mdiClose}
.idx=${idx}
@remove=${this._removeItem}
.label=${label}
selected
>
${this.selector.select?.reorder
? html`
<ha-svg-icon
slot="icon"
.path=${mdiDrag}
data-handle
></ha-svg-icon>
`
: nothing}
${options.find((option) => option.value === item)
?.label || item}
</ha-input-chip>
`;
}
@click=${this._removeItem}
></ha-svg-icon>
</ha-chip>
`
)}
</ha-chip-set>
`
@@ -355,7 +354,6 @@ export class HaSelectSelector extends LitElement {
}
private async _removeItem(ev) {
ev.stopPropagation();
const value: string[] = [...ensureArray(this.value!)];
value.splice(ev.target.idx, 1);
@@ -433,9 +431,6 @@ export class HaSelectSelector extends LitElement {
mwc-list-item[disabled] {
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
}
ha-chip-set {
padding: 8px 0;
}
`,
];
}
@@ -76,11 +76,7 @@ export class HaTextSelector extends LitElement {
${this.selector.text?.type === "password"
? html`<ha-icon-button
toggles
.label=${this.hass.localize(
this._unmaskedPassword
? "ui.components.selectors.text.hide_password"
: "ui.components.selectors.text.show_password"
)}
.label=${`${this._unmaskedPassword ? "Hide" : "Show"} password`}
@click=${this._toggleUnmaskedPassword}
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
></ha-icon-button>`
@@ -1,56 +0,0 @@
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { Trigger } from "../../data/automation";
import { TriggerSelector } from "../../data/selector";
import "../../panels/config/automation/trigger/ha-automation-trigger";
import { HomeAssistant } from "../../types";
@customElement("ha-selector-trigger")
export class HaTriggerSelector extends LitElement {
@property() public hass!: HomeAssistant;
@property() public selector!: TriggerSelector;
@property() public value?: Trigger;
@property() public label?: string;
@property({ type: Boolean, reflect: true }) public disabled = false;
protected render() {
return html`
${this.label ? html`<label>${this.label}</label>` : nothing}
<ha-automation-trigger
.disabled=${this.disabled}
.triggers=${this.value || []}
.hass=${this.hass}
.nested=${this.selector.trigger?.nested}
.reOrderMode=${this.selector.trigger?.reorder_mode}
></ha-automation-trigger>
`;
}
static get styles(): CSSResultGroup {
return css`
ha-automation-trigger {
display: block;
margin-bottom: 16px;
}
:host([disabled]) ha-automation-trigger {
opacity: var(--light-disabled-opacity);
pointer-events: none;
}
label {
display: block;
margin-bottom: 4px;
font-weight: 500;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-trigger": HaTriggerSelector;
}
}
@@ -1,10 +1,10 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { ActionConfig } from "../../data/lovelace/config/action";
import { UiActionSelector } from "../../data/selector";
import "../../panels/lovelace/components/hui-action-editor";
import { HomeAssistant } from "../../types";
import "../../panels/lovelace/components/hui-action-editor";
import { ActionConfig } from "../../data/lovelace";
@customElement("ha-selector-ui_action")
export class HaSelectorUiAction extends LitElement {
@@ -25,7 +25,6 @@ export class HaSelectorUiAction extends LitElement {
.hass=${this.hass}
.config=${this.value}
.actions=${this.selector.ui_action?.actions}
.defaultAction=${this.selector.ui_action?.default_action}
.tooltipText=${this.helper}
@value-changed=${this._valueChanged}
></hui-action-editor>
@@ -1,6 +1,7 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { ActionConfig } from "../../data/lovelace";
import { UiColorSelector } from "../../data/selector";
import "../../panels/lovelace/components/hui-color-picker";
import { HomeAssistant } from "../../types";
@@ -11,7 +12,7 @@ export class HaSelectorUiColor extends LitElement {
@property() public selector!: UiColorSelector;
@property() public value?: string;
@property() public value?: ActionConfig;
@property() public label?: string;
@@ -0,0 +1,65 @@
import { LitElement, css, html } from "lit";
import { customElement, property } from "lit/decorators";
import type { UserSelector } from "../../data/selector";
import { HomeAssistant } from "../../types";
import "../user/ha-user-picker";
import "../user/ha-users-picker";
@customElement("ha-selector-user")
export class HaUserSelector extends LitElement {
@property() public hass!: HomeAssistant;
@property() public selector!: UserSelector;
@property() public value?: any;
@property() public label?: string;
@property() public helper?: string;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
protected render() {
if (this.selector.user?.multiple) {
return html`
<ha-users-picker
.hass=${this.hass}
.value=${this.value}
.label=${this.label}
.helper=${this.helper}
.disabled=${this.disabled}
.required=${this.required}
.includeSystem=${this.selector.user.include_system}
></ha-users-picker>
`;
}
return html`
<ha-user-picker
.hass=${this.hass}
.value=${this.value}
.label=${this.label}
.helper=${this.helper}
.disabled=${this.disabled}
.required=${this.required}
.includeSystem=${this.selector.user?.include_system}
></ha-user-picker>
`;
}
static get styles() {
return css`
ha-user-picker {
width: 100%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-user": HaUserSelector;
}
}
+1 -1
View File
@@ -44,13 +44,13 @@ const LOAD_ELEMENTS = {
icon: () => import("./ha-selector-icon"),
media: () => import("./ha-selector-media"),
theme: () => import("./ha-selector-theme"),
trigger: () => import("./ha-selector-trigger"),
tts: () => import("./ha-selector-tts"),
tts_voice: () => import("./ha-selector-tts-voice"),
location: () => import("./ha-selector-location"),
color_temp: () => import("./ha-selector-color-temp"),
ui_action: () => import("./ha-selector-ui-action"),
ui_color: () => import("./ha-selector-ui-color"),
user: () => import("./ha-selector-user"),
};
const LEGACY_UI_SELECTORS = new Set(["ui-action", "ui-color"]);
+1 -1
View File
@@ -41,7 +41,7 @@ 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 { ActionHandlerDetail } from "../data/lovelace/action_handler";
import { ActionHandlerDetail } from "../data/lovelace";
import {
PersistentNotification,
subscribeNotifications,
@@ -899,7 +899,6 @@ export class HaMediaPlayerBrowse extends LitElement {
overflow-y: auto;
box-sizing: border-box;
height: 100%;
position: relative;
}
/* HEADER */
+155 -60
View File
@@ -1,68 +1,84 @@
import "@material/mwc-list/mwc-list-item";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { property } from "lit/decorators";
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
import { stringCompare } from "../../common/string/compare";
import {
fuzzyFilterSort,
ScorableTextItem,
} from "../../common/string/filter/sequence-matching";
import { fetchUsers, User } from "../../data/user";
import { HomeAssistant } from "../../types";
import { HomeAssistant, ValueChangedEvent } from "../../types";
import "../ha-combo-box";
import type { HaComboBox } from "../ha-combo-box";
import "../ha-list-item";
import "../ha-select";
import "./ha-user-badge";
import "../ha-list-item";
type ScorableUser = ScorableTextItem & User;
class HaUserPicker extends LitElement {
public hass?: HomeAssistant;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public label?: string;
@property() public noUserLabel?: string;
@property() public value?: string;
@property() public value = "";
@property() public helper?: string;
@property() public users?: User[];
@property({ attribute: false }) public users?: User[];
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public disabled?: boolean;
private _sortedUsers = memoizeOne((users?: User[]) => {
if (!users) {
return [];
@property({ type: Boolean }) public required?: boolean;
@state() private _opened?: boolean;
@query("ha-combo-box", true) public comboBox!: HaComboBox;
@property({ type: Boolean, attribute: "include-system" })
public includeSystem?: boolean;
private _init = false;
private _getUserItems = memoizeOne(
(users: User[] | undefined): ScorableUser[] => {
if (!users) {
return [];
}
return users
.sort((a, b) =>
stringCompare(a.name, b.name, this.hass!.locale.language)
)
.map((user) => ({
...user,
strings: [user.name, ...(user.username ? [user.username] : [])],
}));
}
);
return users
.filter((user) => !user.system_generated)
.sort((a, b) =>
stringCompare(a.name, b.name, this.hass!.locale.language)
);
});
private _filteredUsers = memoizeOne(
(users: User[], includeSystem?: boolean) =>
users.filter((user) => includeSystem || !user.system_generated)
);
protected render(): TemplateResult {
return html`
<ha-select
.label=${this.label}
.disabled=${this.disabled}
.value=${this.value}
@selected=${this._userChanged}
>
${this.users?.length === 0
? html`<mwc-list-item value="">
${this.noUserLabel ||
this.hass?.localize("ui.components.user-picker.no_user")}
</mwc-list-item>`
: ""}
${this._sortedUsers(this.users).map(
(user) => html`
<ha-list-item graphic="avatar" .value=${user.id}>
<ha-user-badge
.hass=${this.hass}
.user=${user}
slot="graphic"
></ha-user-badge>
${user.name}
</ha-list-item>
`
)}
</ha-select>
`;
public async open() {
await this.updateComplete;
await this.comboBox?.open();
}
public async focus() {
await this.updateComplete;
await this.comboBox?.focus();
}
protected firstUpdated(changedProps) {
@@ -74,25 +90,104 @@ class HaUserPicker extends LitElement {
}
}
private _userChanged(ev) {
const newValue = ev.target.value;
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (
(!this._init && this.users) ||
(this._init && changedProps.has("_opened") && this._opened)
) {
this._init = true;
const filteredUsers = this._filteredUsers(
this.users ?? [],
this.includeSystem
);
const items = this._getUserItems(filteredUsers);
if (newValue !== this.value) {
this.value = newValue;
setTimeout(() => {
fireEvent(this, "value-changed", { value: newValue });
fireEvent(this, "change");
}, 0);
this.comboBox.items = items;
this.comboBox.filteredItems = items;
}
}
private _rowRenderer: ComboBoxLitRenderer<User> = (item) => html`
<ha-list-item
graphic="avatar"
.value=${item.id}
.twoline=${!!item.username}
>
<ha-user-badge
.hass=${this.hass}
.user=${item}
slot="graphic"
></ha-user-badge>
${item.name}
<span slot="secondary">${item.username}</span>
</ha-list-item>
`;
protected render(): TemplateResult {
return html`
<ha-combo-box
.hass=${this.hass}
.label=${this.label === undefined && this.hass
? this.hass.localize("ui.components.user-picker.user")
: this.label}
.value=${this._value}
.helper=${this.helper}
.renderer=${this._rowRenderer}
.disabled=${this.disabled}
.required=${this.required}
item-id-path="id"
item-value-path="id"
item-label-path="name"
@opened-changed=${this._openedChanged}
@value-changed=${this._valueChanged}
@filter-changed=${this._filterChanged}
>
</ha-combo-box>
`;
}
private _filterChanged(ev: CustomEvent): void {
const target = ev.target as HaComboBox;
const filterString = ev.detail.value.toLowerCase();
target.filteredItems = filterString.length
? fuzzyFilterSort<ScorableUser>(filterString, target.items || [])
: target.items;
}
private _valueChanged(ev: ValueChangedEvent<string>) {
ev.stopPropagation();
let newValue = ev.detail.value;
if (newValue === "no_users") {
newValue = "";
}
if (newValue !== this._value) {
this._setValue(newValue);
}
}
private _openedChanged(ev: ValueChangedEvent<boolean>) {
this._opened = ev.detail.value;
}
private get _value() {
return this.value || "";
}
private _setValue(value: string) {
this.value = value;
setTimeout(() => {
fireEvent(this, "value-changed", { value });
fireEvent(this, "change");
}, 0);
}
static get styles(): CSSResultGroup {
return css`
:host {
display: inline-block;
}
mwc-list {
display: block;
ha-select {
width: 100%;
}
`;
}
+74 -68
View File
@@ -1,5 +1,4 @@
import { mdiClose } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { guard } from "lit/directives/guard";
import memoizeOne from "memoize-one";
@@ -15,6 +14,10 @@ class HaUsersPickerLight extends LitElement {
@property() public value?: string[];
@property({ type: Boolean }) public disabled?: boolean;
@property({ type: Boolean }) public required?: boolean;
@property({ attribute: "picked-user-label" })
public pickedUserLabel?: string;
@@ -24,6 +27,9 @@ class HaUsersPickerLight extends LitElement {
@property({ attribute: false })
public users?: User[];
@property({ type: Boolean, attribute: "include-system" })
public includeSystem?: boolean;
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
if (this.users === undefined) {
@@ -38,69 +44,80 @@ class HaUsersPickerLight extends LitElement {
return nothing;
}
const notSelectedUsers = this._notSelectedUsers(this.users, this.value);
const filteredUsers = this._filteredUsers(this.users, this.includeSystem);
const selectedUsers = this._selectedUsers(filteredUsers, this.value);
const notSelectedUsers = this._notSelectedUsers(filteredUsers, this.value);
return html`
${guard(
[notSelectedUsers],
() =>
this.value?.map(
(user_id, idx) => html`
<div>
<ha-user-picker
.label=${this.pickedUserLabel}
.noUserLabel=${this.hass!.localize(
"ui.components.user-picker.remove_user"
)}
.index=${idx}
.hass=${this.hass}
.value=${user_id}
.users=${this._notSelectedUsersAndSelected(
user_id,
this.users,
notSelectedUsers
)}
@value-changed=${this._userChanged}
></ha-user-picker>
<ha-icon-button
.userId=${user_id}
.label=${this.hass!.localize(
"ui.components.user-picker.remove_user"
)}
.path=${mdiClose}
@click=${this._removeUser}
>
></ha-icon-button
>
</div>
`
)
${guard([notSelectedUsers], () =>
selectedUsers.map(
(user, idx) => html`
<div>
<ha-user-picker
.label=${this.pickedUserLabel}
.index=${idx}
.hass=${this.hass}
.value=${user.id}
.users=${this._notSelectedUsersAndCurrent(
user,
filteredUsers,
notSelectedUsers
)}
@value-changed=${this._userChanged}
.disabled=${this.disabled}
.includeSystem=${this.includeSystem}
></ha-user-picker>
</div>
`
)
)}
<div>${this._renderPicker(notSelectedUsers)}</div>
`;
}
private _renderPicker(users?: User[]) {
return html`
<ha-user-picker
.label=${this.pickUserLabel ||
this.hass!.localize("ui.components.user-picker.add_user")}
.label=${this.pickUserLabel}
.hass=${this.hass}
.users=${notSelectedUsers}
.disabled=${!notSelectedUsers?.length}
.users=${users}
@value-changed=${this._addUser}
.disabled=${this.disabled || users?.length === 0}
.required=${this.required}
.includeSystem=${this.includeSystem}
></ha-user-picker>
`;
}
private _notSelectedUsers = memoizeOne(
(users?: User[], currentUsers?: string[]) =>
currentUsers
? users?.filter(
(user) => !user.system_generated && !currentUsers.includes(user.id)
)
: users?.filter((user) => !user.system_generated)
private _filteredUsers = memoizeOne(
(users: User[], includeSystem?: boolean) =>
users.filter((user) => includeSystem || !user.system_generated)
);
private _notSelectedUsersAndSelected = (
userId: string,
private _selectedUsers = memoizeOne(
(users: User[], selectedUserIds?: string[]) => {
if (!selectedUserIds) {
return [];
}
return users.filter((user) => selectedUserIds.includes(user.id));
}
);
private _notSelectedUsers = memoizeOne(
(users: User[], selectedUserIds?: string[]) => {
if (!selectedUserIds) {
return users;
}
return users.filter((user) => !selectedUserIds.includes(user.id));
}
);
private _notSelectedUsersAndCurrent = (
currentUser: User,
users?: User[],
notSelected?: User[]
) => {
const selectedUser = users?.find((user) => user.id === userId);
const selectedUser = users?.find((user) => user.id === currentUser.id);
if (selectedUser) {
return notSelected ? [...notSelected, selectedUser] : [selectedUser];
}
@@ -123,7 +140,7 @@ class HaUsersPickerLight extends LitElement {
const index = (event.currentTarget as any).index;
const newValue = event.detail.value;
const newUsers = [...this._currentUsers];
if (newValue === "") {
if (newValue === undefined) {
newUsers.splice(index, 1);
} else {
newUsers.splice(index, 1, newValue);
@@ -146,22 +163,11 @@ class HaUsersPickerLight extends LitElement {
this._updateUsers([...currentUsers, toAdd]);
}
private _removeUser(event) {
const userId = (event.currentTarget as any).userId;
this._updateUsers(this._currentUsers.filter((user) => user !== userId));
}
static get styles(): CSSResultGroup {
return css`
:host {
display: block;
}
div {
display: flex;
align-items: center;
}
`;
}
static override styles = css`
div {
margin-top: 8px;
}
`;
}
declare global {
+19 -32
View File
@@ -642,7 +642,10 @@ const tryDescribeTrigger = (
}
// Device Trigger
if (trigger.platform === "device" && trigger.device_id) {
if (trigger.platform === "device") {
if (!trigger.device_id) {
return "Device trigger";
}
const config = trigger as DeviceTrigger;
const localized = localizeDeviceAutomationTrigger(
hass,
@@ -658,12 +661,9 @@ const tryDescribeTrigger = (
}`;
}
return (
hass.localize(
`ui.panel.config.automation.editor.triggers.type.${trigger.platform}.label`
) ||
hass.localize(`ui.panel.config.automation.editor.triggers.unknown_trigger`)
);
return `${
trigger.platform ? trigger.platform.replace(/_/g, " ") : "Unknown"
} trigger`;
};
export const describeCondition = (
@@ -1074,7 +1074,10 @@ const tryDescribeCondition = (
);
}
if (condition.condition === "device" && condition.device_id) {
if (condition.condition === "device") {
if (!condition.device_id) {
return "Device condition";
}
const config = condition as DeviceCondition;
const localized = localizeDeviceAutomationCondition(
hass,
@@ -1090,30 +1093,14 @@ const tryDescribeCondition = (
}`;
}
if (condition.condition === "template") {
return hass.localize(
`${conditionsTranslationBaseKey}.template.description.full`
);
if (condition.condition === "trigger") {
if (!condition.id) {
return "Trigger condition";
}
return `When triggered by ${condition.id}`;
}
if (condition.condition === "trigger" && condition.id != null) {
return hass.localize(
`${conditionsTranslationBaseKey}.trigger.description.full`,
{
id: formatListWithOrs(
hass.locale,
ensureArray(condition.id).map((id) => id.toString())
),
}
);
}
return (
hass.localize(
`ui.panel.config.automation.editor.conditions.type.${condition.condition}.label`
) ||
hass.localize(
`ui.panel.config.automation.editor.conditions.unknown_condition`
)
);
return `${
condition.condition ? condition.condition.replace(/_/g, " ") : "Unknown"
} condition`;
};
+1 -2
View File
@@ -139,8 +139,7 @@ export const getCalendars = (hass: HomeAssistant): Calendar[] =>
.filter(
(eid) =>
computeDomain(eid) === "calendar" &&
!isUnavailableState(hass.states[eid].state) &&
hass.entities[eid]?.hidden !== true
!isUnavailableState(hass.states[eid].state)
)
.sort()
.map((eid, idx) => ({
+2 -2
View File
@@ -34,7 +34,7 @@ export const TEMPERATURE_ATTRIBUTES = new Set([
"max_temp",
]);
export const DOMAIN_ATTRIBUTES_UNITS = {
export const DOMAIN_ATTRIBUTES_UNITS: Record<string, Record<string, string>> = {
climate: {
humidity: "%",
current_humidity: "%",
@@ -74,4 +74,4 @@ export const DOMAIN_ATTRIBUTES_UNITS = {
sensor: {
battery_level: "%",
},
} as const satisfies Record<string, Record<string, string>>;
};
+272 -3
View File
@@ -2,7 +2,9 @@ import {
Connection,
getCollection,
HassEventBase,
HassServiceTarget,
} from "home-assistant-js-websocket";
import { HASSDomEvent } from "../common/dom/fire_event";
import { HuiErrorCard } from "../panels/lovelace/cards/hui-error-card";
import {
Lovelace,
@@ -10,13 +12,90 @@ import {
LovelaceCard,
} from "../panels/lovelace/types";
import { HomeAssistant } from "../types";
import { fetchConfig, LegacyLovelaceConfig } from "./lovelace/config/types";
import { LovelaceViewConfig } from "./lovelace/config/view";
export interface LovelacePanelConfig {
mode: "yaml" | "storage";
}
export type LovelaceStrategyConfig = {
type: string;
[key: string]: any;
};
export interface LovelaceConfig {
title?: string;
strategy?: LovelaceStrategyConfig;
views: LovelaceViewConfig[];
background?: string;
}
export interface LegacyLovelaceConfig extends LovelaceConfig {
resources?: LovelaceResource[];
}
export interface LovelaceResource {
id: string;
type: "css" | "js" | "module" | "html";
url: string;
}
export interface LovelaceResourcesMutableParams {
res_type: LovelaceResource["type"];
url: string;
}
export type LovelaceDashboard =
| LovelaceYamlDashboard
| LovelaceStorageDashboard;
interface LovelaceGenericDashboard {
id: string;
url_path: string;
require_admin: boolean;
show_in_sidebar: boolean;
icon?: string;
title: string;
}
export interface LovelaceYamlDashboard extends LovelaceGenericDashboard {
mode: "yaml";
filename: string;
}
export interface LovelaceStorageDashboard extends LovelaceGenericDashboard {
mode: "storage";
}
export interface LovelaceDashboardMutableParams {
require_admin: boolean;
show_in_sidebar: boolean;
icon?: string;
title: string;
}
export interface LovelaceDashboardCreateParams
extends LovelaceDashboardMutableParams {
url_path: string;
mode: "storage";
}
export interface LovelaceViewConfig {
index?: number;
title?: string;
type?: string;
strategy?: LovelaceStrategyConfig;
badges?: Array<string | LovelaceBadgeConfig>;
cards?: LovelaceCardConfig[];
path?: string;
icon?: string;
theme?: string;
panel?: boolean;
background?: string;
visible?: boolean | ShowViewConfig[];
subview?: boolean;
back_path?: string;
}
export interface LovelaceViewElement extends HTMLElement {
hass?: HomeAssistant;
lovelace?: Lovelace;
@@ -28,6 +107,89 @@ export interface LovelaceViewElement extends HTMLElement {
setConfig(config: LovelaceViewConfig): void;
}
export interface ShowViewConfig {
user?: string;
}
export interface LovelaceBadgeConfig {
type?: string;
[key: string]: any;
}
export interface LovelaceCardConfig {
index?: number;
view_index?: number;
view_layout?: any;
type: string;
[key: string]: any;
}
export interface ToggleActionConfig extends BaseActionConfig {
action: "toggle";
}
export interface CallServiceActionConfig extends BaseActionConfig {
action: "call-service";
service: string;
target?: HassServiceTarget;
// "service_data" is kept for backwards compatibility. Replaced by "data".
service_data?: Record<string, unknown>;
data?: Record<string, unknown>;
}
export interface NavigateActionConfig extends BaseActionConfig {
action: "navigate";
navigation_path: string;
navigation_replace?: boolean;
}
export interface UrlActionConfig extends BaseActionConfig {
action: "url";
url_path: string;
}
export interface MoreInfoActionConfig extends BaseActionConfig {
action: "more-info";
}
export interface AssistActionConfig extends BaseActionConfig {
action: "assist";
pipeline_id?: string;
start_listening?: boolean;
}
export interface NoActionConfig extends BaseActionConfig {
action: "none";
}
export interface CustomActionConfig extends BaseActionConfig {
action: "fire-dom-event";
}
export interface BaseActionConfig {
action: string;
confirmation?: ConfirmationRestrictionConfig;
}
export interface ConfirmationRestrictionConfig {
text?: string;
exemptions?: RestrictionConfig[];
}
export interface RestrictionConfig {
user: string;
}
export type ActionConfig =
| ToggleActionConfig
| CallServiceActionConfig
| NavigateActionConfig
| UrlActionConfig
| MoreInfoActionConfig
| AssistActionConfig
| NoActionConfig
| CustomActionConfig;
type LovelaceUpdatedEvent = HassEventBase & {
event_type: "lovelace_updated";
data: {
@@ -36,6 +198,101 @@ type LovelaceUpdatedEvent = HassEventBase & {
};
};
export const fetchResources = (conn: Connection): Promise<LovelaceResource[]> =>
conn.sendMessagePromise({
type: "lovelace/resources",
});
export const createResource = (
hass: HomeAssistant,
values: LovelaceResourcesMutableParams
) =>
hass.callWS<LovelaceResource>({
type: "lovelace/resources/create",
...values,
});
export const updateResource = (
hass: HomeAssistant,
id: string,
updates: Partial<LovelaceResourcesMutableParams>
) =>
hass.callWS<LovelaceResource>({
type: "lovelace/resources/update",
resource_id: id,
...updates,
});
export const deleteResource = (hass: HomeAssistant, id: string) =>
hass.callWS({
type: "lovelace/resources/delete",
resource_id: id,
});
export const fetchDashboards = (
hass: HomeAssistant
): Promise<LovelaceDashboard[]> =>
hass.callWS({
type: "lovelace/dashboards/list",
});
export const createDashboard = (
hass: HomeAssistant,
values: LovelaceDashboardCreateParams
) =>
hass.callWS<LovelaceDashboard>({
type: "lovelace/dashboards/create",
...values,
});
export const updateDashboard = (
hass: HomeAssistant,
id: string,
updates: Partial<LovelaceDashboardMutableParams>
) =>
hass.callWS<LovelaceDashboard>({
type: "lovelace/dashboards/update",
dashboard_id: id,
...updates,
});
export const deleteDashboard = (hass: HomeAssistant, id: string) =>
hass.callWS({
type: "lovelace/dashboards/delete",
dashboard_id: id,
});
export const fetchConfig = (
conn: Connection,
urlPath: string | null,
force: boolean
): Promise<LovelaceConfig> =>
conn.sendMessagePromise({
type: "lovelace/config",
url_path: urlPath,
force,
});
export const saveConfig = (
hass: HomeAssistant,
urlPath: string | null,
config: LovelaceConfig
): Promise<void> =>
hass.callWS({
type: "lovelace/config/save",
url_path: urlPath,
config,
});
export const deleteConfig = (
hass: HomeAssistant,
urlPath: string | null
): Promise<void> =>
hass.callWS({
type: "lovelace/config/delete",
url_path: urlPath,
});
export const subscribeLovelaceUpdates = (
conn: Connection,
urlPath: string | null,
@@ -67,7 +324,7 @@ export const getLovelaceCollection = (
const fetchLegacyConfig = (
conn: Connection,
force: boolean
): Promise<LegacyLovelaceConfig> =>
): Promise<LovelaceConfig> =>
conn.sendMessagePromise({
type: "lovelace/config",
force,
@@ -90,3 +347,15 @@ export const getLegacyLovelaceCollection = (conn: Connection) =>
)
)
);
export interface ActionHandlerOptions {
hasHold?: boolean;
hasDoubleClick?: boolean;
disabled?: boolean;
}
export interface ActionHandlerDetail {
action: "hold" | "tap" | "double_tap";
}
export type ActionHandlerEvent = HASSDomEvent<ActionHandlerDetail>;
-13
View File
@@ -1,13 +0,0 @@
import { HASSDomEvent } from "../../common/dom/fire_event";
export interface ActionHandlerOptions {
hasHold?: boolean;
hasDoubleClick?: boolean;
disabled?: boolean;
}
export interface ActionHandlerDetail {
action: "hold" | "tap" | "double_tap";
}
export type ActionHandlerEvent = HASSDomEvent<ActionHandlerDetail>;
-67
View File
@@ -1,67 +0,0 @@
import type { HassServiceTarget } from "home-assistant-js-websocket";
export interface ToggleActionConfig extends BaseActionConfig {
action: "toggle";
}
export interface CallServiceActionConfig extends BaseActionConfig {
action: "call-service";
service: string;
target?: HassServiceTarget;
// "service_data" is kept for backwards compatibility. Replaced by "data".
service_data?: Record<string, unknown>;
data?: Record<string, unknown>;
}
export interface NavigateActionConfig extends BaseActionConfig {
action: "navigate";
navigation_path: string;
navigation_replace?: boolean;
}
export interface UrlActionConfig extends BaseActionConfig {
action: "url";
url_path: string;
}
export interface MoreInfoActionConfig extends BaseActionConfig {
action: "more-info";
}
export interface AssistActionConfig extends BaseActionConfig {
action: "assist";
pipeline_id?: string;
start_listening?: boolean;
}
export interface NoActionConfig extends BaseActionConfig {
action: "none";
}
export interface CustomActionConfig extends BaseActionConfig {
action: "fire-dom-event";
}
export interface BaseActionConfig {
action: string;
confirmation?: ConfirmationRestrictionConfig;
}
export interface ConfirmationRestrictionConfig {
text?: string;
exemptions?: RestrictionConfig[];
}
export interface RestrictionConfig {
user: string;
}
export type ActionConfig =
| ToggleActionConfig
| CallServiceActionConfig
| NavigateActionConfig
| UrlActionConfig
| MoreInfoActionConfig
| AssistActionConfig
| NoActionConfig
| CustomActionConfig;
-4
View File
@@ -1,4 +0,0 @@
export interface LovelaceBadgeConfig {
type?: string;
[key: string]: any;
}
-7
View File
@@ -1,7 +0,0 @@
export interface LovelaceCardConfig {
index?: number;
view_index?: number;
view_layout?: any;
type: string;
[key: string]: any;
}
-4
View File
@@ -1,4 +0,0 @@
export interface LovelaceStrategyConfig {
type: string;
[key: string]: any;
}
-63
View File
@@ -1,63 +0,0 @@
import type { Connection } from "home-assistant-js-websocket";
import type { HomeAssistant } from "../../../types";
import type { LovelaceResource } from "../resource";
import type { LovelaceStrategyConfig } from "./strategy";
import type { LovelaceViewRawConfig } from "./view";
export interface LovelaceDashboardBaseConfig {}
export interface LovelaceConfig extends LovelaceDashboardBaseConfig {
title?: string;
background?: string;
views: LovelaceViewRawConfig[];
}
export interface LovelaceDashboardStrategyConfig
extends LovelaceDashboardBaseConfig {
strategy: LovelaceStrategyConfig;
}
export interface LegacyLovelaceConfig extends LovelaceConfig {
resources?: LovelaceResource[];
}
export type LovelaceRawConfig =
| LovelaceConfig
| LovelaceDashboardStrategyConfig;
export function isStrategyDashboard(
config: LovelaceRawConfig
): config is LovelaceDashboardStrategyConfig {
return "strategy" in config;
}
export const fetchConfig = (
conn: Connection,
urlPath: string | null,
force: boolean
): Promise<LovelaceRawConfig> =>
conn.sendMessagePromise({
type: "lovelace/config",
url_path: urlPath,
force,
});
export const saveConfig = (
hass: HomeAssistant,
urlPath: string | null,
config: LovelaceRawConfig
): Promise<void> =>
hass.callWS({
type: "lovelace/config/save",
url_path: urlPath,
config,
});
export const deleteConfig = (
hass: HomeAssistant,
urlPath: string | null
): Promise<void> =>
hass.callWS({
type: "lovelace/config/delete",
url_path: urlPath,
});
-40
View File
@@ -1,40 +0,0 @@
import type { LovelaceBadgeConfig } from "./badge";
import type { LovelaceCardConfig } from "./card";
import type { LovelaceStrategyConfig } from "./strategy";
export interface ShowViewConfig {
user?: string;
}
export interface LovelaceBaseViewConfig {
index?: number;
title?: string;
path?: string;
icon?: string;
theme?: string;
panel?: boolean;
background?: string;
visible?: boolean | ShowViewConfig[];
subview?: boolean;
back_path?: string;
}
export interface LovelaceViewConfig extends LovelaceBaseViewConfig {
type?: string;
badges?: Array<string | LovelaceBadgeConfig>;
cards?: LovelaceCardConfig[];
}
export interface LovelaceStrategyViewConfig extends LovelaceBaseViewConfig {
strategy: LovelaceStrategyConfig;
}
export type LovelaceViewRawConfig =
| LovelaceViewConfig
| LovelaceStrategyViewConfig;
export function isStrategyView(
view: LovelaceViewRawConfig
): view is LovelaceStrategyViewConfig {
return "strategy" in view;
}
-69
View File
@@ -1,69 +0,0 @@
import type { HomeAssistant } from "../../types";
export type LovelaceDashboard =
| LovelaceYamlDashboard
| LovelaceStorageDashboard;
interface LovelaceGenericDashboard {
id: string;
url_path: string;
require_admin: boolean;
show_in_sidebar: boolean;
icon?: string;
title: string;
}
export interface LovelaceYamlDashboard extends LovelaceGenericDashboard {
mode: "yaml";
filename: string;
}
export interface LovelaceStorageDashboard extends LovelaceGenericDashboard {
mode: "storage";
}
export interface LovelaceDashboardMutableParams {
require_admin: boolean;
show_in_sidebar: boolean;
icon?: string;
title: string;
}
export interface LovelaceDashboardCreateParams
extends LovelaceDashboardMutableParams {
url_path: string;
mode: "storage";
}
export const fetchDashboards = (
hass: HomeAssistant
): Promise<LovelaceDashboard[]> =>
hass.callWS({
type: "lovelace/dashboards/list",
});
export const createDashboard = (
hass: HomeAssistant,
values: LovelaceDashboardCreateParams
) =>
hass.callWS<LovelaceDashboard>({
type: "lovelace/dashboards/create",
...values,
});
export const updateDashboard = (
hass: HomeAssistant,
id: string,
updates: Partial<LovelaceDashboardMutableParams>
) =>
hass.callWS<LovelaceDashboard>({
type: "lovelace/dashboards/update",
dashboard_id: id,
...updates,
});
export const deleteDashboard = (hass: HomeAssistant, id: string) =>
hass.callWS({
type: "lovelace/dashboards/delete",
dashboard_id: id,
});
-44
View File
@@ -1,44 +0,0 @@
import type { Connection } from "home-assistant-js-websocket";
import type { HomeAssistant } from "../../types";
export type LovelaceResource = {
id: string;
type: "css" | "js" | "module" | "html";
url: string;
};
export type LovelaceResourcesMutableParams = {
res_type: LovelaceResource["type"];
url: string;
};
export const fetchResources = (conn: Connection): Promise<LovelaceResource[]> =>
conn.sendMessagePromise({
type: "lovelace/resources",
});
export const createResource = (
hass: HomeAssistant,
values: LovelaceResourcesMutableParams
) =>
hass.callWS<LovelaceResource>({
type: "lovelace/resources/create",
...values,
});
export const updateResource = (
hass: HomeAssistant,
id: string,
updates: Partial<LovelaceResourcesMutableParams>
) =>
hass.callWS<LovelaceResource>({
type: "lovelace/resources/update",
resource_id: id,
...updates,
});
export const deleteResource = (hass: HomeAssistant, id: string) =>
hass.callWS({
type: "lovelace/resources/delete",
resource_id: id,
});
+2 -3
View File
@@ -1,9 +1,8 @@
import { LovelaceRawConfig } from "./lovelace/config/types";
import { LovelaceResource } from "./lovelace/resource";
import { LovelaceConfig, LovelaceResource } from "./lovelace";
import { RecorderInfo } from "./recorder";
export interface WindowWithPreloads extends Window {
llConfProm?: Promise<LovelaceRawConfig>;
llConfProm?: Promise<LovelaceConfig>;
llResProm?: Promise<LovelaceResource[]>;
recorderInfoProm?: Promise<RecorderInfo>;
}
+9 -10
View File
@@ -49,11 +49,11 @@ export type Selector =
| TemplateSelector
| ThemeSelector
| TimeSelector
| TriggerSelector
| TTSSelector
| TTSVoiceSelector
| UiActionSelector
| UiColorSelector;
| UiColorSelector
| UserSelector;
export interface ActionSelector {
action: {
@@ -374,13 +374,6 @@ export interface TimeSelector {
time: {} | null;
}
export interface TriggerSelector {
trigger: {
reorder_mode?: boolean;
nested?: boolean;
} | null;
}
export interface TTSSelector {
tts: { language?: string } | null;
}
@@ -392,7 +385,6 @@ export interface TTSVoiceSelector {
export interface UiActionSelector {
ui_action: {
actions?: UiAction[];
default_action?: UiAction;
} | null;
}
@@ -401,6 +393,13 @@ export interface UiColorSelector {
ui_color: {} | null;
}
export interface UserSelector {
user: {
multiple?: boolean;
include_system?: boolean;
} | null;
}
export const expandAreaTarget = (
hass: HomeAssistant,
areaId: string,
+5 -10
View File
@@ -61,12 +61,7 @@ export const updateItem = (
entity_id: string,
item: TodoItem
): Promise<ServiceCallResponse> =>
hass.callService(
"todo",
"update_item",
{ item: item.uid, rename: item.summary, status: item.status },
{ entity_id }
);
hass.callService("todo", "update_item", item, { entity_id });
export const createItem = (
hass: HomeAssistant,
@@ -75,9 +70,9 @@ export const createItem = (
): Promise<ServiceCallResponse> =>
hass.callService(
"todo",
"add_item",
"create_item",
{
item: summary,
summary,
},
{ entity_id }
);
@@ -89,9 +84,9 @@ export const deleteItem = (
): Promise<ServiceCallResponse> =>
hass.callService(
"todo",
"remove_item",
"delete_item",
{
item: uid,
uid,
},
{ entity_id }
);
-1
View File
@@ -61,7 +61,6 @@ export type TranslationCategory =
| "state"
| "entity"
| "entity_component"
| "exceptions"
| "config"
| "config_panel"
| "options"
@@ -415,7 +415,6 @@ export class HaMoreInfoClimateTemperature extends LitElement {
line-height: 64px;
letter-spacing: -0.25px;
margin: 0;
direction: ltr;
}
.temperature span {
display: inline-flex;
@@ -6,7 +6,6 @@ import { stateColorCss } from "../../../../common/entity/state_color";
import "../../../../components/ha-control-slider";
import { CoverEntity } from "../../../../data/cover";
import { UNAVAILABLE } from "../../../../data/entity";
import { DOMAIN_ATTRIBUTES_UNITS } from "../../../../data/entity_attributes";
import { HomeAssistant } from "../../../../types";
@customElement("ha-more-info-cover-position")
@@ -61,8 +60,6 @@ export class HaMoreInfoCoverPosition extends LitElement {
"--control-slider-background": color,
})}
.disabled=${this.stateObj.state === UNAVAILABLE}
.unit=${DOMAIN_ATTRIBUTES_UNITS.cover.current_position}
.locale=${this.hass.locale}
>
</ha-control-slider>
`;
@@ -79,7 +76,6 @@ export class HaMoreInfoCoverPosition extends LitElement {
--control-slider-color: var(--primary-color);
--control-slider-background: var(--disabled-color);
--control-slider-background-opacity: 0.2;
--control-slider-tooltip-font-size: 20px;
}
`;
}
@@ -13,7 +13,6 @@ import { stateColorCss } from "../../../../common/entity/state_color";
import "../../../../components/ha-control-slider";
import { CoverEntity } from "../../../../data/cover";
import { UNAVAILABLE } from "../../../../data/entity";
import { DOMAIN_ATTRIBUTES_UNITS } from "../../../../data/entity_attributes";
import { HomeAssistant } from "../../../../types";
export function generateTiltSliderTrackBackgroundGradient() {
@@ -97,8 +96,6 @@ export class HaMoreInfoCoverTiltPosition extends LitElement {
"--control-slider-background": color,
})}
.disabled=${this.stateObj.state === UNAVAILABLE}
.unit=${DOMAIN_ATTRIBUTES_UNITS.cover.current_tilt_position}
.locale=${this.hass.locale}
>
<div slot="background" class="gradient"></div>
</ha-control-slider>
@@ -116,7 +113,6 @@ export class HaMoreInfoCoverTiltPosition extends LitElement {
--control-slider-color: var(--primary-color);
--control-slider-background: var(--disabled-color);
--control-slider-background-opacity: 0.2;
--control-slider-tooltip-font-size: 20px;
}
.gradient {
background: -webkit-linear-gradient(top, ${GRADIENT});
@@ -8,7 +8,6 @@ import "../../../../components/ha-control-select";
import type { ControlSelectOption } from "../../../../components/ha-control-select";
import "../../../../components/ha-control-slider";
import { UNAVAILABLE } from "../../../../data/entity";
import { DOMAIN_ATTRIBUTES_UNITS } from "../../../../data/entity_attributes";
import {
computeFanSpeedCount,
computeFanSpeedIcon,
@@ -131,8 +130,6 @@ export class HaMoreInfoFanSpeed extends LitElement {
"--control-slider-background": color,
})}
.disabled=${this.stateObj.state === UNAVAILABLE}
.unit=${DOMAIN_ATTRIBUTES_UNITS.fan.percentage}
.locale=${this.hass.locale}
>
</ha-control-slider>
`;
@@ -149,7 +146,6 @@ export class HaMoreInfoFanSpeed extends LitElement {
--control-slider-color: var(--primary-color);
--control-slider-background: var(--disabled-color);
--control-slider-background-opacity: 0.2;
--control-slider-tooltip-font-size: 20px;
}
ha-control-select {
height: 45vh;
@@ -77,8 +77,6 @@ export class HaMoreInfoLightBrightness extends LitElement {
"--control-slider-background": color,
})}
.disabled=${this.stateObj.state === UNAVAILABLE}
unit="%"
.locale=${this.hass.locale}
>
</ha-control-slider>
`;
@@ -95,7 +93,6 @@ export class HaMoreInfoLightBrightness extends LitElement {
--control-slider-color: var(--primary-color);
--control-slider-background: var(--disabled-color);
--control-slider-background-opacity: 0.2;
--control-slider-tooltip-font-size: 20px;
}
`;
}
@@ -26,7 +26,6 @@ import {
LightEntity,
} from "../../../../data/light";
import { HomeAssistant } from "../../../../types";
import { DOMAIN_ATTRIBUTES_UNITS } from "../../../../data/entity_attributes";
declare global {
interface HASSDomEvents {
@@ -94,8 +93,6 @@ class LightColorTempPicker extends LitElement {
"--gradient": gradient,
})}
.disabled=${this.stateObj.state === UNAVAILABLE}
.unit=${DOMAIN_ATTRIBUTES_UNITS.light.color_temp_kelvin}
.locale=${this.hass.locale}
>
</ha-control-slider>
`;
@@ -196,7 +193,6 @@ class LightColorTempPicker extends LitElement {
top,
var(--gradient)
);
--control-slider-tooltip-font-size: 20px;
--control-slider-background-opacity: 1;
}
`,
@@ -434,7 +434,6 @@ class MoreInfoClimate extends LitElement {
font-size: 22px;
font-weight: 500;
line-height: 28px;
direction: ltr;
}
ha-select {
width: 100%;
@@ -33,12 +33,36 @@ class MoreInfoCover extends LitElement {
@property({ attribute: false }) public stateObj?: CoverEntity;
@state() private _livePosition?: number;
@state() private _liveTilt?: number;
@state() private _mode?: Mode;
private _setMode(ev) {
this._mode = ev.currentTarget.mode;
}
private _positionSliderMoved(ev) {
const value = (ev.detail as any).value;
if (isNaN(value)) return;
this._livePosition = value;
}
private _positionValueChanged() {
this._livePosition = undefined;
}
private _tiltSliderMoved(ev) {
const value = (ev.detail as any).value;
if (isNaN(value)) return;
this._liveTilt = value;
}
private _tiltValueChanged() {
this._liveTilt = undefined;
}
protected willUpdate(changedProps: PropertyValues): void {
super.willUpdate(changedProps);
if (changedProps.has("stateObj") && this.stateObj) {
@@ -53,11 +77,20 @@ class MoreInfoCover extends LitElement {
}
private get _stateOverride() {
const stateDisplay = this.hass.formatEntityState(this.stateObj!);
const liveValue = this._livePosition ?? this._liveTilt;
const forcedState =
liveValue != null ? (liveValue ? "open" : "closed") : undefined;
const stateDisplay = this.hass.formatEntityState(
this.stateObj!,
forcedState
);
const positionStateDisplay = computeCoverPositionStateDisplay(
this.stateObj!,
this.hass
this.hass,
liveValue
);
if (positionStateDisplay) {
@@ -114,6 +147,8 @@ class MoreInfoCover extends LitElement {
<ha-more-info-cover-position
.stateObj=${this.stateObj}
.hass=${this.hass}
@slider-moved=${this._positionSliderMoved}
@value-changed=${this._positionValueChanged}
></ha-more-info-cover-position>
`
: nothing}
@@ -122,6 +157,8 @@ class MoreInfoCover extends LitElement {
<ha-more-info-cover-tilt-position
.stateObj=${this.stateObj}
.hass=${this.hass}
@slider-moved=${this._tiltSliderMoved}
@value-changed=${this._tiltValueChanged}
></ha-more-info-cover-tilt-position>
`
: nothing}
@@ -40,6 +40,18 @@ class MoreInfoFan extends LitElement {
@state() public _presetMode?: string;
@state() private _liveSpeed?: number;
private _speedSliderMoved(ev) {
const value = (ev.detail as any).value;
if (isNaN(value)) return;
this._liveSpeed = value;
}
private _speedValueChanged() {
this._liveSpeed = undefined;
}
private _toggle = () => {
const service = this.stateObj?.state === "on" ? "turn_off" : "turn_on";
forwardHaptic("light");
@@ -92,14 +104,23 @@ class MoreInfoFan extends LitElement {
}
private get _stateOverride() {
const stateDisplay = this.hass.formatEntityState(this.stateObj!);
const liveValue = this._liveSpeed;
const forcedState =
liveValue != null ? (liveValue ? "on" : "off") : undefined;
const stateDisplay = this.hass.formatEntityState(
this.stateObj!,
forcedState
);
const positionStateDisplay = computeFanSpeedStateDisplay(
this.stateObj!,
this.hass
this.hass,
liveValue
);
if (positionStateDisplay && stateActive(this.stateObj!)) {
if (positionStateDisplay && (stateActive(this.stateObj!) || liveValue)) {
return positionStateDisplay;
}
return stateDisplay;
@@ -144,6 +165,8 @@ class MoreInfoFan extends LitElement {
<ha-more-info-fan-speed
.stateObj=${this.stateObj}
.hass=${this.hass}
@slider-moved=${this._speedSliderMoved}
@value-changed=${this._speedValueChanged}
>
</ha-more-info-fan-speed>
`
@@ -250,7 +250,6 @@ class MoreInfoHumidifier extends LitElement {
font-size: 22px;
font-weight: 500;
line-height: 28px;
direction: ltr;
}
`,
];
@@ -60,10 +60,29 @@ class MoreInfoLight extends LitElement {
@state() private _effect?: string;
@state() private _selectedBrightness?: number;
@state() private _colorTempPreview?: number;
@state() private _mainControl: MainControl = "brightness";
private _brightnessChanged(ev) {
const value = (ev.detail as any).value;
if (isNaN(value)) return;
this._selectedBrightness = (value * 255) / 100;
}
private _tempColorHovered(ev: CustomEvent<HASSDomEvents["color-hovered"]>) {
if (ev.detail && "color_temp_kelvin" in ev.detail) {
this._colorTempPreview = ev.detail.color_temp_kelvin;
} else {
this._colorTempPreview = undefined;
}
}
protected updated(changedProps: PropertyValues<typeof this>): void {
if (changedProps.has("stateObj")) {
this._selectedBrightness = this.stateObj?.attributes.brightness;
this._effect = this.stateObj?.attributes.effect;
}
}
@@ -79,8 +98,19 @@ class MoreInfoLight extends LitElement {
}
private get _stateOverride() {
if (this.stateObj?.attributes.brightness) {
return this.hass.formatEntityAttributeValue(this.stateObj!, "brightness");
if (this._colorTempPreview) {
return this.hass.formatEntityAttributeValue(
this.stateObj!,
"color_temp_kelvin",
this._colorTempPreview
);
}
if (this._selectedBrightness) {
return this.hass.formatEntityAttributeValue(
this.stateObj!,
"brightness",
this._selectedBrightness
);
}
return undefined;
}
@@ -138,6 +168,7 @@ class MoreInfoLight extends LitElement {
<ha-more-info-light-brightness
.stateObj=${this.stateObj}
.hass=${this.hass}
@slider-moved=${this._brightnessChanged}
>
</ha-more-info-light-brightness>
`
@@ -156,6 +187,7 @@ class MoreInfoLight extends LitElement {
<light-color-temp-picker
.hass=${this.hass}
.stateObj=${this.stateObj}
@color-hovered=${this._tempColorHovered}
>
</light-color-temp-picker>
`
@@ -243,7 +243,6 @@ class MoreInfoWaterHeater extends LitElement {
font-size: 22px;
font-weight: 500;
line-height: 28px;
direction: ltr;
}
`,
];
@@ -356,7 +356,6 @@ class MoreInfoWeather extends LitElement {
.templow {
min-width: 48px;
text-align: right;
direction: ltr;
}
.templow {
+15 -14
View File
@@ -27,7 +27,7 @@ import {
fuzzyFilterSort,
} from "../../common/string/filter/sequence-matching";
import { debounce } from "../../common/util/debounce";
import "../../components/ha-label";
import "../../components/ha-chip";
import "../../components/ha-circular-progress";
import "../../components/ha-icon-button";
import "../../components/ha-list-item";
@@ -326,17 +326,19 @@ export class QuickBar extends LitElement {
hasMeta
>
<span>
<ha-label
<ha-chip
.label=${item.categoryText}
hasIcon
class="command-category ${item.categoryKey}"
>
${item.iconPath
? html`
<ha-svg-icon .path=${item.iconPath} slot="icon"></ha-svg-icon>
`
: nothing}
${item.categoryText}
</ha-label>
? html`<ha-svg-icon
.path=${item.iconPath}
slot="icon"
></ha-svg-icon>`
: ""}
${item.categoryText}</ha-chip
>
</span>
<span class="command-text">${item.primaryText}</span>
@@ -764,7 +766,6 @@ export class QuickBar extends LitElement {
haStyleDialog,
css`
mwc-list {
position: relative;
--mdc-list-vertical-padding: 0;
}
.heading {
@@ -814,20 +815,20 @@ export class QuickBar extends LitElement {
}
.command-category {
--ha-label-icon-color: #585858;
--ha-label-text-color: #212121;
--ha-chip-icon-color: #585858;
--ha-chip-text-color: #212121;
}
.command-category.reload {
--ha-label-background-color: #cddc39;
--ha-chip-background-color: #cddc39;
}
.command-category.navigation {
--ha-label-background-color: var(--light-primary-color);
--ha-chip-background-color: var(--light-primary-color);
}
.command-category.server_control {
--ha-label-background-color: var(--warning-color);
--ha-chip-background-color: var(--warning-color);
}
span.command-text {
+1 -2
View File
@@ -15,8 +15,7 @@ import { hassUrl } from "../data/auth";
import { isExternal } from "../data/external";
import { getRecorderInfo } from "../data/recorder";
import { subscribeFrontendUserData } from "../data/frontend";
import { fetchConfig } from "../data/lovelace/config/types";
import { fetchResources } from "../data/lovelace/resource";
import { fetchConfig, fetchResources } from "../data/lovelace";
import { subscribePanels } from "../data/ws-panels";
import { subscribeThemes } from "../data/ws-themes";
import { subscribeRepairsIssueRegistry } from "../data/repairs";
File diff suppressed because one or more lines are too long
+13 -3
View File
@@ -50,9 +50,19 @@
<body>
<div id="ha-launch-screen">
<div class="ha-launch-screen-spacer"></div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
<path fill="#18BCF2" d="M240 224.762a15 15 0 0 1-15 15H15a15 15 0 0 1-15-15v-90c0-8.25 4.77-19.769 10.61-25.609l98.78-98.7805c5.83-5.83 15.38-5.83 21.21 0l98.79 98.7895c5.83 5.83 10.61 17.36 10.61 25.61v90-.01Z"/>
<path fill="#F2F4F9" d="m107.27 239.762-40.63-40.63c-2.09.72-4.32 1.13-6.64 1.13-11.3 0-20.5-9.2-20.5-20.5s9.2-20.5 20.5-20.5 20.5 9.2 20.5 20.5c0 2.33-.41 4.56-1.13 6.65l31.63 31.63v-115.88c-6.8-3.3395-11.5-10.3195-11.5-18.3895 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5c0 8.07-4.7 15.05-11.5 18.3895v81.27l31.46-31.46c-.62-1.96-.96-4.04-.96-6.2 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5-9.2 20.5-20.5 20.5c-2.5 0-4.88-.47-7.09-1.29L129 208.892v30.88z"/>
<svg
viewBox="0 0 240 240"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M240 224.762C240 233.012 233.25 239.762 225 239.762H15C6.75 239.762 0 233.012 0 224.762V134.762C0 126.512 4.77 114.993 10.61 109.153L109.39 10.3725C115.22 4.5425 124.77 4.5425 130.6 10.3725L229.39 109.162C235.22 114.992 240 126.522 240 134.772V224.772V224.762Z"
fill="#F2F4F9"
/>
<path
d="M229.39 109.153L130.61 10.3725C124.78 4.5425 115.23 4.5425 109.4 10.3725L10.61 109.153C4.78 114.983 0 126.512 0 134.762V224.762C0 233.012 6.75 239.762 15 239.762H107.27L66.64 199.132C64.55 199.852 62.32 200.262 60 200.262C48.7 200.262 39.5 191.062 39.5 179.762C39.5 168.462 48.7 159.262 60 159.262C71.3 159.262 80.5 168.462 80.5 179.762C80.5 182.092 80.09 184.322 79.37 186.412L111 218.042V102.162C104.2 98.8225 99.5 91.8425 99.5 83.7725C99.5 72.4725 108.7 63.2725 120 63.2725C131.3 63.2725 140.5 72.4725 140.5 83.7725C140.5 91.8425 135.8 98.8225 129 102.162V183.432L160.46 151.972C159.84 150.012 159.5 147.932 159.5 145.772C159.5 134.472 168.7 125.272 180 125.272C191.3 125.272 200.5 134.472 200.5 145.772C200.5 157.072 191.3 166.272 180 166.272C177.5 166.272 175.12 165.802 172.91 164.982L129 208.892V239.772H225C233.25 239.772 240 233.022 240 224.772V134.772C240 126.522 235.23 115.002 229.39 109.162V109.153Z"
fill="#18BCF2"
/>
</svg>
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div>
</div>
@@ -584,10 +584,6 @@ class DialogCalendarEventEditor extends LitElement {
return [
haStyleDialog,
css`
ha-dialog {
--mdc-dialog-min-width: min(600px, 95vw);
--mdc-dialog-max-width: min(600px, 95vw);
}
state-info {
line-height: 40px;
}
@@ -1,14 +1,14 @@
import type { SelectedDetail } from "@material/mwc-list";
import { formatInTimeZone, toDate } from "date-fns-tz";
import { LitElement, PropertyValues, css, html, nothing } from "lit";
import { css, html, LitElement, PropertyValues, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import type { Options, WeekdayStr } from "rrule";
import { ByWeekday, RRule, Weekday } from "rrule";
import { firstWeekdayIndex } from "../../common/datetime/first_weekday";
import { stopPropagation } from "../../common/dom/stop_propagation";
import { LocalizeKeys } from "../../common/translations/localize";
import "../../components/chips/ha-chip-set";
import "../../components/chips/ha-filter-chip";
import "../../components/ha-chip";
import "../../components/ha-date-input";
import "../../components/ha-list-item";
import "../../components/ha-select";
@@ -16,17 +16,17 @@ import type { HaSelect } from "../../components/ha-select";
import "../../components/ha-textfield";
import { HomeAssistant } from "../../types";
import {
DEFAULT_COUNT,
MonthlyRepeatItem,
RepeatEnd,
RepeatFrequency,
convertFrequency,
convertRepeatFrequency,
DEFAULT_COUNT,
getMonthdayRepeatFromRule,
getMonthlyRepeatItems,
getMonthlyRepeatWeekdayFromRule,
getWeekday,
getWeekdays,
MonthlyRepeatItem,
RepeatEnd,
RepeatFrequency,
ruleByWeekDay,
untilValue,
} from "./recurrence";
@@ -240,24 +240,22 @@ export class RecurrenceRuleEditor extends LitElement {
renderWeekly() {
return html`
${this.renderInterval()}
<ha-chip-set class="weekdays">
<div class="weekdays">
${this._allWeekdays!.map(
(item) => html`
<ha-filter-chip
no-leading-icon
<ha-chip
.value=${item}
.selected=${this._weekday.has(item)}
class=${classMap({ active: this._weekday.has(item) })}
@click=${this._onWeekdayToggle}
.label=${this.hass.localize(
>${this.hass.localize(
`ui.components.calendar.event.repeat.weekly.weekday.${
item.toLowerCase() as Lowercase<WeekdayStr>
}`
)}
)}</ha-chip
>
</ha-filter-chip>
`
)}
</ha-chip-set>
</div>
`;
}
@@ -381,10 +379,10 @@ export class RecurrenceRuleEditor extends LitElement {
private _onWeekdayToggle(e: MouseEvent) {
const target = e.currentTarget as any;
const value = target.value as WeekdayStr;
if (this._weekday.has(value)) {
this._weekday.delete(value);
} else {
if (!target.classList.contains("active")) {
this._weekday.add(value);
} else {
this._weekday.delete(value);
}
this.requestUpdate("_weekday");
}
@@ -506,6 +504,8 @@ export class RecurrenceRuleEditor extends LitElement {
margin-bottom: 16px;
}
.weekdays {
display: flex;
justify-content: space-between;
margin-bottom: 16px;
}
ha-textfield:last-child,
@@ -513,6 +513,11 @@ export class RecurrenceRuleEditor extends LitElement {
.weekdays:last-child {
margin-bottom: 0;
}
.active {
--ha-chip-background-color: var(--primary-color);
--ha-chip-text-color: var(--text-primary-color);
}
`;
}
@@ -342,7 +342,7 @@ export class HaChooseAction extends LitElement implements ActionElement {
private _duplicateOption(ev) {
const index = (ev.target as any).idx;
this._createOption(deepClone(ensureArray(this.action.choose)[index]));
this._addOption(deepClone(ensureArray(this.action.choose)[index]));
}
protected firstUpdated() {
@@ -399,15 +399,11 @@ export class HaChooseAction extends LitElement implements ActionElement {
});
}
private _addOption() {
this._createOption({ conditions: [], sequence: [] });
}
private _createOption(opt: ChooseActionChoice) {
private _addOption(opt?: ChooseActionChoice) {
const choose = this.action.choose
? [...ensureArray(this.action.choose)]
: [];
choose.push(opt);
choose.push(opt ?? { conditions: [], sequence: [] });
fireEvent(this, "value-changed", {
value: { ...this.action, choose },
});
@@ -27,7 +27,7 @@ import type {
RowClickedEvent,
} from "../../../components/data-table/ha-data-table";
import "../../../components/ha-button-related-filter-menu";
import "../../../components/ha-label";
import "../../../components/ha-chip";
import "../../../components/ha-fab";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-overflow-menu";
@@ -202,11 +202,11 @@ class HaAutomationPicker extends LitElement {
template: (automation) =>
automation.disabled
? html`
<ha-label>
<ha-chip>
${this.hass.localize(
"ui.panel.config.automation.picker.disabled"
)}
</ha-label>
</ha-chip>
`
: "",
};
@@ -8,17 +8,16 @@ import {
} from "@mdi/js";
import { HassEntities, UnsubscribeFunc } from "home-assistant-js-websocket";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
css,
html,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/chips/ha-assist-chip";
import "../../../components/ha-button-menu";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
@@ -35,9 +34,9 @@ import {
subscribeRepairsIssueRegistry,
} from "../../../data/repairs";
import {
UpdateEntity,
checkForEntityUpdates,
filterUpdateEntitiesWithInstall,
UpdateEntity,
} from "../../../data/update";
import { showQuickBar } from "../../../dialogs/quick-bar/show-dialog-quick-bar";
import { showRestartDialog } from "../../../dialogs/restart/show-dialog-restart";
@@ -232,17 +231,15 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
></ha-config-repairs>
${totalRepairIssues > repairsIssues.length
? html`
<ha-assist-chip
href="/config/repairs"
.label=${this.hass.localize(
<a class="button" href="/config/repairs">
${this.hass.localize(
"ui.panel.config.repairs.more_repairs",
{
count:
totalRepairIssues - repairsIssues.length,
}
)}
>
</ha-assist-chip>
</a>
`
: ""}
`
@@ -260,17 +257,15 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
></ha-config-updates>
${totalUpdates > canInstallUpdates.length
? html`
<ha-assist-chip
href="/config/updates"
label=${this.hass.localize(
<a class="button" href="/config/updates">
${this.hass.localize(
"ui.panel.config.updates.more_updates",
{
count:
totalUpdates - canInstallUpdates.length,
}
)}
>
</ha-assist-chip>
</a>
`
: ""}
`
@@ -354,8 +349,13 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
text-decoration: none;
color: var(--primary-text-color);
}
ha-assist-chip {
a.button {
display: inline-block;
color: var(--primary-text-color);
padding: 6px 16px;
margin: 8px 16px 16px 16px;
border-radius: 32px;
border: 1px solid var(--divider-color);
}
.title {
font-size: 16px;

Some files were not shown because too many files have changed in this diff Show More