mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-13 19:29:27 +00:00
Compare commits
2 Commits
auto-updat
...
update-int
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3703ffc42d | ||
![]() |
9ea8e13c87 |
15
.github/workflows/release.yaml
vendored
15
.github/workflows/release.yaml
vendored
@@ -10,18 +10,10 @@ env:
|
||||
NODE_VERSION: 14
|
||||
NODE_OPTIONS: --max_old_space_size=6144
|
||||
|
||||
# Set default workflow permissions
|
||||
# All scopes not mentioned here are set to no access
|
||||
# https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
|
||||
permissions:
|
||||
actions: none
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v2
|
||||
@@ -55,13 +47,6 @@ jobs:
|
||||
|
||||
script/release
|
||||
|
||||
- name: Upload release assets
|
||||
uses: softprops/action-gh-release@v0.1.14
|
||||
with:
|
||||
files: |
|
||||
dist/*.whl
|
||||
dist/*.tar.gz
|
||||
|
||||
wheels-init:
|
||||
name: Init wheels build
|
||||
needs: release
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "web-animations-js/web-animations-next-lite.min";
|
||||
import "../../../src/resources/ha-style";
|
||||
import "../../../src/resources/roboto";
|
||||
import "./layout/hc-lovelace";
|
||||
|
@@ -31,7 +31,7 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
|
||||
version_latest: "3.6.2",
|
||||
update_available: false,
|
||||
repository: "a0d7b954",
|
||||
icon: false,
|
||||
icon: true,
|
||||
logo: true,
|
||||
},
|
||||
{
|
||||
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 44 KiB |
Binary file not shown.
Before Width: | Height: | Size: 35 KiB |
Binary file not shown.
Before Width: | Height: | Size: 67 KiB |
Binary file not shown.
Before Width: | Height: | Size: 27 KiB |
Binary file not shown.
Before Width: | Height: | Size: 32 KiB |
@@ -23,7 +23,7 @@ if [[ "${PULL_REQUEST}" == "true" ]]; then
|
||||
createStatus "pending" "Building design preview" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
|
||||
gulp build-gallery
|
||||
if [ $? -eq 0 ]; then
|
||||
createStatus "success" "Build complete" "$DEPLOY_PRIME_URL"
|
||||
createStatus "success" "Build complete" "$DEPLOY_URL"
|
||||
else
|
||||
createStatus "error" "Build failed" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
|
||||
fi
|
||||
|
@@ -36,17 +36,12 @@ module.exports = [
|
||||
category: "misc",
|
||||
header: "Miscelaneous",
|
||||
},
|
||||
{
|
||||
category: "brand",
|
||||
header: "Brand",
|
||||
},
|
||||
{
|
||||
category: "user-test",
|
||||
header: "Users",
|
||||
pages: ["user-types", "configuration-menu"],
|
||||
header: "User Tests",
|
||||
},
|
||||
{
|
||||
category: "design.home-assistant.io",
|
||||
header: "About",
|
||||
header: "Design Documentation",
|
||||
},
|
||||
];
|
||||
|
@@ -53,19 +53,13 @@ class DemoBlackWhiteRow extends LitElement {
|
||||
|
||||
firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
applyThemesOnElement(
|
||||
this.shadowRoot!.querySelector(".dark"),
|
||||
{
|
||||
default_theme: "default",
|
||||
default_dark_theme: "default",
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
applyThemesOnElement(this.shadowRoot!.querySelector(".dark"), {
|
||||
default_theme: "default",
|
||||
default_dark_theme: "default",
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
});
|
||||
}
|
||||
|
||||
handleSubmit(ev) {
|
||||
|
@@ -78,9 +78,6 @@ class DemoCards extends LitElement {
|
||||
ha-formfield {
|
||||
margin-right: 16px;
|
||||
}
|
||||
#container {
|
||||
background-color: var(--primary-background-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -12,14 +12,7 @@ class PageDescription extends HaMarkdown {
|
||||
if (!PAGES[this.page].description) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="heading">
|
||||
<div class="title">
|
||||
${PAGES[this.page].metadata.title || this.page.split("/")[1]}
|
||||
</div>
|
||||
<div class="subtitle">${PAGES[this.page].metadata.subtitle}</div>
|
||||
</div>
|
||||
${until(
|
||||
PAGES[this.page]
|
||||
.description()
|
||||
@@ -32,22 +25,9 @@ class PageDescription extends HaMarkdown {
|
||||
static styles = [
|
||||
HaMarkdown.styles,
|
||||
css`
|
||||
.heading {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--secondary-background-color);
|
||||
}
|
||||
.title {
|
||||
font-size: 42px;
|
||||
line-height: 56px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.root {
|
||||
max-width: 800px;
|
||||
margin: 16px auto;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.root > *:first-child {
|
||||
margin-top: 0;
|
||||
|
@@ -1,11 +0,0 @@
|
||||
export const LONG_TEXT = `
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc laoreet velit ut elit volutpat, eget ultrices odio lacinia. In imperdiet malesuada est, nec sagittis metus ultricies quis. Sed nisl ex, convallis porttitor ante quis, hendrerit tristique justo. Mauris pharetra venenatis augue, eu maximus sem cursus in. Quisque sed consequat risus. Suspendisse facilisis ligula a odio consectetur condimentum. Curabitur vehicula elit nec augue mollis, et volutpat massa dictum.
|
||||
|
||||
Nam pellentesque auctor rutrum. Suspendisse elit est, sodales vel diam nec, porttitor faucibus massa. Ut pretium ac orci eu pharetra. Praesent in nibh at magna viverra rutrum eu vitae tortor. Etiam eget sem ex. Fusce tristique odio nec lacus mattis, vitae tempor nunc malesuada. Maecenas faucibus magna vel libero maximus egestas. Vestibulum luctus semper velit, in lobortis risus tempus non. Curabitur bibendum ornare commodo. Quisque commodo neque sit amet tincidunt lacinia. Proin elementum ante velit, eu congue nulla semper quis. Pellentesque consequat vel nunc at scelerisque. Mauris sit amet venenatis diam, blandit viverra leo. Integer commodo laoreet orci.
|
||||
|
||||
Curabitur ipsum tortor, sodales ut augue sed, commodo porttitor libero. Pellentesque molestie vitae mi consectetur tempor. In sed lectus consequat, lobortis neque non, semper ipsum. Etiam eget ex et nibh sagittis pulvinar lacinia ac mauris. Aenean ligula eros, viverra ac nibh at, venenatis semper quam. Sed interdum ligula sit amet massa tincidunt tincidunt. Suspendisse potenti. Aliquam egestas facilisis est, sed faucibus erat scelerisque id. Duis dolor quam, viverra vitae orci euismod, laoreet pellentesque justo. Nunc malesuada non erat at ullamcorper. Mauris eget posuere odio. Vestibulum turpis nunc, pharetra eget ante in, feugiat mollis justo. Proin porttitor, diam nec vulputate pretium, tellus arcu rhoncus turpis, a blandit nisi nulla quis arcu. Nunc ac ullamcorper ligula, nec facilisis leo.
|
||||
|
||||
In vitae eros sollicitudin, iaculis ex eget, egestas orci. Etiam sed pretium lorem. Nam nisi enim, consectetur sit amet semper ac, semper pharetra diam. In pulvinar neque sapien, ac ullamcorper est lacinia a. Etiam tincidunt velit sed diam malesuada, eu ornare ex consectetur. Phasellus in imperdiet tellus. Sed bibendum, dui sit amet fringilla aliquet, enim odio sollicitudin lorem, vel semper turpis mauris vel mauris. Aenean congue magna ac massa cursus, in dictum orci commodo. Pellentesque mollis velit in sollicitudin tincidunt. Vestibulum et efficitur nulla.
|
||||
|
||||
Quisque posuere, velit sed porttitor dapibus, neque augue fringilla felis, eu luctus nisi nisl nec ipsum. Curabitur pellentesque ac lectus eget ultricies. Vestibulum est dolor, lacinia pharetra vulputate a, facilisis a magna. Nam vitae arcu nibh. Praesent finibus blandit ante, ac gravida ex mollis eget. Donec quam est, pulvinar vitae neque ut, bibendum aliquam erat. Nullam mollis arcu at sem tincidunt, in tristique lectus facilisis. Aenean ut lacus vel nisl finibus iaculis non a turpis. Integer eget ipsum ante. Donec nunc neque, vestibulum ac magna ac, posuere scelerisque dui. Pellentesque massa nibh, rhoncus id dolor quis, placerat posuere turpis. Donec aliquet augue nisi, eu finibus dui auctor et. Vestibulum eu varius lorem. Quisque lectus ante, malesuada pretium risus eget, interdum mattis enim.
|
||||
`;
|
@@ -5,7 +5,6 @@ import { html, css, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../src/components/ha-icon-button";
|
||||
import "../../src/managers/notification-manager";
|
||||
import "../../src/components/ha-expansion-panel";
|
||||
import { haStyle } from "../../src/resources/styles";
|
||||
import { PAGES, SIDEBAR } from "../build/import-pages";
|
||||
import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
|
||||
@@ -45,10 +44,6 @@ class HaGallery extends LitElement {
|
||||
for (const page of group.pages!) {
|
||||
const key = `${group.category}/${page}`;
|
||||
const active = this._page === key;
|
||||
if (!(key in PAGES)) {
|
||||
console.error("Undefined page referenced in sidebar.js:", key);
|
||||
continue;
|
||||
}
|
||||
const title = PAGES[key].metadata.title || page;
|
||||
links.push(html`
|
||||
<a ?active=${active} href=${`#${group.category}/${page}`}>${title}</a>
|
||||
@@ -58,9 +53,10 @@ class HaGallery extends LitElement {
|
||||
sidebar.push(
|
||||
group.header
|
||||
? html`
|
||||
<ha-expansion-panel .header=${group.header}>
|
||||
<details>
|
||||
<summary class="section">${group.header}</summary>
|
||||
${links}
|
||||
</ha-expansion-panel>
|
||||
</details>
|
||||
`
|
||||
: links
|
||||
);
|
||||
@@ -96,34 +92,27 @@ class HaGallery extends LitElement {
|
||||
${dynamicElement(`demo-${this._page.replace("/", "-")}`)}
|
||||
</div>
|
||||
<div class="page-footer">
|
||||
<div class="header">Help us to improve our documentation</div>
|
||||
<div class="secondary">
|
||||
Suggest an edit to this page, or provide/view feedback for this
|
||||
page.
|
||||
</div>
|
||||
<div>
|
||||
${PAGES[this._page].description ||
|
||||
Object.keys(PAGES[this._page].metadata).length > 0
|
||||
? html`
|
||||
<a
|
||||
href=${`${GITHUB_DEMO_URL}${this._page}.markdown`}
|
||||
target="_blank"
|
||||
>
|
||||
Edit text
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
${PAGES[this._page].demo
|
||||
? html`
|
||||
<a
|
||||
href=${`${GITHUB_DEMO_URL}${this._page}.ts`}
|
||||
target="_blank"
|
||||
>
|
||||
Edit demo
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
${PAGES[this._page].description ||
|
||||
Object.keys(PAGES[this._page].metadata).length > 0
|
||||
? html`
|
||||
<a
|
||||
href=${`${GITHUB_DEMO_URL}${this._page}.markdown`}
|
||||
target="_blank"
|
||||
>
|
||||
Edit text
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
${PAGES[this._page].demo
|
||||
? html`
|
||||
<a
|
||||
href=${`${GITHUB_DEMO_URL}${this._page}.ts`}
|
||||
target="_blank"
|
||||
>
|
||||
Edit demo
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
</mwc-drawer>
|
||||
@@ -197,16 +186,27 @@ class HaGallery extends LitElement {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.sidebar details {
|
||||
margin-top: 1em;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.sidebar summary {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.sidebar a {
|
||||
color: var(--primary-text-color);
|
||||
display: block;
|
||||
padding: 12px;
|
||||
padding: 4px 12px;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sidebar a[active]::before {
|
||||
border-radius: 12px;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 2px;
|
||||
@@ -237,32 +237,14 @@ class HaGallery extends LitElement {
|
||||
|
||||
.page-footer {
|
||||
text-align: center;
|
||||
margin: 16px;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
background-color: var(--primary-background-color);
|
||||
}
|
||||
|
||||
.page-footer div {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.page-footer .header {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-footer .secondary {
|
||||
line-height: 23px;
|
||||
text-align: center;
|
||||
margin: 16px 0;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.page-footer a {
|
||||
display: inline-block;
|
||||
margin: 0 8px;
|
||||
text-decoration: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -1,34 +0,0 @@
|
||||
---
|
||||
title: "Logo"
|
||||
---
|
||||
|
||||

|
||||
|
||||
# Using our logo
|
||||
|
||||
As a community, we are proud of our logo. Follow these guidelines to ensure it always looks its best. Our logo follows Google's material design spec and uses the blue interface color.
|
||||
|
||||
[Download Logo](https://github.com/home-assistant/assets/tree/master/logo)
|
||||
|
||||

|
||||
|
||||
|
||||
## Using the icon
|
||||
|
||||
Our icon is a shorter and most used version of our logo. The icon can exist without the wordmark, the wordmark should never exist without the icon.
|
||||
|
||||

|
||||
|
||||
## Using the right variant
|
||||
|
||||
The pretty blue logo with a background shadow, pictured top left, is our primary logo. It should only be used with black, white, and non-duotone photography.
|
||||
|
||||
When needed you can use our logo without a shadow, as seen as the second variant.
|
||||
|
||||
The outlined logo should only be used on packaging.
|
||||
|
||||
## Exclusion zone
|
||||
|
||||
The logo needs some personal space. It's exclusion zone is equal to a quarter the height of the icon.
|
||||
|
||||

|
@@ -1,41 +0,0 @@
|
||||
---
|
||||
title: "Our story"
|
||||
---
|
||||
|
||||
## Open source home automation that puts local control and privacy first
|
||||
|
||||
Home Assistant is a free and open-source software for home automation that is designed to be the central control system for smart home devices with a focus on local control and privacy. It can be accessed via a web-based user interface, via apps for Android and iOS, or using voice commands via a supported virtual assistant like Google Assistant and Amazon Alexa.
|
||||
|
||||
IoT devices and services are supported by modular support for controlling proprietary ecosystems if they provide public access via an Open API for third-party integrations and protocols like Bluetooth, MQTT, Zigbee, and Z-Wave, After the Home Assistant software application is installed as a computer appliance it will act as a central control system for home automation. Information from all entities it sees can be used and controlled from within scripts trigger automations using scheduling and "blueprint" subroutines, e.g. for controlling lighting, climate, entertainment systems, and appliances.
|
||||
|
||||
# Open Home
|
||||
|
||||
The Open Home is our vision for the smart home. It defines the values that we put at the heart of every decision we make at Home Assistant. It’s woven into our architecture, licensing, community, and everything else.
|
||||
|
||||
The Open Home is about privacy, choice, and durability.
|
||||
|
||||
## Privacy
|
||||
|
||||
Your home should be your safe space. A place where you can be your true self without having to bother about what the world thinks of you. A place where you don’t need to act differently to avoid an algorithm categorizing your behavior. Privacy for the Open Home means that devices need to work locally. No one else needs to know if you turn on a light bulb or change the thermostat.
|
||||
|
||||
It is okay for a product to offer a cloud connection, but it should be extra and opt-in.
|
||||
|
||||
## Choice
|
||||
|
||||
Devices in your home gather data about themselves and their surroundings. Your data. Vendors shouldn’t be able to limit your access to your data or limit the interoperability of your devices with the rest of your smart home.
|
||||
|
||||
Choice for the Open Home means that devices need to make the gathered data available through local APIs. This avoids vendor lock-in and allows users to create their own smart home with devices from different manufacturers.
|
||||
|
||||
## Durability
|
||||
|
||||
If there is one thing that technology firms are very good at, it is launching new products. However, maintaining the products and making sure they keep working is an afterthought for most. The result is that vendors can decide to no longer support your device, crippling its features or even preventing it from working at all. As we install more and more devices in our home, durability is becoming more and more important. We shouldn’t have to buy everything new every couple of years because the manufacturer decided to move on.
|
||||
|
||||
Durability for the Open Home means that devices are designed and built to keep working. Not just this year, but for the next decade.
|
||||
|
||||
# Our history
|
||||
|
||||
The project was started as a Python application by Paulus Schoutsen in September 2013 and first published publicly on GitHub in November 2013. In July 2017, a managed operating system called Hass.io was initially introduced to make it easier use to use Home Assistant on single-board computers like the Raspberry Pi series. Its bundled "supervisor" management system allowed users to manage, backup, and update the local installation and introduced the option to extend the functionality of the software with add-ons.
|
||||
|
||||
An optional subscription service was introduced in December 2017 for $5/month to solve the complexities associated with secured remote access, as well as linking to Amazon Alexa and Google Assistant. Nabu Casa, Inc. was formed in September 2018 to take over the subscription service. The company's funding is based solely on revenue from the subscription service. It is used to finance the project's infrastructure and to pay for full-time employees contributing to the project.
|
||||
|
||||
In January 2020, branding was adjusted to make it easier to refer to different parts of the project. The main piece of software was renamed to Home Assistant Core, while the full suite of software with the embedded operating system and bundled "supervisor" management system was renamed to Home Assistant.
|
@@ -1,6 +1,5 @@
|
||||
---
|
||||
title: Alerts
|
||||
subtitle: An alert displays a short, important message in a way that attracts the user's attention without interrupting the user's task.
|
||||
---
|
||||
|
||||
# Alert `<ha-alert>`
|
||||
|
@@ -3,7 +3,18 @@ import { customElement } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-faded";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import { LONG_TEXT } from "../../data/text";
|
||||
|
||||
const LONG_TEXT = `
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc laoreet velit ut elit volutpat, eget ultrices odio lacinia. In imperdiet malesuada est, nec sagittis metus ultricies quis. Sed nisl ex, convallis porttitor ante quis, hendrerit tristique justo. Mauris pharetra venenatis augue, eu maximus sem cursus in. Quisque sed consequat risus. Suspendisse facilisis ligula a odio consectetur condimentum. Curabitur vehicula elit nec augue mollis, et volutpat massa dictum.
|
||||
|
||||
Nam pellentesque auctor rutrum. Suspendisse elit est, sodales vel diam nec, porttitor faucibus massa. Ut pretium ac orci eu pharetra. Praesent in nibh at magna viverra rutrum eu vitae tortor. Etiam eget sem ex. Fusce tristique odio nec lacus mattis, vitae tempor nunc malesuada. Maecenas faucibus magna vel libero maximus egestas. Vestibulum luctus semper velit, in lobortis risus tempus non. Curabitur bibendum ornare commodo. Quisque commodo neque sit amet tincidunt lacinia. Proin elementum ante velit, eu congue nulla semper quis. Pellentesque consequat vel nunc at scelerisque. Mauris sit amet venenatis diam, blandit viverra leo. Integer commodo laoreet orci.
|
||||
|
||||
Curabitur ipsum tortor, sodales ut augue sed, commodo porttitor libero. Pellentesque molestie vitae mi consectetur tempor. In sed lectus consequat, lobortis neque non, semper ipsum. Etiam eget ex et nibh sagittis pulvinar lacinia ac mauris. Aenean ligula eros, viverra ac nibh at, venenatis semper quam. Sed interdum ligula sit amet massa tincidunt tincidunt. Suspendisse potenti. Aliquam egestas facilisis est, sed faucibus erat scelerisque id. Duis dolor quam, viverra vitae orci euismod, laoreet pellentesque justo. Nunc malesuada non erat at ullamcorper. Mauris eget posuere odio. Vestibulum turpis nunc, pharetra eget ante in, feugiat mollis justo. Proin porttitor, diam nec vulputate pretium, tellus arcu rhoncus turpis, a blandit nisi nulla quis arcu. Nunc ac ullamcorper ligula, nec facilisis leo.
|
||||
|
||||
In vitae eros sollicitudin, iaculis ex eget, egestas orci. Etiam sed pretium lorem. Nam nisi enim, consectetur sit amet semper ac, semper pharetra diam. In pulvinar neque sapien, ac ullamcorper est lacinia a. Etiam tincidunt velit sed diam malesuada, eu ornare ex consectetur. Phasellus in imperdiet tellus. Sed bibendum, dui sit amet fringilla aliquet, enim odio sollicitudin lorem, vel semper turpis mauris vel mauris. Aenean congue magna ac massa cursus, in dictum orci commodo. Pellentesque mollis velit in sollicitudin tincidunt. Vestibulum et efficitur nulla.
|
||||
|
||||
Quisque posuere, velit sed porttitor dapibus, neque augue fringilla felis, eu luctus nisi nisl nec ipsum. Curabitur pellentesque ac lectus eget ultricies. Vestibulum est dolor, lacinia pharetra vulputate a, facilisis a magna. Nam vitae arcu nibh. Praesent finibus blandit ante, ac gravida ex mollis eget. Donec quam est, pulvinar vitae neque ut, bibendum aliquam erat. Nullam mollis arcu at sem tincidunt, in tristique lectus facilisis. Aenean ut lacus vel nisl finibus iaculis non a turpis. Integer eget ipsum ante. Donec nunc neque, vestibulum ac magna ac, posuere scelerisque dui. Pellentesque massa nibh, rhoncus id dolor quis, placerat posuere turpis. Donec aliquet augue nisi, eu finibus dui auctor et. Vestibulum eu varius lorem. Quisque lectus ante, malesuada pretium risus eget, interdum mattis enim.
|
||||
`;
|
||||
|
||||
const SMALL_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
|
||||
|
||||
|
@@ -1,109 +1,17 @@
|
||||
/* eslint-disable lit/no-template-arrow */
|
||||
import "@material/mwc-button";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { LitElement, TemplateResult, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
|
||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import "../../components/demo-black-white-row";
|
||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/demo-black-white-row";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||
friendly_name: "Alarm",
|
||||
}),
|
||||
getEntity("media_player", "livingroom", "playing", {
|
||||
friendly_name: "Livingroom",
|
||||
}),
|
||||
getEntity("media_player", "lounge", "idle", {
|
||||
friendly_name: "Lounge",
|
||||
supported_features: 444983,
|
||||
}),
|
||||
getEntity("light", "bedroom", "on", {
|
||||
friendly_name: "Bedroom",
|
||||
}),
|
||||
getEntity("switch", "coffee", "off", {
|
||||
friendly_name: "Coffee",
|
||||
}),
|
||||
];
|
||||
|
||||
const DEVICES = [
|
||||
{
|
||||
area_id: "bedroom",
|
||||
configuration_url: null,
|
||||
config_entries: ["config_entry_1"],
|
||||
connections: [],
|
||||
disabled_by: null,
|
||||
entry_type: null,
|
||||
id: "device_1",
|
||||
identifiers: [["demo", "volume1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
name_by_user: null,
|
||||
name: "Dishwasher",
|
||||
sw_version: null,
|
||||
hw_version: null,
|
||||
via_device_id: null,
|
||||
},
|
||||
{
|
||||
area_id: "backyard",
|
||||
configuration_url: null,
|
||||
config_entries: ["config_entry_2"],
|
||||
connections: [],
|
||||
disabled_by: null,
|
||||
entry_type: null,
|
||||
id: "device_2",
|
||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
name_by_user: null,
|
||||
name: "Lamp",
|
||||
sw_version: null,
|
||||
hw_version: null,
|
||||
via_device_id: null,
|
||||
},
|
||||
{
|
||||
area_id: null,
|
||||
configuration_url: null,
|
||||
config_entries: ["config_entry_3"],
|
||||
connections: [],
|
||||
disabled_by: null,
|
||||
entry_type: null,
|
||||
id: "device_3",
|
||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
name_by_user: "User name",
|
||||
name: "Technical name",
|
||||
sw_version: null,
|
||||
hw_version: null,
|
||||
via_device_id: null,
|
||||
},
|
||||
];
|
||||
|
||||
const AREAS = [
|
||||
{
|
||||
area_id: "backyard",
|
||||
name: "Backyard",
|
||||
picture: null,
|
||||
},
|
||||
{
|
||||
area_id: "bedroom",
|
||||
name: "Bedroom",
|
||||
picture: null,
|
||||
},
|
||||
{
|
||||
area_id: "livingroom",
|
||||
name: "Livingroom",
|
||||
picture: null,
|
||||
},
|
||||
];
|
||||
|
||||
const SCHEMAS: {
|
||||
title: string;
|
||||
@@ -130,8 +38,6 @@ const SCHEMAS: {
|
||||
select: "Select",
|
||||
icon: "Icon",
|
||||
media: "Media",
|
||||
location: "Location",
|
||||
entities: "Entities",
|
||||
},
|
||||
schema: [
|
||||
{ name: "addon", selector: { addon: {} } },
|
||||
@@ -139,7 +45,6 @@ const SCHEMAS: {
|
||||
{
|
||||
name: "Attribute",
|
||||
selector: { attribute: { entity_id: "" } },
|
||||
context: { filter_entity: "entity" },
|
||||
},
|
||||
{ name: "Device", selector: { device: {} } },
|
||||
{ name: "Duration", selector: { duration: {} } },
|
||||
@@ -147,9 +52,7 @@ const SCHEMAS: {
|
||||
{ name: "target", selector: { target: {} } },
|
||||
{ name: "number", selector: { number: { min: 0, max: 10 } } },
|
||||
{ name: "boolean", selector: { boolean: {} } },
|
||||
{ name: "time", required: true, selector: { time: {} } },
|
||||
{ name: "datetime", required: true, selector: { datetime: {} } },
|
||||
{ name: "date", required: true, selector: { date: {} } },
|
||||
{ name: "time", selector: { time: {} } },
|
||||
{ name: "action", selector: { action: {} } },
|
||||
{ name: "text", selector: { text: { multiline: false } } },
|
||||
{ name: "text_multiline", selector: { text: { multiline: true } } },
|
||||
@@ -172,14 +75,6 @@ const SCHEMAS: {
|
||||
media: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "location",
|
||||
selector: { location: { radius: true, icon: "mdi:home" } },
|
||||
},
|
||||
{
|
||||
name: "entities",
|
||||
selector: { entity: { multiple: true } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -420,10 +315,9 @@ class DemoHaForm extends LitElement {
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("config", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockEntityRegistry(hass);
|
||||
mockDeviceRegistry(hass, DEVICES);
|
||||
mockAreaRegistry(hass, AREAS);
|
||||
mockDeviceRegistry(hass);
|
||||
mockAreaRegistry(hass);
|
||||
mockHassioSupervisor(hass);
|
||||
}
|
||||
|
||||
|
@@ -1,20 +1,20 @@
|
||||
/* eslint-disable lit/no-template-arrow */
|
||||
import "@material/mwc-button";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { LitElement, TemplateResult, css, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
import "../../../../src/components/ha-selector/ha-selector";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import { BlueprintInput } from "../../../../src/data/blueprint";
|
||||
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/demo-black-white-row";
|
||||
import { BlueprintInput } from "../../../../src/data/blueprint";
|
||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
|
||||
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||
@@ -109,7 +109,7 @@ const AREAS = [
|
||||
|
||||
const SCHEMAS: {
|
||||
name: string;
|
||||
input: Record<string, (BlueprintInput & { required?: boolean }) | null>;
|
||||
input: Record<string, BlueprintInput | null>;
|
||||
}[] = [
|
||||
{
|
||||
name: "One of each",
|
||||
@@ -146,8 +146,6 @@ const SCHEMAS: {
|
||||
},
|
||||
boolean: { name: "Boolean", selector: { boolean: {} } },
|
||||
time: { name: "Time", selector: { time: {} } },
|
||||
date: { name: "Date", selector: { date: {} } },
|
||||
datetime: { name: "Date Time", selector: { datetime: {} } },
|
||||
action: { name: "Action", selector: { action: {} } },
|
||||
text: {
|
||||
name: "Text",
|
||||
@@ -164,91 +162,12 @@ const SCHEMAS: {
|
||||
},
|
||||
},
|
||||
object: { name: "Object", selector: { object: {} } },
|
||||
select_radio: {
|
||||
name: "Select (Radio)",
|
||||
selector: {
|
||||
select: { options: ["Option 1", "Option 2"], mode: "list" },
|
||||
},
|
||||
},
|
||||
select: {
|
||||
name: "Select",
|
||||
selector: {
|
||||
select: {
|
||||
options: [
|
||||
"Option 1",
|
||||
"Option 2",
|
||||
"Option 3",
|
||||
"Option 4",
|
||||
"Option 5",
|
||||
"Option 6",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
select_custom: {
|
||||
name: "Select (Custom)",
|
||||
selector: {
|
||||
select: {
|
||||
custom_value: true,
|
||||
options: [
|
||||
"Option 1",
|
||||
"Option 2",
|
||||
"Option 3",
|
||||
"Option 4",
|
||||
"Option 5",
|
||||
"Option 6",
|
||||
],
|
||||
},
|
||||
},
|
||||
selector: { select: { options: ["Option 1", "Option 2"] } },
|
||||
},
|
||||
icon: { name: "Icon", selector: { icon: {} } },
|
||||
media: { name: "Media", selector: { media: {} } },
|
||||
location: { name: "Location", selector: { location: {} } },
|
||||
location_radius: {
|
||||
name: "Location with radius",
|
||||
selector: { location: { radius: true, icon: "mdi:home" } },
|
||||
},
|
||||
color_temp: {
|
||||
name: "Color Temperature",
|
||||
selector: { color_temp: {} },
|
||||
},
|
||||
color_rgb: { name: "Color", selector: { color_rgb: {} } },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Multiples",
|
||||
input: {
|
||||
entity: { name: "Entity", selector: { entity: { multiple: true } } },
|
||||
device: { name: "Device", selector: { device: { multiple: true } } },
|
||||
area: { name: "Area", selector: { area: { multiple: true } } },
|
||||
select: {
|
||||
name: "Select Multiple",
|
||||
selector: {
|
||||
select: {
|
||||
multiple: true,
|
||||
custom_value: true,
|
||||
options: [
|
||||
"Option 1",
|
||||
"Option 2",
|
||||
"Option 3",
|
||||
"Option 4",
|
||||
"Option 5",
|
||||
"Option 6",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
select_checkbox: {
|
||||
name: "Select Multiple (Checkbox)",
|
||||
required: false,
|
||||
selector: {
|
||||
select: {
|
||||
mode: "list",
|
||||
multiple: true,
|
||||
options: ["Option 1", "Option 2", "Option 3", "Option 4"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -257,12 +176,6 @@ const SCHEMAS: {
|
||||
class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
@state() public hass!: HomeAssistant;
|
||||
|
||||
@state() private _disabled = false;
|
||||
|
||||
@state() private _required = false;
|
||||
|
||||
@state() private _label = true;
|
||||
|
||||
private data = SCHEMAS.map(() => ({}));
|
||||
|
||||
constructor() {
|
||||
@@ -396,29 +309,6 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="options">
|
||||
<ha-formfield label="Labels">
|
||||
<ha-switch
|
||||
.name=${"label"}
|
||||
.checked=${this._label}
|
||||
@change=${this._handleOptionChange}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Required">
|
||||
<ha-switch
|
||||
.name=${"required"}
|
||||
.checked=${this._required}
|
||||
@change=${this._handleOptionChange}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Disabled">
|
||||
<ha-switch
|
||||
.name=${"disabled"}
|
||||
.checked=${this._disabled}
|
||||
@change=${this._handleOptionChange}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
${SCHEMAS.map((info, idx) => {
|
||||
const data = this.data[idx];
|
||||
const valueChanged = (ev) => {
|
||||
@@ -441,10 +331,7 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
.hass=${this.hass}
|
||||
.selector=${value!.selector}
|
||||
.key=${key}
|
||||
.label=${this._label ? value!.name : undefined}
|
||||
.value=${data[key] ?? value!.default}
|
||||
.disabled=${this._disabled}
|
||||
.required=${this._required}
|
||||
@value-changed=${valueChanged}
|
||||
></ha-selector>
|
||||
</ha-settings-row>
|
||||
@@ -457,20 +344,10 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleOptionChange(ev) {
|
||||
this[`_${ev.target.name}`] = ev.target.checked;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-selector {
|
||||
width: 60;
|
||||
}
|
||||
.options {
|
||||
padding: 16px 48px;
|
||||
}
|
||||
.options ha-formfield {
|
||||
margin-right: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -2,8 +2,6 @@
|
||||
title: Editing design.home-assistant.io
|
||||
---
|
||||
|
||||

|
||||
|
||||
# How to edit design.home-assistant.io
|
||||
|
||||
All pages are stored in [the pages folder][pages-folder] on GitHub. Pages are grouped in a folder per sidebar section. Each page can contain a `<page name>.markdown` description file, a `<page name>.ts` demo file or both. If both are defined the description is rendered first. The description can contain metadata to specify the title of the page.
|
||||
@@ -43,12 +41,15 @@ import { html, css, LitElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
|
||||
|
||||
@customElement("demo-user-experience-usability")
|
||||
export class DemoUserExperienceUsability extends LitElement {
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-content">Hello world!</div>
|
||||
<div class="card-content">
|
||||
Hello world!
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ const CONFIGS = [
|
||||
heading: "markdown-it demo",
|
||||
config: `
|
||||
- type: markdown
|
||||
content: >-
|
||||
content: >
|
||||
# h1 Heading 8-)
|
||||
|
||||
## h2 Heading
|
||||
@@ -249,17 +249,6 @@ const CONFIGS = [
|
||||
::: warning
|
||||
*here be dragons*
|
||||
:::
|
||||
|
||||
### ha-alert
|
||||
|
||||
You can use our [\`ha-alert\`](https://design.home-assistant.io/#components/ha-alert) component in markdown content rendered in the Home Assistant Frontend.
|
||||
|
||||
<ha-alert alert-type="error">This is an error alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="warning">This is a warning alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="info">This is an info alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="success">This is a success alert — check it out!</ha-alert>
|
||||
<ha-alert title="Test alert">This is an alert with a title</ha-alert>
|
||||
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
@@ -188,7 +188,6 @@ const createEntityRegistryEntries = (
|
||||
device_id: "mock-device-id",
|
||||
area_id: null,
|
||||
disabled_by: null,
|
||||
hidden_by: null,
|
||||
entity_category: null,
|
||||
entity_id: "binary_sensor.updater",
|
||||
name: null,
|
||||
|
@@ -1,3 +0,0 @@
|
||||
---
|
||||
title: Update
|
||||
---
|
@@ -1,185 +0,0 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
UPDATE_SUPPORT_BACKUP,
|
||||
UPDATE_SUPPORT_PROGRESS,
|
||||
UPDATE_SUPPORT_INSTALL,
|
||||
UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
UPDATE_SUPPORT_AUTO_UPDATE,
|
||||
} from "../../../../src/data/update";
|
||||
import "../../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import {
|
||||
MockHomeAssistant,
|
||||
provideHass,
|
||||
} from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-more-infos";
|
||||
import { LONG_TEXT } from "../../data/text";
|
||||
|
||||
const base_attributes = {
|
||||
title: "Awesome",
|
||||
current_version: "1.2.2",
|
||||
latest_version: "1.2.3",
|
||||
release_url: "https://home-assistant.io",
|
||||
supported_features: UPDATE_SUPPORT_INSTALL,
|
||||
skipped_version: null,
|
||||
in_progress: false,
|
||||
release_summary:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In nec metus aliquet, porta mi ut, ultrices odio. Etiam egestas orci tellus, non semper metus blandit tincidunt. Praesent elementum turpis vel tempor pharetra. Sed quis cursus diam. Proin sem justo.",
|
||||
};
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("update", "update1", "on", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update",
|
||||
}),
|
||||
getEntity("update", "update2", "on", {
|
||||
...base_attributes,
|
||||
title: null,
|
||||
friendly_name: "Update without title",
|
||||
}),
|
||||
getEntity("update", "update3", "on", {
|
||||
...base_attributes,
|
||||
release_url: null,
|
||||
friendly_name: "Update without release_url",
|
||||
}),
|
||||
getEntity("update", "update4", "on", {
|
||||
...base_attributes,
|
||||
release_summary: null,
|
||||
friendly_name: "Update without release_summary",
|
||||
}),
|
||||
getEntity("update", "update5", "off", {
|
||||
...base_attributes,
|
||||
current_version: "1.2.3",
|
||||
friendly_name: "No update",
|
||||
}),
|
||||
getEntity("update", "update6", "off", {
|
||||
...base_attributes,
|
||||
skipped_version: "1.2.3",
|
||||
friendly_name: "Skipped version",
|
||||
}),
|
||||
getEntity("update", "update7", "on", {
|
||||
...base_attributes,
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_BACKUP,
|
||||
friendly_name: "With backup support",
|
||||
}),
|
||||
getEntity("update", "update8", "on", {
|
||||
...base_attributes,
|
||||
in_progress: true,
|
||||
friendly_name: "With true in_progress",
|
||||
}),
|
||||
getEntity("update", "update9", "on", {
|
||||
...base_attributes,
|
||||
in_progress: 25,
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
||||
friendly_name: "With 25 in_progress",
|
||||
}),
|
||||
getEntity("update", "update10", "on", {
|
||||
...base_attributes,
|
||||
in_progress: 50,
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
||||
friendly_name: "With 50 in_progress",
|
||||
}),
|
||||
getEntity("update", "update11", "on", {
|
||||
...base_attributes,
|
||||
in_progress: 75,
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
||||
friendly_name: "With 75 in_progress",
|
||||
}),
|
||||
getEntity("update", "update12", "unavailable", {
|
||||
...base_attributes,
|
||||
in_progress: 50,
|
||||
friendly_name: "Unavailable",
|
||||
}),
|
||||
getEntity("update", "update13", "on", {
|
||||
...base_attributes,
|
||||
supported_features: 0,
|
||||
friendly_name: "No install support",
|
||||
}),
|
||||
getEntity("update", "update14", "off", {
|
||||
...base_attributes,
|
||||
current_version: null,
|
||||
friendly_name: "Update without current_version",
|
||||
}),
|
||||
getEntity("update", "update15", "off", {
|
||||
...base_attributes,
|
||||
latest_version: null,
|
||||
friendly_name: "Update without latest_version",
|
||||
}),
|
||||
getEntity("update", "update16", "off", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update with release notes",
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
}),
|
||||
getEntity("update", "update17", "off", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update with release notes error",
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
}),
|
||||
getEntity("update", "update18", "off", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update with release notes loading",
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
}),
|
||||
getEntity("update", "update18", "on", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update with auto update",
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_AUTO_UPDATE,
|
||||
}),
|
||||
];
|
||||
|
||||
@customElement("demo-more-info-update")
|
||||
class DemoMoreInfoUpdate extends LitElement {
|
||||
@property() public hass!: MockHomeAssistant;
|
||||
|
||||
@query("demo-more-infos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<demo-more-infos
|
||||
.hass=${this.hass}
|
||||
.entities=${ENTITIES.map((ent) => ent.entityId)}
|
||||
></demo-more-infos>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
hass.mockWS(
|
||||
"update/release_notes",
|
||||
(msg: { type: string; entity_id: string }) => {
|
||||
if (msg.entity_id === "update.update16") {
|
||||
return LONG_TEXT;
|
||||
}
|
||||
if (msg.entity_id === "update.update17") {
|
||||
return Promise.reject({
|
||||
code: "error",
|
||||
message: "Could not fetch release notes",
|
||||
});
|
||||
}
|
||||
if (msg.entity_id === "update.update18") {
|
||||
return undefined;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-more-info-update": DemoMoreInfoUpdate;
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
---
|
||||
title: "User types"
|
||||
---
|
||||
|
||||
We have defined three user types for Home Assistant. They are a lean segmentation of users that helps us make decisions throughout the product. User types differ from traditional personas in that the segmentation criteria aren’t demographic and don’t personify a group into a single character with a fictitious background story.
|
||||
|
||||
# Outgrowers
|
||||
|
||||
Users that outgrow big tech smart home solutions. It just needs to work with easy setup via an app.
|
||||
|
||||
# Tinkerers
|
||||
|
||||
Technoid users in home networking and development that know how to code.
|
||||
|
||||
# Questioner
|
||||
|
||||
Users who want more advanced home automation, but need support to make it work.
|
@@ -1,4 +1,5 @@
|
||||
import { mdiFolderUpload } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input-container";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
|
@@ -121,8 +121,7 @@ export class HassioMain extends SupervisorBaseElement {
|
||||
this.parentElement,
|
||||
this.hass.themes,
|
||||
themeName,
|
||||
themeSettings,
|
||||
true
|
||||
themeSettings
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -54,14 +54,14 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
type updateType = "os" | "supervisor" | "core" | "addon";
|
||||
|
||||
const SUPERVISOR_UPDATE_NAMES = {
|
||||
core: "Home Assistant Core",
|
||||
os: "Home Assistant Operating System",
|
||||
supervisor: "Home Assistant Supervisor",
|
||||
};
|
||||
|
||||
type updateType = "os" | "supervisor" | "core" | "addon";
|
||||
|
||||
const changelogUrl = (
|
||||
entry: updateType,
|
||||
version: string
|
||||
|
@@ -72,13 +72,14 @@
|
||||
"@material/mwc-textfield": "0.25.3",
|
||||
"@material/mwc-top-app-bar-fixed": "^0.25.3",
|
||||
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
|
||||
"@mdi/js": "6.6.95",
|
||||
"@mdi/svg": "6.6.95",
|
||||
"@mdi/js": "6.5.95",
|
||||
"@mdi/svg": "6.5.95",
|
||||
"@polymer/app-layout": "^3.1.0",
|
||||
"@polymer/iron-flex-layout": "^3.0.1",
|
||||
"@polymer/iron-icon": "^3.0.1",
|
||||
"@polymer/iron-input": "^3.0.1",
|
||||
"@polymer/iron-resizable-behavior": "^3.0.1",
|
||||
"@polymer/paper-dropdown-menu": "^3.2.0",
|
||||
"@polymer/paper-input": "^3.2.1",
|
||||
"@polymer/paper-item": "^3.0.1",
|
||||
"@polymer/paper-listbox": "^3.0.1",
|
||||
@@ -108,7 +109,7 @@
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^1.1.5",
|
||||
"home-assistant-js-websocket": "^7.0.1",
|
||||
"home-assistant-js-websocket": "^6.0.1",
|
||||
"idb-keyval": "^5.1.3",
|
||||
"intl-messageformat": "^9.9.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
@@ -135,6 +136,7 @@
|
||||
"vis-network": "^8.5.4",
|
||||
"vue": "^2.6.12",
|
||||
"vue2-daterange-picker": "^0.5.1",
|
||||
"web-animations-js": "^2.3.2",
|
||||
"workbox-cacheable-response": "^6.4.2",
|
||||
"workbox-core": "^6.4.2",
|
||||
"workbox-expiration": "^6.4.2",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = home-assistant-frontend
|
||||
version = 20220330.0
|
||||
version = 20220301.0
|
||||
author = The Home Assistant Authors
|
||||
author_email = hello@home-assistant.io
|
||||
license = Apache-2.0
|
||||
|
7
setup.py
Normal file
7
setup.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
Entry point for setuptools. Required for editable installs.
|
||||
TODO: Remove file after updating to pip 21.3
|
||||
"""
|
||||
from setuptools import setup
|
||||
|
||||
setup()
|
@@ -101,19 +101,13 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
this._fetchAuthProviders();
|
||||
|
||||
if (matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
applyThemesOnElement(
|
||||
document.documentElement,
|
||||
{
|
||||
default_theme: "default",
|
||||
default_dark_theme: null,
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
applyThemesOnElement(document.documentElement, {
|
||||
default_theme: "default",
|
||||
default_dark_theme: null,
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.redirectUri) {
|
||||
|
@@ -187,7 +187,6 @@ export const DOMAINS_WITH_MORE_INFO = [
|
||||
"scene",
|
||||
"sun",
|
||||
"timer",
|
||||
"update",
|
||||
"vacuum",
|
||||
"water_heater",
|
||||
"weather",
|
||||
@@ -201,7 +200,6 @@ export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [
|
||||
"input_text",
|
||||
"number",
|
||||
"scene",
|
||||
"update",
|
||||
"select",
|
||||
];
|
||||
|
||||
|
@@ -31,12 +31,11 @@ export const applyThemesOnElement = (
|
||||
element,
|
||||
themes: HomeAssistant["themes"],
|
||||
selectedTheme?: string,
|
||||
themeSettings?: Partial<HomeAssistant["selectedTheme"]>,
|
||||
main?: boolean
|
||||
themeSettings?: Partial<HomeAssistant["selectedTheme"]>
|
||||
) => {
|
||||
// If there is no explicitly desired theme provided, and the element is the main element we automatically
|
||||
// If there is no explicitly desired theme provided, we automatically
|
||||
// use the active one from `themes`.
|
||||
const themeToApply = selectedTheme || (main ? themes.theme : undefined);
|
||||
const themeToApply = selectedTheme || themes.theme;
|
||||
|
||||
// If there is no explicitly desired dark mode provided, we automatically
|
||||
// use the active one from `themes`.
|
||||
@@ -48,7 +47,7 @@ export const applyThemesOnElement = (
|
||||
let cacheKey = themeToApply;
|
||||
let themeRules: Partial<ThemeVars> = {};
|
||||
|
||||
if (themeToApply && darkMode) {
|
||||
if (darkMode) {
|
||||
cacheKey = `${cacheKey}__dark`;
|
||||
themeRules = { ...darkStyles };
|
||||
}
|
||||
|
@@ -1,18 +1,12 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||
import { FrontendLocaleData } from "../../data/translation";
|
||||
import {
|
||||
updateIsInstalling,
|
||||
UpdateEntity,
|
||||
UPDATE_SUPPORT_PROGRESS,
|
||||
} from "../../data/update";
|
||||
import { formatDate } from "../datetime/format_date";
|
||||
import { formatDateTime } from "../datetime/format_date_time";
|
||||
import { formatTime } from "../datetime/format_time";
|
||||
import { formatNumber, isNumericState } from "../number/format_number";
|
||||
import { LocalizeFunc } from "../translations/localize";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
import { supportsFeature } from "./supports-feature";
|
||||
|
||||
export const computeStateDisplay = (
|
||||
localize: LocalizeFunc,
|
||||
@@ -136,28 +130,6 @@ export const computeStateDisplay = (
|
||||
}
|
||||
}
|
||||
|
||||
if (domain === "update") {
|
||||
// When updating, and entity does not support % show "Installing"
|
||||
// When updating, and entity does support % show "Installing (xx%)"
|
||||
// When update available, show the version
|
||||
// When the latest version is skipped, show the latest version
|
||||
// When update is not available, show "Up-to-date"
|
||||
// When update is not available and there is no latest_version show "Unavailable"
|
||||
return compareState === "on"
|
||||
? updateIsInstalling(stateObj as UpdateEntity)
|
||||
? supportsFeature(stateObj, UPDATE_SUPPORT_PROGRESS)
|
||||
? localize("ui.card.update.installing_with_progress", {
|
||||
progress: stateObj.attributes.in_progress,
|
||||
})
|
||||
: localize("ui.card.update.installing")
|
||||
: stateObj.attributes.latest_version
|
||||
: stateObj.attributes.skipped_version ===
|
||||
stateObj.attributes.latest_version
|
||||
? stateObj.attributes.latest_version ??
|
||||
localize("state.default.unavailable")
|
||||
: localize("ui.card.update.up_to_date");
|
||||
}
|
||||
|
||||
return (
|
||||
// Return device class translation
|
||||
(stateObj.attributes.device_class &&
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
|
||||
export const computeStateDomain = (stateObj: HassEntity) =>
|
||||
|
@@ -9,10 +9,11 @@ import {
|
||||
mdiCast,
|
||||
mdiCastConnected,
|
||||
mdiClock,
|
||||
mdiEmoticonDead,
|
||||
mdiFlash,
|
||||
mdiGestureTapButton,
|
||||
mdiLanConnect,
|
||||
mdiLanDisconnect,
|
||||
mdiLightSwitch,
|
||||
mdiLock,
|
||||
mdiLockAlert,
|
||||
mdiLockClock,
|
||||
@@ -21,16 +22,16 @@ import {
|
||||
mdiPowerPlug,
|
||||
mdiPowerPlugOff,
|
||||
mdiRestart,
|
||||
mdiSleep,
|
||||
mdiTimerSand,
|
||||
mdiToggleSwitch,
|
||||
mdiToggleSwitchOff,
|
||||
mdiCheckCircleOutline,
|
||||
mdiCloseCircleOutline,
|
||||
mdiWeatherNight,
|
||||
mdiPackage,
|
||||
mdiPackageDown,
|
||||
mdiZWave,
|
||||
} from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { updateIsInstalling, UpdateEntity } from "../../data/update";
|
||||
/**
|
||||
* Return the icon to be used for a domain.
|
||||
*
|
||||
@@ -111,7 +112,19 @@ export const domainIcon = (
|
||||
case "switch":
|
||||
return compareState === "on" ? mdiToggleSwitch : mdiToggleSwitchOff;
|
||||
default:
|
||||
return mdiLightSwitch;
|
||||
return mdiFlash;
|
||||
}
|
||||
|
||||
case "zwave":
|
||||
switch (compareState) {
|
||||
case "dead":
|
||||
return mdiEmoticonDead;
|
||||
case "sleeping":
|
||||
return mdiSleep;
|
||||
case "initializing":
|
||||
return mdiTimerSand;
|
||||
default:
|
||||
return mdiZWave;
|
||||
}
|
||||
|
||||
case "sensor": {
|
||||
@@ -136,13 +149,6 @@ export const domainIcon = (
|
||||
return stateObj?.state === "above_horizon"
|
||||
? FIXED_DOMAIN_ICONS[domain]
|
||||
: mdiWeatherNight;
|
||||
|
||||
case "update":
|
||||
return compareState === "on"
|
||||
? updateIsInstalling(stateObj as UpdateEntity)
|
||||
? mdiPackageDown
|
||||
: mdiPackageUp
|
||||
: mdiPackage;
|
||||
}
|
||||
|
||||
if (domain in FIXED_DOMAIN_ICONS) {
|
||||
|
@@ -1,4 +0,0 @@
|
||||
const regexp =
|
||||
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
|
||||
export const isIPAddress = (input: string): boolean => regexp.test(input);
|
@@ -7,7 +7,6 @@ export const iconColorCSS = css`
|
||||
ha-state-icon[data-domain="calendar"][data-state="on"],
|
||||
ha-state-icon[data-domain="camera"][data-state="streaming"],
|
||||
ha-state-icon[data-domain="cover"][data-state="open"],
|
||||
ha-state-icon[data-domain="device_tracker"][data-state="home"],
|
||||
ha-state-icon[data-domain="fan"][data-state="on"],
|
||||
ha-state-icon[data-domain="humidifier"][data-state="on"],
|
||||
ha-state-icon[data-domain="light"][data-state="on"],
|
||||
@@ -71,6 +70,9 @@ export const iconColorCSS = css`
|
||||
}
|
||||
|
||||
ha-state-icon[data-domain="plant"][data-state="problem"],
|
||||
ha-state-icon[data-domain="zwave"][data-state="dead"] {
|
||||
color: var(--state-icon-error-color);
|
||||
}
|
||||
|
||||
/* Color the icon if unavailable */
|
||||
ha-state-icon[data-state="unavailable"] {
|
||||
|
@@ -1,53 +0,0 @@
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
interface ResultCache<T> {
|
||||
[entityId: string]: Promise<T> | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a function with result caching per entity.
|
||||
* @param cacheKey key to store the cache on hass object
|
||||
* @param cacheTime time to cache the results
|
||||
* @param func function to fetch the data
|
||||
* @param hass Home Assistant object
|
||||
* @param entityId entity to fetch data for
|
||||
* @param args extra arguments to pass to the function to fetch the data
|
||||
* @returns
|
||||
*/
|
||||
export const timeCacheEntityPromiseFunc = async <T>(
|
||||
cacheKey: string,
|
||||
cacheTime: number,
|
||||
func: (hass: HomeAssistant, entityId: string, ...args: any[]) => Promise<T>,
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
...args: any[]
|
||||
): Promise<T> => {
|
||||
let cache: ResultCache<T> | undefined = (hass as any)[cacheKey];
|
||||
|
||||
if (!cache) {
|
||||
cache = hass[cacheKey] = {};
|
||||
}
|
||||
|
||||
const lastResult = cache[entityId];
|
||||
|
||||
if (lastResult) {
|
||||
return lastResult;
|
||||
}
|
||||
|
||||
const result = func(hass, entityId, ...args);
|
||||
cache[entityId] = result;
|
||||
|
||||
result.then(
|
||||
// When successful, set timer to clear cache
|
||||
() =>
|
||||
setTimeout(() => {
|
||||
cache![entityId] = undefined;
|
||||
}, cacheTime),
|
||||
// On failure, clear cache right away
|
||||
() => {
|
||||
cache![entityId] = undefined;
|
||||
}
|
||||
);
|
||||
|
||||
return result;
|
||||
};
|
@@ -1,80 +1,43 @@
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
interface CacheResult<T> {
|
||||
result: T;
|
||||
cacheKey: any;
|
||||
interface ResultCache<T> {
|
||||
[entityId: string]: Promise<T> | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches a result of a promise for X time. Allows optional extra validation
|
||||
* check to invalidate the cache.
|
||||
* @param cacheKey the key to store the cache
|
||||
* @param cacheTime the time to cache the result
|
||||
* @param func the function to fetch the data
|
||||
* @param generateCacheKey optional function to generate a cache key based on current hass + cached result. Cache is invalid if generates a different cache key.
|
||||
* @param hass Home Assistant object
|
||||
* @param args extra arguments to pass to the function to fetch the data
|
||||
* @returns
|
||||
*/
|
||||
export const timeCachePromiseFunc = async <T>(
|
||||
cacheKey: string,
|
||||
cacheTime: number,
|
||||
func: (hass: HomeAssistant, ...args: any[]) => Promise<T>,
|
||||
generateCacheKey:
|
||||
| ((hass: HomeAssistant, lastResult: T) => unknown)
|
||||
| undefined,
|
||||
func: (hass: HomeAssistant, entityId: string, ...args: any[]) => Promise<T>,
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
...args: any[]
|
||||
): Promise<T> => {
|
||||
const anyHass = hass as any;
|
||||
const lastResult: Promise<CacheResult<T>> | CacheResult<T> | undefined =
|
||||
anyHass[cacheKey];
|
||||
let cache: ResultCache<T> | undefined = (hass as any)[cacheKey];
|
||||
|
||||
const checkCachedResult = (result: CacheResult<T>): T | Promise<T> => {
|
||||
if (
|
||||
!generateCacheKey ||
|
||||
generateCacheKey(hass, result.result) === result.cacheKey
|
||||
) {
|
||||
return result.result;
|
||||
}
|
||||
|
||||
anyHass[cacheKey] = undefined;
|
||||
return timeCachePromiseFunc(
|
||||
cacheKey,
|
||||
cacheTime,
|
||||
func,
|
||||
generateCacheKey,
|
||||
hass,
|
||||
...args
|
||||
);
|
||||
};
|
||||
|
||||
// If we have a cached result, return it if it's still valid
|
||||
if (lastResult) {
|
||||
return lastResult instanceof Promise
|
||||
? lastResult.then(checkCachedResult)
|
||||
: checkCachedResult(lastResult);
|
||||
if (!cache) {
|
||||
cache = hass[cacheKey] = {};
|
||||
}
|
||||
|
||||
const resultPromise = func(hass, ...args);
|
||||
anyHass[cacheKey] = resultPromise;
|
||||
const lastResult = cache[entityId];
|
||||
|
||||
resultPromise.then(
|
||||
if (lastResult) {
|
||||
return lastResult;
|
||||
}
|
||||
|
||||
const result = func(hass, entityId, ...args);
|
||||
cache[entityId] = result;
|
||||
|
||||
result.then(
|
||||
// When successful, set timer to clear cache
|
||||
(result) => {
|
||||
anyHass[cacheKey] = {
|
||||
result,
|
||||
cacheKey: generateCacheKey?.(hass, result),
|
||||
};
|
||||
() =>
|
||||
setTimeout(() => {
|
||||
anyHass[cacheKey] = undefined;
|
||||
}, cacheTime);
|
||||
},
|
||||
cache![entityId] = undefined;
|
||||
}, cacheTime),
|
||||
// On failure, clear cache right away
|
||||
() => {
|
||||
anyHass[cacheKey] = undefined;
|
||||
cache![entityId] = undefined;
|
||||
}
|
||||
);
|
||||
|
||||
return resultPromise;
|
||||
return result;
|
||||
};
|
||||
|
@@ -1,9 +1,8 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiAlertOctagram, mdiCheckBold } from "@mdi/js";
|
||||
import type { Button } from "@material/mwc-button";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../ha-circular-progress";
|
||||
import "../ha-svg-icon";
|
||||
|
||||
@customElement("ha-progress-button")
|
||||
export class HaProgressButton extends LitElement {
|
||||
@@ -13,53 +12,38 @@ export class HaProgressButton extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public raised = false;
|
||||
|
||||
@state() private _result?: "success" | "error";
|
||||
@query("mwc-button", true) private _button?: Button;
|
||||
|
||||
public render(): TemplateResult {
|
||||
const overlay = this._result || this.progress;
|
||||
return html`
|
||||
<mwc-button
|
||||
?raised=${this.raised}
|
||||
.disabled=${this.disabled || this.progress}
|
||||
@click=${this._buttonTapped}
|
||||
class=${this._result || ""}
|
||||
>
|
||||
<slot></slot>
|
||||
</mwc-button>
|
||||
${!overlay
|
||||
? ""
|
||||
: html`
|
||||
<div class="progress">
|
||||
${this._result === "success"
|
||||
? html`<ha-svg-icon .path=${mdiCheckBold}></ha-svg-icon>`
|
||||
: this._result === "error"
|
||||
? html`<ha-svg-icon .path=${mdiAlertOctagram}></ha-svg-icon>`
|
||||
: this.progress
|
||||
? html`
|
||||
<ha-circular-progress
|
||||
size="small"
|
||||
active
|
||||
></ha-circular-progress>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`}
|
||||
${this.progress
|
||||
? html`<div class="progress">
|
||||
<ha-circular-progress size="small" active></ha-circular-progress>
|
||||
</div>`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
public actionSuccess(): void {
|
||||
this._setResult("success");
|
||||
this._tempClass("success");
|
||||
}
|
||||
|
||||
public actionError(): void {
|
||||
this._setResult("error");
|
||||
this._tempClass("error");
|
||||
}
|
||||
|
||||
private _setResult(result: "success" | "error"): void {
|
||||
this._result = result;
|
||||
private _tempClass(className: string): void {
|
||||
this._button!.classList.add(className);
|
||||
setTimeout(() => {
|
||||
this._result = undefined;
|
||||
}, 2000);
|
||||
this._button!.classList.remove(className);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
private _buttonTapped(ev: Event): void {
|
||||
@@ -85,7 +69,6 @@ export class HaProgressButton extends LitElement {
|
||||
background-color: var(--success-color);
|
||||
transition: none;
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
mwc-button[raised].success {
|
||||
@@ -98,7 +81,6 @@ export class HaProgressButton extends LitElement {
|
||||
background-color: var(--error-color);
|
||||
transition: none;
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
mwc-button[raised].error {
|
||||
@@ -107,21 +89,13 @@ export class HaProgressButton extends LitElement {
|
||||
}
|
||||
|
||||
.progress {
|
||||
bottom: 4px;
|
||||
bottom: 0;
|
||||
margin-top: 4px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 4px;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ha-svg-icon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
mwc-button.success slot,
|
||||
mwc-button.error slot {
|
||||
visibility: hidden;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -86,8 +86,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
@state() private _opened?: boolean;
|
||||
|
||||
@query("ha-combo-box", true) public comboBox!: HaComboBox;
|
||||
@@ -271,7 +269,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
.value=${this._value}
|
||||
.renderer=${rowRenderer}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
item-value-path="id"
|
||||
item-label-path="name"
|
||||
@opened-changed=${this._openedChanged}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
@@ -11,8 +11,6 @@ class HaDevicesPicker extends LitElement {
|
||||
|
||||
@property() public value?: string[];
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
/**
|
||||
* Show entities from specific domains.
|
||||
* @type {string}
|
||||
@@ -68,7 +66,6 @@ class HaDevicesPicker extends LitElement {
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.label=${this.pickDeviceLabel}
|
||||
.required=${this.required}
|
||||
@value-changed=${this._addDevice}
|
||||
></ha-device-picker>
|
||||
</div>
|
||||
@@ -119,12 +116,6 @@ class HaDevicesPicker extends LitElement {
|
||||
|
||||
this._updateDevices([...currentDevices, toAdd]);
|
||||
}
|
||||
|
||||
static override styles = css`
|
||||
div {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -14,8 +14,6 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
|
||||
@property({ type: Array }) public value?: string[];
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
/**
|
||||
* Show entities from specific domains.
|
||||
* @type {string}
|
||||
@@ -48,29 +46,11 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
@property({ type: Array, attribute: "include-unit-of-measurement" })
|
||||
public includeUnitOfMeasurement?: string[];
|
||||
|
||||
/**
|
||||
* List of allowed entities to show. Will ignore all other filters.
|
||||
* @type {Array}
|
||||
* @attr include-entities
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-entities" })
|
||||
public includeEntities?: string[];
|
||||
|
||||
/**
|
||||
* List of entities to be excluded.
|
||||
* @type {Array}
|
||||
* @attr exclude-entities
|
||||
*/
|
||||
@property({ type: Array, attribute: "exclude-entities" })
|
||||
public excludeEntities?: string[];
|
||||
|
||||
@property({ attribute: "picked-entity-label" })
|
||||
public pickedEntityLabel?: string;
|
||||
|
||||
@property({ attribute: "pick-entity-label" }) public pickEntityLabel?: string;
|
||||
|
||||
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
@@ -87,8 +67,6 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
.includeEntities=${this.includeEntities}
|
||||
.excludeEntities=${this.excludeEntities}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeUnitOfMeasurement=${this.includeUnitOfMeasurement}
|
||||
.entityFilter=${this._entityFilter}
|
||||
@@ -104,13 +82,10 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
.includeEntities=${this.includeEntities}
|
||||
.excludeEntities=${this.excludeEntities}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeUnitOfMeasurement=${this.includeUnitOfMeasurement}
|
||||
.entityFilter=${this._entityFilter}
|
||||
.label=${this.pickEntityLabel}
|
||||
.required=${this.required}
|
||||
@value-changed=${this._addEntity}
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
@@ -119,9 +94,7 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
|
||||
private _entityFilter: HaEntityPickerEntityFilterFunc = (
|
||||
stateObj: HassEntity
|
||||
) =>
|
||||
(!this.value || !this.value.includes(stateObj.entity_id)) &&
|
||||
(!this.entityFilter || this.entityFilter(stateObj));
|
||||
) => !this.value || !this.value.includes(stateObj.entity_id);
|
||||
|
||||
private get _currentEntities() {
|
||||
return this.value || [];
|
||||
|
@@ -19,8 +19,6 @@ class HaEntityAttributePicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "allow-custom-value" })
|
||||
public allowCustomValue;
|
||||
|
||||
@@ -63,7 +61,6 @@ class HaEntityAttributePicker extends LitElement {
|
||||
"ui.components.entity.entity-attribute-picker.attribute"
|
||||
)}
|
||||
.disabled=${this.disabled || !this.entityId}
|
||||
.required=${this.required}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
item-value-path="value"
|
||||
item-label-path="label"
|
||||
|
@@ -7,7 +7,6 @@ import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-combo-box";
|
||||
@@ -16,21 +15,18 @@ import "../ha-icon-button";
|
||||
import "../ha-svg-icon";
|
||||
import "./state-badge";
|
||||
|
||||
interface HassEntityWithCachedName extends HassEntity {
|
||||
friendly_name: string;
|
||||
}
|
||||
|
||||
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
||||
|
||||
// eslint-disable-next-line lit/prefer-static-styles
|
||||
const rowRenderer: ComboBoxLitRenderer<HassEntityWithCachedName> = (item) =>
|
||||
html`<mwc-list-item graphic="avatar" .twoline=${!!item.entity_id}>
|
||||
${item.state
|
||||
? html`<state-badge slot="graphic" .stateObj=${item}></state-badge>`
|
||||
: ""}
|
||||
<span>${item.friendly_name}</span>
|
||||
<span slot="secondary">${item.entity_id}</span>
|
||||
</mwc-list-item>`;
|
||||
const rowRenderer: ComboBoxLitRenderer<HassEntity & { friendly_name: string }> =
|
||||
(item) =>
|
||||
html`<mwc-list-item graphic="avatar" .twoline=${!!item.entity_id}>
|
||||
${item.state
|
||||
? html`<state-badge slot="graphic" .stateObj=${item}></state-badge>`
|
||||
: ""}
|
||||
<span>${item.friendly_name}</span>
|
||||
<span slot="secondary">${item.entity_id}</span>
|
||||
</mwc-list-item>`;
|
||||
@customElement("ha-entity-picker")
|
||||
export class HaEntityPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -39,8 +35,6 @@ export class HaEntityPicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
@property({ type: Boolean, attribute: "allow-custom-entity" })
|
||||
public allowCustomEntity;
|
||||
|
||||
@@ -80,22 +74,6 @@ export class HaEntityPicker extends LitElement {
|
||||
@property({ type: Array, attribute: "include-unit-of-measurement" })
|
||||
public includeUnitOfMeasurement?: string[];
|
||||
|
||||
/**
|
||||
* List of allowed entities to show. Will ignore all other filters.
|
||||
* @type {Array}
|
||||
* @attr include-entities
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-entities" })
|
||||
public includeEntities?: string[];
|
||||
|
||||
/**
|
||||
* List of entities to be excluded.
|
||||
* @type {Array}
|
||||
* @attr exclude-entities
|
||||
*/
|
||||
@property({ type: Array, attribute: "exclude-entities" })
|
||||
public excludeEntities?: string[];
|
||||
|
||||
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
|
||||
|
||||
@property({ type: Boolean }) public hideClearIcon = false;
|
||||
@@ -118,7 +96,7 @@ export class HaEntityPicker extends LitElement {
|
||||
|
||||
private _initedStates = false;
|
||||
|
||||
private _states: HassEntityWithCachedName[] = [];
|
||||
private _states: HassEntity[] = [];
|
||||
|
||||
private _getStates = memoizeOne(
|
||||
(
|
||||
@@ -128,11 +106,9 @@ export class HaEntityPicker extends LitElement {
|
||||
excludeDomains: this["excludeDomains"],
|
||||
entityFilter: this["entityFilter"],
|
||||
includeDeviceClasses: this["includeDeviceClasses"],
|
||||
includeUnitOfMeasurement: this["includeUnitOfMeasurement"],
|
||||
includeEntities: this["includeEntities"],
|
||||
excludeEntities: this["excludeEntities"]
|
||||
): HassEntityWithCachedName[] => {
|
||||
let states: HassEntityWithCachedName[] = [];
|
||||
includeUnitOfMeasurement: this["includeUnitOfMeasurement"]
|
||||
) => {
|
||||
let states: HassEntity[] = [];
|
||||
|
||||
if (!hass) {
|
||||
return [];
|
||||
@@ -146,7 +122,7 @@ export class HaEntityPicker extends LitElement {
|
||||
state: "",
|
||||
last_changed: "",
|
||||
last_updated: "",
|
||||
context: { id: "", user_id: null, parent_id: null },
|
||||
context: { id: "", user_id: null },
|
||||
friendly_name: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_entities"
|
||||
),
|
||||
@@ -160,30 +136,6 @@ export class HaEntityPicker extends LitElement {
|
||||
];
|
||||
}
|
||||
|
||||
if (includeEntities) {
|
||||
entityIds = entityIds.filter((entityId) =>
|
||||
this.includeEntities!.includes(entityId)
|
||||
);
|
||||
|
||||
return entityIds
|
||||
.map((key) => ({
|
||||
...hass!.states[key],
|
||||
friendly_name: computeStateName(hass!.states[key]) || key,
|
||||
}))
|
||||
.sort((entityA, entityB) =>
|
||||
caseInsensitiveStringCompare(
|
||||
entityA.friendly_name,
|
||||
entityB.friendly_name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (excludeEntities) {
|
||||
entityIds = entityIds.filter(
|
||||
(entityId) => !excludeEntities!.includes(entityId)
|
||||
);
|
||||
}
|
||||
|
||||
if (includeDomains) {
|
||||
entityIds = entityIds.filter((eid) =>
|
||||
includeDomains.includes(computeDomain(eid))
|
||||
@@ -196,17 +148,10 @@ export class HaEntityPicker extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
states = entityIds
|
||||
.map((key) => ({
|
||||
...hass!.states[key],
|
||||
friendly_name: computeStateName(hass!.states[key]) || key,
|
||||
}))
|
||||
.sort((entityA, entityB) =>
|
||||
caseInsensitiveStringCompare(
|
||||
entityA.friendly_name,
|
||||
entityB.friendly_name
|
||||
)
|
||||
);
|
||||
states = entityIds.sort().map((key) => ({
|
||||
...hass!.states[key],
|
||||
friendly_name: computeStateName(hass!.states[key]) || key,
|
||||
}));
|
||||
|
||||
if (includeDeviceClasses) {
|
||||
states = states.filter(
|
||||
@@ -245,7 +190,7 @@ export class HaEntityPicker extends LitElement {
|
||||
state: "",
|
||||
last_changed: "",
|
||||
last_updated: "",
|
||||
context: { id: "", user_id: null, parent_id: null },
|
||||
context: { id: "", user_id: null },
|
||||
friendly_name: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_match"
|
||||
),
|
||||
@@ -283,9 +228,7 @@ export class HaEntityPicker extends LitElement {
|
||||
this.excludeDomains,
|
||||
this.entityFilter,
|
||||
this.includeDeviceClasses,
|
||||
this.includeUnitOfMeasurement,
|
||||
this.includeEntities,
|
||||
this.excludeEntities
|
||||
this.includeUnitOfMeasurement
|
||||
);
|
||||
if (this._initedStates) {
|
||||
(this.comboBox as any).filteredItems = this._states;
|
||||
@@ -307,7 +250,6 @@ export class HaEntityPicker extends LitElement {
|
||||
.allowCustomValue=${this.allowCustomEntity}
|
||||
.filteredItems=${this._states}
|
||||
.renderer=${rowRenderer}
|
||||
.required=${this.required}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
|
@@ -13,12 +13,9 @@ import { HaComboBox } from "./ha-combo-box";
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (
|
||||
item
|
||||
) => html`<mwc-list-item twoline graphic="icon">
|
||||
) => html`<mwc-list-item twoline>
|
||||
<span>${item.name}</span>
|
||||
<span slot="secondary">${item.slug}</span>
|
||||
${item.icon
|
||||
? html`<img slot="graphic" .src="/api/hassio/addons/${item.slug}/icon" />`
|
||||
: ""}
|
||||
</mwc-list-item>`;
|
||||
|
||||
@customElement("ha-addon-picker")
|
||||
@@ -33,8 +30,6 @@ class HaAddonPicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@query("ha-combo-box") private _comboBox!: HaComboBox;
|
||||
|
||||
public open() {
|
||||
@@ -60,8 +55,6 @@ class HaAddonPicker extends LitElement {
|
||||
? this.hass.localize("ui.components.addon-picker.addon")
|
||||
: this.label}
|
||||
.value=${this._value}
|
||||
.required=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
.renderer=${rowRenderer}
|
||||
.items=${this._addons}
|
||||
item-value-path="slug"
|
||||
|
@@ -28,8 +28,8 @@ import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { PolymerChangedEvent } from "../polymer-types";
|
||||
import { HomeAssistant } from "../types";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import "./ha-combo-box";
|
||||
import type { HaComboBox } from "./ha-combo-box";
|
||||
import "./ha-combo-box";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@@ -84,8 +84,6 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
@state() private _areas?: AreaRegistryEntry[];
|
||||
|
||||
@state() private _devices?: DeviceRegistryEntry[];
|
||||
@@ -317,7 +315,6 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
item-label-path="name"
|
||||
.value=${this.value}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.label=${this.label === undefined && this.hass
|
||||
? this.hass.localize("ui.components.area-picker.area")
|
||||
: this.label}
|
||||
|
@@ -1,163 +0,0 @@
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { EntityRegistryEntry } from "../data/entity_registry";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import "./ha-area-picker";
|
||||
|
||||
@customElement("ha-areas-picker")
|
||||
export class HaAreasPicker extends SubscribeMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public value?: string[];
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-add" })
|
||||
public noAdd?: boolean;
|
||||
|
||||
/**
|
||||
* Show only areas with entities from specific domains.
|
||||
* @type {Array}
|
||||
* @attr include-domains
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-domains" })
|
||||
public includeDomains?: string[];
|
||||
|
||||
/**
|
||||
* Show no areas with entities of these domains.
|
||||
* @type {Array}
|
||||
* @attr exclude-domains
|
||||
*/
|
||||
@property({ type: Array, attribute: "exclude-domains" })
|
||||
public excludeDomains?: string[];
|
||||
|
||||
/**
|
||||
* Show only areas with entities of these device classes.
|
||||
* @type {Array}
|
||||
* @attr include-device-classes
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-device-classes" })
|
||||
public includeDeviceClasses?: string[];
|
||||
|
||||
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||
|
||||
@property() public entityFilter?: (entity: EntityRegistryEntry) => boolean;
|
||||
|
||||
@property({ attribute: "picked-area-label" })
|
||||
public pickedAreaLabel?: string;
|
||||
|
||||
@property({ attribute: "pick-area-label" })
|
||||
public pickAreaLabel?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const currentAreas = this._currentAreas;
|
||||
return html`
|
||||
${currentAreas.map(
|
||||
(area) => html`
|
||||
<div>
|
||||
<ha-area-picker
|
||||
.curValue=${area}
|
||||
.noAdd=${this.noAdd}
|
||||
.hass=${this.hass}
|
||||
.value=${area}
|
||||
.label=${this.pickedAreaLabel}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.entityFilter=${this.entityFilter}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._areaChanged}
|
||||
></ha-area-picker>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
<div>
|
||||
<ha-area-picker
|
||||
.noAdd=${this.noAdd}
|
||||
.hass=${this.hass}
|
||||
.label=${this.pickAreaLabel}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.entityFilter=${this.entityFilter}
|
||||
.disabled=${this.disabled}
|
||||
.placeholder=${this.placeholder}
|
||||
.required=${this.required}
|
||||
@value-changed=${this._addArea}
|
||||
></ha-area-picker>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _currentAreas(): string[] {
|
||||
return this.value || [];
|
||||
}
|
||||
|
||||
private async _updateAreas(areas) {
|
||||
this.value = areas;
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: areas,
|
||||
});
|
||||
}
|
||||
|
||||
private _areaChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const curValue = (ev.currentTarget as any).curValue;
|
||||
const newValue = ev.detail.value;
|
||||
if (newValue === curValue) {
|
||||
return;
|
||||
}
|
||||
const currentAreas = this._currentAreas;
|
||||
if (!newValue || currentAreas.includes(newValue)) {
|
||||
this._updateAreas(currentAreas.filter((ent) => ent !== curValue));
|
||||
return;
|
||||
}
|
||||
this._updateAreas(
|
||||
currentAreas.map((ent) => (ent === curValue ? newValue : ent))
|
||||
);
|
||||
}
|
||||
|
||||
private _addArea(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
const toAdd = ev.detail.value;
|
||||
if (!toAdd) {
|
||||
return;
|
||||
}
|
||||
(ev.currentTarget as any).value = "";
|
||||
const currentAreas = this._currentAreas;
|
||||
if (currentAreas.includes(toAdd)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateAreas([...currentAreas, toAdd]);
|
||||
}
|
||||
|
||||
static override styles = css`
|
||||
div {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-areas-picker": HaAreasPicker;
|
||||
}
|
||||
}
|
@@ -1,13 +1,12 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { LitElement, html, TemplateResult, css } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "./ha-select";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "./ha-textfield";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import "./ha-select";
|
||||
import "./ha-textfield";
|
||||
|
||||
export interface TimeChangedEvent {
|
||||
days?: number;
|
||||
hours: number;
|
||||
minutes: number;
|
||||
seconds: number;
|
||||
@@ -22,11 +21,6 @@ export class HaBaseTimeInput extends LitElement {
|
||||
*/
|
||||
@property() label?: string;
|
||||
|
||||
/**
|
||||
* Helper for the input
|
||||
*/
|
||||
@property() helper?: string;
|
||||
|
||||
/**
|
||||
* auto validate time inputs
|
||||
*/
|
||||
@@ -47,11 +41,6 @@ export class HaBaseTimeInput extends LitElement {
|
||||
*/
|
||||
@property({ type: Boolean }) disabled = false;
|
||||
|
||||
/**
|
||||
* day
|
||||
*/
|
||||
@property({ type: Number }) days = 0;
|
||||
|
||||
/**
|
||||
* hour
|
||||
*/
|
||||
@@ -72,11 +61,6 @@ export class HaBaseTimeInput extends LitElement {
|
||||
*/
|
||||
@property({ type: Number }) milliseconds = 0;
|
||||
|
||||
/**
|
||||
* Label for the day input
|
||||
*/
|
||||
@property() dayLabel = "";
|
||||
|
||||
/**
|
||||
* Label for the hour input
|
||||
*/
|
||||
@@ -107,11 +91,6 @@ export class HaBaseTimeInput extends LitElement {
|
||||
*/
|
||||
@property({ type: Boolean }) enableMillisecond = false;
|
||||
|
||||
/**
|
||||
* show the day field
|
||||
*/
|
||||
@property({ type: Boolean }) enableDay = false;
|
||||
|
||||
/**
|
||||
* limit hours input
|
||||
*/
|
||||
@@ -129,33 +108,8 @@ export class HaBaseTimeInput extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.label
|
||||
? html`<label>${this.label}${this.required ? "*" : ""}</label>`
|
||||
: ""}
|
||||
${this.label ? html`<label>${this.label}</label>` : ""}
|
||||
<div class="time-input-wrap">
|
||||
${this.enableDay
|
||||
? html`
|
||||
<ha-textfield
|
||||
id="day"
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
.value=${this.days}
|
||||
.label=${this.dayLabel}
|
||||
name="days"
|
||||
@input=${this._valueChanged}
|
||||
@focus=${this._onFocus}
|
||||
no-spinner
|
||||
.required=${this.required}
|
||||
.autoValidate=${this.autoValidate}
|
||||
min="0"
|
||||
.disabled=${this.disabled}
|
||||
suffix=":"
|
||||
class="hasSuffix"
|
||||
>
|
||||
</ha-textfield>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<ha-textfield
|
||||
id="hour"
|
||||
type="number"
|
||||
@@ -253,7 +207,6 @@ export class HaBaseTimeInput extends LitElement {
|
||||
<mwc-list-item value="PM">PM</mwc-list-item>
|
||||
</ha-select>`}
|
||||
</div>
|
||||
${this.helper ? html`<div class="helper">${this.helper}</div>` : ""}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -350,13 +303,6 @@ export class HaBaseTimeInput extends LitElement {
|
||||
color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.helper {
|
||||
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
|
||||
font-size: 0.75rem;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -25,7 +25,13 @@ export class HaChipSet extends LitElement {
|
||||
${unsafeCSS(chipStyles)}
|
||||
|
||||
slot::slotted(ha-chip) {
|
||||
margin: 4px 4px 4px 0;
|
||||
margin: 4px;
|
||||
}
|
||||
slot::slotted(ha-chip:first-of-type) {
|
||||
margin-left: -4px;
|
||||
}
|
||||
slot::slotted(ha-chip:last-of-type) {
|
||||
margin-right: -4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -14,8 +14,6 @@ import { customElement, property } from "lit/decorators";
|
||||
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(): TemplateResult {
|
||||
@@ -32,11 +30,6 @@ export class HaChip extends LitElement {
|
||||
<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>`
|
||||
: null}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -60,20 +53,14 @@ export class HaChip extends LitElement {
|
||||
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;
|
||||
.mdc-chip__icon--leading {
|
||||
--mdc-icon-size: 20px;
|
||||
color: var(--ha-chip-icon-color, var(--ha-chip-text-color));
|
||||
}
|
||||
.mdc-chip.no-text
|
||||
.mdc-chip__icon--leading:not(.mdc-chip__icon--leading-hidden) {
|
||||
margin-right: -4px;
|
||||
}
|
||||
|
||||
span[role="gridcell"] {
|
||||
line-height: 14px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -87,8 +87,6 @@ export class HaComboBox extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: "opened" })
|
||||
private _opened?: boolean;
|
||||
|
||||
@@ -110,22 +108,17 @@ export class HaComboBox extends LitElement {
|
||||
return this._comboBox.selectedItem;
|
||||
}
|
||||
|
||||
public setInputValue(value: string) {
|
||||
this._comboBox.value = value;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<vaadin-combo-box-light
|
||||
.itemValuePath=${this.itemValuePath}
|
||||
.itemIdPath=${this.itemIdPath}
|
||||
.itemLabelPath=${this.itemLabelPath}
|
||||
.items=${this.items}
|
||||
.value=${this.value || ""}
|
||||
.items=${this.items}
|
||||
.filteredItems=${this.filteredItems}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
${comboBoxRenderer(this.renderer || this._defaultRowRenderer)}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
@@ -136,7 +129,6 @@ export class HaComboBox extends LitElement {
|
||||
.label=${this.label}
|
||||
.placeholder=${this.placeholder}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.validationMessage=${this.validationMessage}
|
||||
.errorMessage=${this.errorMessage}
|
||||
class="input"
|
||||
|
@@ -35,20 +35,17 @@ export class HaDateInput extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
render() {
|
||||
return html`<ha-textfield
|
||||
.label=${this.label}
|
||||
.disabled=${this.disabled}
|
||||
iconTrailing
|
||||
iconTrailing="calendar"
|
||||
@click=${this._openDialog}
|
||||
.value=${this.value
|
||||
? formatDateNumeric(new Date(this.value), this.locale)
|
||||
: ""}
|
||||
.required=${this.required}
|
||||
>
|
||||
<ha-svg-icon slot="trailingIcon" .path=${mdiCalendar}></ha-svg-icon>
|
||||
</ha-textfield>`;
|
||||
|
@@ -5,7 +5,6 @@ import "./ha-base-time-input";
|
||||
import type { TimeChangedEvent } from "./ha-base-time-input";
|
||||
|
||||
export interface HaDurationData {
|
||||
days?: number;
|
||||
hours?: number;
|
||||
minutes?: number;
|
||||
seconds?: number;
|
||||
@@ -18,14 +17,10 @@ class HaDurationInput extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public enableMillisecond?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public enableDay?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@query("paper-time-input", true) private _input?: HTMLElement;
|
||||
@@ -40,23 +35,19 @@ class HaDurationInput extends LitElement {
|
||||
return html`
|
||||
<ha-base-time-input
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.required=${this.required}
|
||||
.autoValidate=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
errorMessage="Required"
|
||||
enableSecond
|
||||
.enableMillisecond=${this.enableMillisecond}
|
||||
.enableDay=${this.enableDay}
|
||||
format="24"
|
||||
.days=${this._days}
|
||||
.hours=${this._hours}
|
||||
.minutes=${this._minutes}
|
||||
.seconds=${this._seconds}
|
||||
.milliseconds=${this._milliseconds}
|
||||
@value-changed=${this._durationChanged}
|
||||
noHoursLimit
|
||||
dayLabel="dd"
|
||||
hourLabel="hh"
|
||||
minLabel="mm"
|
||||
secLabel="ss"
|
||||
@@ -65,10 +56,6 @@ class HaDurationInput extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private get _days() {
|
||||
return this.data?.days ? Number(this.data.days) : 0;
|
||||
}
|
||||
|
||||
private get _hours() {
|
||||
return this.data?.hours ? Number(this.data.hours) : 0;
|
||||
}
|
||||
@@ -107,11 +94,6 @@ class HaDurationInput extends LitElement {
|
||||
value.minutes %= 60;
|
||||
}
|
||||
|
||||
if (this.enableDay && value.hours > 24) {
|
||||
value.days = (value.days ?? 0) + Math.floor(value.hours / 24);
|
||||
value.hours %= 24;
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value,
|
||||
});
|
||||
|
@@ -1,13 +1,6 @@
|
||||
import { mdiChevronDown } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { nextRender } from "../common/util/render-status";
|
||||
@@ -23,21 +16,11 @@ class HaExpansionPanel extends LitElement {
|
||||
|
||||
@property() secondary?: string;
|
||||
|
||||
@state() _showContent = this.expanded;
|
||||
|
||||
@query(".container") private _container!: HTMLDivElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div
|
||||
id="summary"
|
||||
@click=${this._toggleContainer}
|
||||
@keydown=${this._toggleContainer}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-expanded=${this.expanded}
|
||||
aria-controls="sect1"
|
||||
>
|
||||
<div class="summary" @click=${this._toggleContainer}>
|
||||
<slot class="header" name="header">
|
||||
${this.header}
|
||||
<slot class="secondary" name="secondary">${this.secondary}</slot>
|
||||
@@ -50,37 +33,21 @@ class HaExpansionPanel extends LitElement {
|
||||
<div
|
||||
class="container ${classMap({ expanded: this.expanded })}"
|
||||
@transitionend=${this._handleTransitionEnd}
|
||||
role="region"
|
||||
aria-labelledby="summary"
|
||||
aria-hidden=${!this.expanded}
|
||||
tabindex="-1"
|
||||
>
|
||||
${this._showContent ? html`<slot></slot>` : ""}
|
||||
<slot></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues) {
|
||||
if (changedProps.has("expanded") && this.expanded) {
|
||||
this._showContent = this.expanded;
|
||||
}
|
||||
}
|
||||
|
||||
private _handleTransitionEnd() {
|
||||
this._container.style.removeProperty("height");
|
||||
this._showContent = this.expanded;
|
||||
}
|
||||
|
||||
private async _toggleContainer(ev): Promise<void> {
|
||||
if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
private async _toggleContainer(): Promise<void> {
|
||||
const newExpanded = !this.expanded;
|
||||
fireEvent(this, "expanded-will-change", { expanded: newExpanded });
|
||||
|
||||
if (newExpanded) {
|
||||
this._showContent = true;
|
||||
// allow for dynamic content to be rendered
|
||||
await nextRender();
|
||||
}
|
||||
@@ -113,21 +80,17 @@ class HaExpansionPanel extends LitElement {
|
||||
var(--divider-color, #e0e0e0)
|
||||
);
|
||||
border-radius: var(--ha-card-border-radius, 4px);
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
#summary {
|
||||
.summary {
|
||||
display: flex;
|
||||
padding: var(--expansion-panel-summary-padding, 0 8px);
|
||||
padding: var(--expansion-panel-summary-padding, 0);
|
||||
min-height: 48px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
font-weight: 500;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#summary:focus {
|
||||
background: var(--input-fill-color);
|
||||
}
|
||||
|
||||
.summary-icon {
|
||||
@@ -140,7 +103,6 @@ class HaExpansionPanel extends LitElement {
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: var(--expansion-panel-content-padding, 0 8px);
|
||||
overflow: hidden;
|
||||
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
height: 0px;
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { styles } from "@material/mwc-textfield/mwc-textfield.css";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import "@polymer/iron-input/iron-input";
|
||||
import "@polymer/paper-input/paper-input-container";
|
||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@@ -20,7 +21,7 @@ export class HaFileUpload extends LitElement {
|
||||
|
||||
@property() public accept!: string;
|
||||
|
||||
@property() public icon?: string;
|
||||
@property() public icon!: string;
|
||||
|
||||
@property() public label!: string;
|
||||
|
||||
@@ -38,7 +39,15 @@ export class HaFileUpload extends LitElement {
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
if (this.autoOpenFileDialog) {
|
||||
this._openFilePicker();
|
||||
this._input?.click();
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (changedProperties.has("_drag") && !this.uploading) {
|
||||
(
|
||||
this.shadowRoot!.querySelector("paper-input-container") as any
|
||||
)._setFocused(this._drag);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,75 +60,51 @@ export class HaFileUpload extends LitElement {
|
||||
active
|
||||
></ha-circular-progress>`
|
||||
: html`
|
||||
<label
|
||||
for="input"
|
||||
class="mdc-text-field mdc-text-field--filled ${classMap({
|
||||
"mdc-text-field--focused": this._drag,
|
||||
"mdc-text-field--with-leading-icon": Boolean(this.icon),
|
||||
"mdc-text-field--with-trailing-icon": Boolean(this.value),
|
||||
})}"
|
||||
@drop=${this._handleDrop}
|
||||
@dragenter=${this._handleDragStart}
|
||||
@dragover=${this._handleDragStart}
|
||||
@dragleave=${this._handleDragEnd}
|
||||
@dragend=${this._handleDragEnd}
|
||||
>
|
||||
<span class="mdc-text-field__ripple"></span>
|
||||
<span
|
||||
class="mdc-floating-label ${this.value || this._drag
|
||||
? "mdc-floating-label--float-above"
|
||||
: ""}"
|
||||
id="label"
|
||||
>${this.label}</span
|
||||
<label for="input">
|
||||
<paper-input-container
|
||||
.alwaysFloatLabel=${Boolean(this.value)}
|
||||
@drop=${this._handleDrop}
|
||||
@dragenter=${this._handleDragStart}
|
||||
@dragover=${this._handleDragStart}
|
||||
@dragleave=${this._handleDragEnd}
|
||||
@dragend=${this._handleDragEnd}
|
||||
class=${classMap({
|
||||
dragged: this._drag,
|
||||
})}
|
||||
>
|
||||
${this.icon
|
||||
? html`<span
|
||||
class="mdc-text-field__icon mdc-text-field__icon--leading"
|
||||
tabindex="-1"
|
||||
>
|
||||
<ha-icon-button
|
||||
@click=${this._openFilePicker}
|
||||
.path=${this.icon}
|
||||
></ha-icon-button>
|
||||
</span>`
|
||||
: ""}
|
||||
<div class="value">${this.value}</div>
|
||||
<input
|
||||
id="input"
|
||||
type="file"
|
||||
class="mdc-text-field__input file"
|
||||
accept=${this.accept}
|
||||
@change=${this._handleFilePicked}
|
||||
aria-labelledby="label"
|
||||
/>
|
||||
${this.value
|
||||
? html`<span
|
||||
class="mdc-text-field__icon mdc-text-field__icon--trailing"
|
||||
tabindex="1"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="suffix"
|
||||
@click=${this._clearValue}
|
||||
.label=${this.hass?.localize("ui.common.close") ||
|
||||
"close"}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
</span>`
|
||||
: ""}
|
||||
<span
|
||||
class="mdc-line-ripple ${this._drag
|
||||
? "mdc-line-ripple--active"
|
||||
: ""}"
|
||||
></span>
|
||||
<label for="input" slot="label"> ${this.label} </label>
|
||||
<iron-input slot="input">
|
||||
<input
|
||||
id="input"
|
||||
type="file"
|
||||
class="file"
|
||||
accept=${this.accept}
|
||||
@change=${this._handleFilePicked}
|
||||
/>
|
||||
${this.value}
|
||||
</iron-input>
|
||||
${this.value
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="suffix"
|
||||
@click=${this._clearValue}
|
||||
.label=${this.hass?.localize("ui.common.close") ||
|
||||
"close"}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: html`
|
||||
<ha-icon-button
|
||||
slot="suffix"
|
||||
.path=${this.icon}
|
||||
></ha-icon-button>
|
||||
`}
|
||||
</paper-input-container>
|
||||
</label>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
|
||||
private _openFilePicker() {
|
||||
this._input?.click();
|
||||
}
|
||||
|
||||
private _handleDrop(ev: DragEvent) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
@@ -152,66 +137,40 @@ export class HaFileUpload extends LitElement {
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
styles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
.mdc-text-field--filled {
|
||||
height: auto;
|
||||
padding-top: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.mdc-text-field--filled.mdc-text-field--with-trailing-icon {
|
||||
padding-top: 28px;
|
||||
}
|
||||
.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__icon {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.mdc-text-field--filled.mdc-text-field--with-trailing-icon
|
||||
.mdc-text-field__icon {
|
||||
align-self: flex-end;
|
||||
}
|
||||
.mdc-text-field__icon--leading {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.mdc-text-field--filled .mdc-floating-label--float-above {
|
||||
transform: scale(0.75);
|
||||
top: 8px;
|
||||
}
|
||||
.dragged:before {
|
||||
position: var(--layout-fit_-_position);
|
||||
top: var(--layout-fit_-_top);
|
||||
right: var(--layout-fit_-_right);
|
||||
bottom: var(--layout-fit_-_bottom);
|
||||
left: var(--layout-fit_-_left);
|
||||
background: currentColor;
|
||||
content: "";
|
||||
opacity: var(--dark-divider-opacity);
|
||||
pointer-events: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.value {
|
||||
width: 100%;
|
||||
}
|
||||
input.file {
|
||||
display: none;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 125px;
|
||||
}
|
||||
ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
--mdc-icon-size: 20px;
|
||||
}
|
||||
ha-circular-progress {
|
||||
display: block;
|
||||
text-align-last: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
return css`
|
||||
paper-input-container {
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
margin: 0 -8px;
|
||||
}
|
||||
paper-input-container.dragged:before {
|
||||
position: var(--layout-fit_-_position);
|
||||
top: var(--layout-fit_-_top);
|
||||
right: var(--layout-fit_-_right);
|
||||
bottom: var(--layout-fit_-_bottom);
|
||||
left: var(--layout-fit_-_left);
|
||||
background: currentColor;
|
||||
content: "";
|
||||
opacity: var(--dark-divider-opacity);
|
||||
pointer-events: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
input.file {
|
||||
display: none;
|
||||
}
|
||||
img {
|
||||
max-width: 125px;
|
||||
max-height: 125px;
|
||||
}
|
||||
ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
--mdc-icon-size: 20px;
|
||||
}
|
||||
ha-circular-progress {
|
||||
display: block;
|
||||
text-align-last: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import type { Selector } from "../../data/selector";
|
||||
import type { HaFormSchema } from "./types";
|
||||
import { HaFormSchema } from "./types";
|
||||
|
||||
export const computeInitialHaFormData = (
|
||||
schema: HaFormSchema[]
|
||||
@@ -32,55 +31,6 @@ export const computeInitialHaFormData = (
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
};
|
||||
} else if ("selector" in field) {
|
||||
const selector: Selector = field.selector;
|
||||
|
||||
if ("device" in selector) {
|
||||
data[field.name] = selector.device.multiple ? [] : "";
|
||||
} else if ("entity" in selector) {
|
||||
data[field.name] = selector.entity.multiple ? [] : "";
|
||||
} else if ("area" in selector) {
|
||||
data[field.name] = selector.area.multiple ? [] : "";
|
||||
} else if ("boolean" in selector) {
|
||||
data[field.name] = false;
|
||||
} else if (
|
||||
"text" in selector ||
|
||||
"addon" in selector ||
|
||||
"attribute" in selector ||
|
||||
"icon" in selector ||
|
||||
"theme" in selector
|
||||
) {
|
||||
data[field.name] = "";
|
||||
} else if ("number" in selector) {
|
||||
data[field.name] = selector.number.min ?? 0;
|
||||
} else if ("select" in selector) {
|
||||
if (selector.select.options.length) {
|
||||
data[field.name] = selector.select.options[0][0];
|
||||
}
|
||||
} else if ("duration" in selector) {
|
||||
data[field.name] = {
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
};
|
||||
} else if ("time" in selector) {
|
||||
data[field.name] = "00:00:00";
|
||||
} else if ("date" in selector || "datetime" in selector) {
|
||||
const now = new Date().toISOString().slice(0, 10);
|
||||
data[field.name] = `${now} 00:00:00`;
|
||||
} else if ("color_rgb" in selector) {
|
||||
data[field.name] = [0, 0, 0];
|
||||
} else if ("color_temp" in selector) {
|
||||
data[field.name] = selector.color_temp.min_mireds ?? 153;
|
||||
} else if (
|
||||
"action" in selector ||
|
||||
"media" in selector ||
|
||||
"target" in selector
|
||||
) {
|
||||
data[field.name] = {};
|
||||
} else {
|
||||
throw new Error("Selector not supported in initial form data");
|
||||
}
|
||||
}
|
||||
});
|
||||
return data;
|
||||
|
@@ -9,9 +9,7 @@ export class HaFormConstant extends LitElement implements HaFormElement {
|
||||
@property() public label!: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<span class="label">${this.label}</span>${this.schema.value
|
||||
? `: ${this.schema.value}`
|
||||
: ""}`;
|
||||
return html`<span class="label">${this.label}</span>: ${this.schema.value}`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@@ -1,20 +1,16 @@
|
||||
import memoizeOne from "memoize-one";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type {
|
||||
HaFormElement,
|
||||
HaFormSelectData,
|
||||
HaFormSelectSchema,
|
||||
} from "./types";
|
||||
import type { SelectSelector } from "../../data/selector";
|
||||
import "../ha-selector/ha-selector-select";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
import "../ha-radio";
|
||||
import type { HaRadio } from "../ha-radio";
|
||||
import "../ha-select";
|
||||
import type { HaSelect } from "../ha-select";
|
||||
import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./types";
|
||||
|
||||
@customElement("ha-form-select")
|
||||
export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public schema!: HaFormSelectSchema;
|
||||
|
||||
@property() public data!: HaFormSelectData;
|
||||
@@ -23,35 +19,60 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
private _selectSchema = memoizeOne(
|
||||
(options): SelectSelector => ({
|
||||
select: {
|
||||
options: options.map((option) => ({
|
||||
value: option[0],
|
||||
label: option[1],
|
||||
})),
|
||||
},
|
||||
})
|
||||
);
|
||||
@query("ha-select", true) private _input?: HTMLElement;
|
||||
|
||||
public focus() {
|
||||
if (this._input) {
|
||||
this._input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this.schema.required && this.schema.options!.length < 6) {
|
||||
return html`
|
||||
<div>
|
||||
${this.label}
|
||||
${this.schema.options.map(
|
||||
([value, label]) => html`
|
||||
<mwc-formfield .label=${label}>
|
||||
<ha-radio
|
||||
.checked=${value === this.data}
|
||||
.value=${value}
|
||||
.disabled=${this.disabled}
|
||||
@change=${this._valueChanged}
|
||||
></ha-radio>
|
||||
</mwc-formfield>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-selector-select
|
||||
.hass=${this.hass}
|
||||
.schema=${this.schema}
|
||||
.value=${this.data}
|
||||
<ha-select
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
.label=${this.label}
|
||||
.value=${this.data}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.schema.required}
|
||||
.selector=${this._selectSchema(this.schema.options)}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-selector-select>
|
||||
@closed=${stopPropagation}
|
||||
@selected=${this._valueChanged}
|
||||
>
|
||||
${!this.schema.required
|
||||
? html`<mwc-list-item value=""></mwc-list-item>`
|
||||
: ""}
|
||||
${this.schema.options!.map(
|
||||
([value, label]) => html`
|
||||
<mwc-list-item .value=${value}>${label}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
let value: string | undefined = ev.detail.value;
|
||||
let value: string | undefined = (ev.target as HaSelect | HaRadio).value;
|
||||
|
||||
if (value === this.data) {
|
||||
return;
|
||||
@@ -65,6 +86,15 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-select,
|
||||
mwc-formfield {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -28,8 +28,6 @@ export class HaFormString extends LitElement implements HaFormElement {
|
||||
|
||||
@property() public label!: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@state() private _unmaskedPassword = false;
|
||||
@@ -55,8 +53,6 @@ export class HaFormString extends LitElement implements HaFormElement {
|
||||
: "password"}
|
||||
.label=${this.label}
|
||||
.value=${this.data || ""}
|
||||
.helper=${this.helper}
|
||||
helperPersistent
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.schema.required}
|
||||
.autoValidate=${this.schema.required}
|
||||
|
@@ -25,8 +25,6 @@ import { HomeAssistant } from "../../types";
|
||||
const getValue = (obj, item) =>
|
||||
obj ? (!item.name ? obj : obj[item.name]) : null;
|
||||
|
||||
const getError = (obj, item) => (obj && item.name ? obj[item.name] : null);
|
||||
|
||||
let selectorImported = false;
|
||||
|
||||
@customElement("ha-form")
|
||||
@@ -86,7 +84,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
`
|
||||
: ""}
|
||||
${this.schema.map((item) => {
|
||||
const error = getError(this.error, item);
|
||||
const error = getValue(this.error, item);
|
||||
|
||||
return html`
|
||||
${error
|
||||
@@ -106,7 +104,6 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
.disabled=${this.disabled}
|
||||
.helper=${this._computeHelper(item)}
|
||||
.required=${item.required || false}
|
||||
.context=${this._generateContext(item)}
|
||||
></ha-selector>`
|
||||
: dynamicElement(`ha-form-${item.type}`, {
|
||||
schema: item,
|
||||
@@ -116,7 +113,6 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
hass: this.hass,
|
||||
computeLabel: this.computeLabel,
|
||||
computeHelper: this.computeHelper,
|
||||
context: this._generateContext(item),
|
||||
})}
|
||||
`;
|
||||
})}
|
||||
@@ -124,20 +120,6 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _generateContext(
|
||||
schema: HaFormSchema
|
||||
): Record<string, any> | undefined {
|
||||
if (!schema.context) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const context = {};
|
||||
for (const [context_key, data_key] of Object.entries(schema.context)) {
|
||||
context[context_key] = this.data[data_key];
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
protected createRenderRoot() {
|
||||
const root = super.createRenderRoot();
|
||||
// attach it as soon as possible to make sure we fetch all events.
|
||||
|
@@ -24,7 +24,6 @@ export interface HaFormBaseSchema {
|
||||
// This value will be set initially when form is loaded
|
||||
suggested_value?: HaFormData;
|
||||
};
|
||||
context?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface HaFormGridSchema extends HaFormBaseSchema {
|
||||
@@ -41,7 +40,7 @@ export interface HaFormSelector extends HaFormBaseSchema {
|
||||
|
||||
export interface HaFormConstantSchema extends HaFormBaseSchema {
|
||||
type: "constant";
|
||||
value?: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface HaFormIntegerSchema extends HaFormBaseSchema {
|
||||
|
@@ -39,8 +39,6 @@ export class HaIconPicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property({ type: Boolean }) public invalid = false;
|
||||
|
||||
@state() private _opened = false;
|
||||
@@ -58,7 +56,6 @@ export class HaIconPicker extends LitElement {
|
||||
.filteredItems=${iconItems}
|
||||
.label=${this.label}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.placeholder=${this.placeholder}
|
||||
.errorMessage=${this.errorMessage}
|
||||
.invalid=${this.invalid}
|
||||
|
@@ -33,7 +33,7 @@ class HaLabeledSlider extends PolymerElement {
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="title">[[_getTitle()]]</div>
|
||||
<div class="title">[[caption]]</div>
|
||||
<div class="extra-container"><slot name="extra"></slot></div>
|
||||
<div class="slider-container">
|
||||
<ha-icon icon="[[icon]]" hidden$="[[!icon]]"></ha-icon>
|
||||
@@ -49,15 +49,10 @@ class HaLabeledSlider extends PolymerElement {
|
||||
`;
|
||||
}
|
||||
|
||||
_getTitle() {
|
||||
return `${this.caption}${this.required ? "*" : ""}`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
caption: String,
|
||||
disabled: Boolean,
|
||||
required: Boolean,
|
||||
min: Number,
|
||||
max: Number,
|
||||
pin: Boolean,
|
||||
|
@@ -2,11 +2,6 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "./ha-markdown-element";
|
||||
|
||||
// Import components that are allwoed to be defined.
|
||||
import "./ha-alert";
|
||||
import "./ha-icon";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-markdown")
|
||||
export class HaMarkdown extends LitElement {
|
||||
@property() public content?;
|
||||
|
@@ -44,9 +44,6 @@ export class HaSelect extends SelectBase {
|
||||
.mdc-select:not(.mdc-select--disabled) .mdc-select__icon {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.mdc-select__anchor {
|
||||
width: var(--ha-select-min-width, 200px);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -14,17 +14,11 @@ export class HaAddonSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
protected render() {
|
||||
return html`<ha-addon-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
allow-custom-entity
|
||||
></ha-addon-picker>`;
|
||||
}
|
||||
|
@@ -6,7 +6,6 @@ import { EntityRegistryEntry } from "../../data/entity_registry";
|
||||
import { AreaSelector } from "../../data/selector";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-area-picker";
|
||||
import "../ha-areas-picker";
|
||||
|
||||
@customElement("ha-selector-area")
|
||||
export class HaAreaSelector extends LitElement {
|
||||
@@ -22,8 +21,6 @@ export class HaAreaSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
protected updated(changedProperties) {
|
||||
if (changedProperties.has("selector")) {
|
||||
const oldSelector = changedProperties.get("selector");
|
||||
@@ -31,55 +28,27 @@ export class HaAreaSelector extends LitElement {
|
||||
oldSelector !== this.selector &&
|
||||
this.selector.area.device?.integration
|
||||
) {
|
||||
getConfigEntries(this.hass, {
|
||||
domain: this.selector.area.device.integration,
|
||||
}).then((entries) => {
|
||||
this._configEntries = entries;
|
||||
});
|
||||
this._loadConfigEntries();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.selector.area.multiple) {
|
||||
return html`
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
no-add
|
||||
.deviceFilter=${this._filterDevices}
|
||||
.entityFilter=${this._filterEntities}
|
||||
.includeDeviceClasses=${this.selector.area.entity?.device_class
|
||||
? [this.selector.area.entity.device_class]
|
||||
: undefined}
|
||||
.includeDomains=${this.selector.area.entity?.domain
|
||||
? [this.selector.area.entity.domain]
|
||||
: undefined}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
></ha-area-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-areas-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.pickAreaLabel=${this.label}
|
||||
no-add
|
||||
.deviceFilter=${this._filterDevices}
|
||||
.entityFilter=${this._filterEntities}
|
||||
.includeDeviceClasses=${this.selector.area.entity?.device_class
|
||||
? [this.selector.area.entity.device_class]
|
||||
: undefined}
|
||||
.includeDomains=${this.selector.area.entity?.domain
|
||||
? [this.selector.area.entity.domain]
|
||||
: undefined}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
></ha-areas-picker>
|
||||
`;
|
||||
return html`<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
no-add
|
||||
.deviceFilter=${this._filterDevices}
|
||||
.entityFilter=${this._filterEntities}
|
||||
.includeDeviceClasses=${this.selector.area.entity?.device_class
|
||||
? [this.selector.area.entity.device_class]
|
||||
: undefined}
|
||||
.includeDomains=${this.selector.area.entity?.domain
|
||||
? [this.selector.area.entity.domain]
|
||||
: undefined}
|
||||
.disabled=${this.disabled}
|
||||
></ha-area-picker>`;
|
||||
}
|
||||
|
||||
private _filterEntities = (entity: EntityRegistryEntry): boolean => {
|
||||
@@ -116,6 +85,12 @@ export class HaAreaSelector extends LitElement {
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
private async _loadConfigEntries() {
|
||||
this._configEntries = (await getConfigEntries(this.hass)).filter(
|
||||
(entry) => entry.domain === this.selector.area.device?.integration
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,10 +1,9 @@
|
||||
import { html, LitElement, PropertyValues } from "lit";
|
||||
import "../entity/ha-entity-attribute-picker";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { AttributeSelector } from "../../data/selector";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../entity/ha-entity-attribute-picker";
|
||||
|
||||
@customElement("ha-selector-attribute")
|
||||
export class HaSelectorAttribute extends SubscribeMixin(LitElement) {
|
||||
@@ -18,67 +17,18 @@ export class HaSelectorAttribute extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
@property() public context?: {
|
||||
filter_entity?: string;
|
||||
};
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-entity-attribute-picker
|
||||
.hass=${this.hass}
|
||||
.entityId=${this.selector.attribute.entity_id ||
|
||||
this.context?.filter_entity}
|
||||
.entityId=${this.selector.attribute.entity_id}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
allow-custom-value
|
||||
></ha-entity-attribute-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
if (
|
||||
// No need to filter value if no value
|
||||
!this.value ||
|
||||
// Only adjust value if we used the context
|
||||
this.selector.attribute.entity_id ||
|
||||
// Only check if context has changed
|
||||
!changedProps.has("context")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldContext = changedProps.get("context") as this["context"];
|
||||
|
||||
if (
|
||||
!this.context ||
|
||||
oldContext?.filter_entity === this.context.filter_entity
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate that that the attribute is still valid for this entity, else unselect.
|
||||
let invalid = false;
|
||||
if (this.context.filter_entity) {
|
||||
const stateObj = this.hass.states[this.context.filter_entity];
|
||||
|
||||
if (!(stateObj && this.value in stateObj.attributes)) {
|
||||
invalid = true;
|
||||
}
|
||||
} else {
|
||||
invalid = this.value !== undefined;
|
||||
}
|
||||
|
||||
if (invalid) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,62 +0,0 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { hex2rgb, rgb2hex } from "../../common/color/convert-color";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { ColorRGBSelector } from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-textfield";
|
||||
|
||||
@customElement("ha-selector-color_rgb")
|
||||
export class HaColorRGBSelector extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public selector!: ColorRGBSelector;
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-textfield
|
||||
type="color"
|
||||
.value=${this.value ? rgb2hex(this.value as any) : ""}
|
||||
.label=${this.label || ""}
|
||||
.required=${this.required}
|
||||
.disalbled=${this.disabled}
|
||||
@change=${this._valueChanged}
|
||||
></ha-textfield>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
const value = (ev.target as any).value;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: hex2rgb(value),
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
ha-textfield {
|
||||
--text-field-padding: 8px;
|
||||
min-width: 75px;
|
||||
flex-grow: 1;
|
||||
margin: 0 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-color_rgb": HaColorRGBSelector;
|
||||
}
|
||||
}
|
@@ -1,62 +0,0 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { ColorTempSelector } from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-labeled-slider";
|
||||
|
||||
@customElement("ha-selector-color_temp")
|
||||
export class HaColorTempSelector extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: ColorTempSelector;
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-labeled-slider
|
||||
pin
|
||||
icon="hass:thermometer"
|
||||
.caption=${this.label}
|
||||
.min=${this.selector.color_temp.min_mireds ?? 153}
|
||||
.max=${this.selector.color_temp.max_mireds ?? 500}
|
||||
.value=${this.value}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
@change=${this._valueChanged}
|
||||
></ha-labeled-slider>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: Number((ev.target as any).value),
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-labeled-slider {
|
||||
--ha-slider-background: -webkit-linear-gradient(
|
||||
right,
|
||||
rgb(255, 160, 0) 0%,
|
||||
white 50%,
|
||||
rgb(166, 209, 255) 100%
|
||||
);
|
||||
/* The color temp minimum value shouldn't be rendered differently. It's not "off". */
|
||||
--paper-slider-knob-start-border-color: var(--primary-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-color_temp": HaColorTempSelector;
|
||||
}
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { DateSelector } from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-date-input";
|
||||
|
||||
@customElement("ha-selector-date")
|
||||
export class HaDateSelector extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: DateSelector;
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-date-input
|
||||
.label=${this.label}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.disabled}
|
||||
.value=${this.value}
|
||||
.required=${this.required}
|
||||
>
|
||||
</ha-date-input>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-date": HaDateSelector;
|
||||
}
|
||||
}
|
@@ -1,78 +0,0 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { DateTimeSelector } from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-date-input";
|
||||
import type { HaDateInput } from "../ha-date-input";
|
||||
import "../ha-time-input";
|
||||
import type { HaTimeInput } from "../ha-time-input";
|
||||
|
||||
@customElement("ha-selector-datetime")
|
||||
export class HaDateTimeSelector extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: DateTimeSelector;
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
@query("ha-date-input") private _dateInput!: HaDateInput;
|
||||
|
||||
@query("ha-time-input") private _timeInput!: HaTimeInput;
|
||||
|
||||
protected render() {
|
||||
const values = this.value?.split(" ");
|
||||
|
||||
return html`
|
||||
<ha-date-input
|
||||
.label=${this.label}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.value=${values?.[0]}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
<ha-time-input
|
||||
enable-second
|
||||
.value=${values?.[1] || "0:00:00"}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-time-input>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: `${this._dateInput.value} ${this._timeInput.value}`,
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
ha-date-input {
|
||||
min-width: 150px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-datetime": HaDateTimeSelector;
|
||||
}
|
||||
}
|
@@ -1,11 +1,10 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import type { DeviceSelector } from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import { DeviceSelector } from "../../data/selector";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../device/ha-device-picker";
|
||||
import "../device/ha-devices-picker";
|
||||
|
||||
@customElement("ha-selector-device")
|
||||
export class HaDeviceSelector extends LitElement {
|
||||
@@ -21,56 +20,30 @@ export class HaDeviceSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
protected updated(changedProperties) {
|
||||
if (changedProperties.has("selector")) {
|
||||
const oldSelector = changedProperties.get("selector");
|
||||
if (oldSelector !== this.selector && this.selector.device?.integration) {
|
||||
getConfigEntries(this.hass, {
|
||||
domain: this.selector.device.integration,
|
||||
}).then((entries) => {
|
||||
this._configEntries = entries;
|
||||
});
|
||||
this._loadConfigEntries();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.selector.device.multiple) {
|
||||
return html`
|
||||
<ha-device-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.deviceFilter=${this._filterDevices}
|
||||
.includeDeviceClasses=${this.selector.device.entity?.device_class
|
||||
? [this.selector.device.entity.device_class]
|
||||
: undefined}
|
||||
.includeDomains=${this.selector.device.entity?.domain
|
||||
? [this.selector.device.entity.domain]
|
||||
: undefined}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
allow-custom-entity
|
||||
></ha-device-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.label ? html`<label>${this.label}</label>` : ""}
|
||||
<ha-devices-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.includeDeviceClasses=${this.selector.device.entity?.device_class
|
||||
? [this.selector.device.entity.device_class]
|
||||
: undefined}
|
||||
.includeDomains=${this.selector.device.entity?.domain
|
||||
? [this.selector.device.entity.domain]
|
||||
: undefined}
|
||||
.required=${this.required}
|
||||
></ha-devices-picker>
|
||||
`;
|
||||
return html`<ha-device-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.deviceFilter=${this._filterDevices}
|
||||
.includeDeviceClasses=${this.selector.device.entity?.device_class
|
||||
? [this.selector.device.entity.device_class]
|
||||
: undefined}
|
||||
.includeDomains=${this.selector.device.entity?.domain
|
||||
? [this.selector.device.entity.domain]
|
||||
: undefined}
|
||||
.disabled=${this.disabled}
|
||||
allow-custom-entity
|
||||
></ha-device-picker>`;
|
||||
}
|
||||
|
||||
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
|
||||
@@ -98,6 +71,12 @@ export class HaDeviceSelector extends LitElement {
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
private async _loadConfigEntries() {
|
||||
this._configEntries = (await getConfigEntries(this.hass)).filter(
|
||||
(entry) => entry.domain === this.selector.device.integration
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import "../ha-duration-input";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { DurationSelector } from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-duration-input";
|
||||
import { DurationSelector } from "../../data/selector";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-selector-duration")
|
||||
export class HaTimeDuration extends LitElement {
|
||||
@@ -14,8 +14,6 @@ export class HaTimeDuration extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
@@ -24,11 +22,9 @@ export class HaTimeDuration extends LitElement {
|
||||
return html`
|
||||
<ha-duration-input
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.data=${this.value}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.enableDay=${this.selector.duration.enable_day}
|
||||
></ha-duration-input>
|
||||
`;
|
||||
}
|
||||
|
@@ -1,23 +1,20 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { html, LitElement, PropertyValues } from "lit";
|
||||
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import {
|
||||
EntitySources,
|
||||
fetchEntitySourcesWithCache,
|
||||
} from "../../data/entity_sources";
|
||||
import { subscribeEntityRegistry } from "../../data/entity_registry";
|
||||
import { EntitySelector } from "../../data/selector";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../entity/ha-entities-picker";
|
||||
import "../entity/ha-entity-picker";
|
||||
|
||||
@customElement("ha-selector-entity")
|
||||
export class HaEntitySelector extends LitElement {
|
||||
export class HaEntitySelector extends SubscribeMixin(LitElement) {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: EntitySelector;
|
||||
|
||||
@state() private _entitySources?: EntitySources;
|
||||
@state() private _entityPlaformLookup?: Record<string, string>;
|
||||
|
||||
@property() public value?: any;
|
||||
|
||||
@@ -25,77 +22,60 @@ export class HaEntitySelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
protected render() {
|
||||
if (!this.selector.entity.multiple) {
|
||||
return html`<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.includeEntities=${this.selector.entity.include_entities}
|
||||
.excludeEntities=${this.selector.entity.exclude_entities}
|
||||
.entityFilter=${this._filterEntities}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.label ? html`<label>${this.label}</label>` : ""}
|
||||
<ha-entities-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.entityFilter=${this._filterEntities}
|
||||
.includeEntities=${this.selector.entity.include_entities}
|
||||
.excludeEntities=${this.selector.entity.exclude_entities}
|
||||
.required=${this.required}
|
||||
></ha-entities-picker>
|
||||
`;
|
||||
return html`<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.entityFilter=${this._filterEntities}
|
||||
.disabled=${this.disabled}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
if (
|
||||
changedProps.has("selector") &&
|
||||
this.selector.entity.integration &&
|
||||
!this._entitySources
|
||||
) {
|
||||
fetchEntitySourcesWithCache(this.hass).then((sources) => {
|
||||
this._entitySources = sources;
|
||||
});
|
||||
}
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
const entityLookup = {};
|
||||
for (const confEnt of entities) {
|
||||
if (!confEnt.platform) {
|
||||
continue;
|
||||
}
|
||||
entityLookup[confEnt.entity_id] = confEnt.platform;
|
||||
}
|
||||
this._entityPlaformLookup = entityLookup;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
private _filterEntities = (entity: HassEntity): boolean => {
|
||||
const {
|
||||
domain: filterDomain,
|
||||
device_class: filterDeviceClass,
|
||||
integration: filterIntegration,
|
||||
} = this.selector.entity;
|
||||
|
||||
if (filterDomain) {
|
||||
if (this.selector.entity?.domain) {
|
||||
const filterDomain = this.selector.entity.domain;
|
||||
const filterDomainIsArray = Array.isArray(filterDomain);
|
||||
const entityDomain = computeStateDomain(entity);
|
||||
if (
|
||||
Array.isArray(filterDomain)
|
||||
? !filterDomain.includes(entityDomain)
|
||||
: entityDomain !== filterDomain
|
||||
(filterDomainIsArray && !filterDomain.includes(entityDomain)) ||
|
||||
(!filterDomainIsArray && entityDomain !== filterDomain)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (
|
||||
filterDeviceClass &&
|
||||
entity.attributes.device_class !== filterDeviceClass
|
||||
) {
|
||||
return false;
|
||||
if (this.selector.entity?.device_class) {
|
||||
if (
|
||||
!entity.attributes.device_class ||
|
||||
entity.attributes.device_class !== this.selector.entity.device_class
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (
|
||||
filterIntegration &&
|
||||
this._entitySources?.[entity.entity_id]?.domain !== filterIntegration
|
||||
) {
|
||||
return false;
|
||||
if (this.selector.entity?.integration) {
|
||||
if (
|
||||
!this._entityPlaformLookup ||
|
||||
this._entityPlaformLookup[entity.entity_id] !==
|
||||
this.selector.entity.integration
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import "../ha-icon-picker";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { IconSelector } from "../../data/selector";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-icon-picker";
|
||||
import { IconSelector } from "../../data/selector";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
@customElement("ha-selector-icon")
|
||||
export class HaIconSelector extends LitElement {
|
||||
@@ -17,15 +17,11 @@ export class HaIconSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-icon-picker
|
||||
.label=${this.label}
|
||||
.value=${this.value}
|
||||
.required=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
.fallbackPath=${this.selector.icon.fallbackPath}
|
||||
.placeholder=${this.selector.icon.placeholder}
|
||||
@value-changed=${this._valueChanged}
|
||||
|
@@ -1,82 +0,0 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type {
|
||||
LocationSelector,
|
||||
LocationSelectorValue,
|
||||
} from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { MarkerLocation } from "../map/ha-locations-editor";
|
||||
import "../map/ha-locations-editor";
|
||||
|
||||
@customElement("ha-selector-location")
|
||||
export class HaLocationSelector extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public selector!: LocationSelector;
|
||||
|
||||
@property() public value?: LocationSelectorValue;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-locations-editor
|
||||
class="flex"
|
||||
.hass=${this.hass}
|
||||
.locations=${this._location(this.selector, this.value)}
|
||||
@location-updated=${this._locationChanged}
|
||||
@radius-updated=${this._radiusChanged}
|
||||
></ha-locations-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
private _location = memoizeOne(
|
||||
(
|
||||
selector: LocationSelector,
|
||||
value?: LocationSelectorValue
|
||||
): MarkerLocation[] => {
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const zoneRadiusColor = selector.location.radius
|
||||
? computedStyles.getPropertyValue("--zone-radius-color") ||
|
||||
computedStyles.getPropertyValue("--accent-color")
|
||||
: undefined;
|
||||
return [
|
||||
{
|
||||
id: "location",
|
||||
latitude: value?.latitude || this.hass.config.latitude,
|
||||
longitude: value?.longitude || this.hass.config.longitude,
|
||||
radius: selector.location.radius ? value?.radius || 1000 : undefined,
|
||||
radius_color: zoneRadiusColor,
|
||||
icon:
|
||||
selector.location.icon || selector.location.radius
|
||||
? "mdi:map-marker-radius"
|
||||
: "mdi:map-marker",
|
||||
location_editable: true,
|
||||
radius_editable: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
private _locationChanged(ev: CustomEvent) {
|
||||
const [latitude, longitude] = ev.detail.location;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this.value, latitude, longitude },
|
||||
});
|
||||
}
|
||||
|
||||
private _radiusChanged(ev: CustomEvent) {
|
||||
const radius = ev.detail.radius;
|
||||
fireEvent(this, "value-changed", { value: { ...this.value, radius } });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-location": HaLocationSelector;
|
||||
}
|
||||
}
|
@@ -35,8 +35,6 @@ export class HaMediaSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public required = true;
|
||||
|
||||
@state() private _thumbnailUrl?: string | null;
|
||||
|
||||
willUpdate(changedProps: PropertyValues<this>) {
|
||||
@@ -86,7 +84,6 @@ export class HaMediaSelector extends LitElement {
|
||||
.label=${this.label ||
|
||||
this.hass.localize("ui.components.selectors.media.pick_media_player")}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
include-domains='["media_player"]'
|
||||
allow-custom-entity
|
||||
@value-changed=${this._entityChanged}
|
||||
|
@@ -19,15 +19,13 @@ export class HaNumberSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
return html`${this.selector.number.mode !== "box"
|
||||
? html`${this.label}${this.required ? "*" : ""}<ha-slider
|
||||
? html`${this.label}<ha-slider
|
||||
.min=${this.selector.number.min}
|
||||
.max=${this.selector.number.max}
|
||||
.value=${this._value}
|
||||
@@ -48,10 +46,8 @@ export class HaNumberSelector extends LitElement {
|
||||
class=${classMap({ single: this.selector.number.mode === "box" })}
|
||||
.min=${this.selector.number.min}
|
||||
.max=${this.selector.number.max}
|
||||
.value=${this.value ?? ""}
|
||||
.value=${this.value || ""}
|
||||
.step=${this.selector.number.step ?? 1}
|
||||
helperPersistent
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.suffix=${this.selector.number.unit_of_measurement}
|
||||
|
@@ -16,13 +16,10 @@ export class HaObjectSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
protected render() {
|
||||
return html`<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.readonly=${this.disabled}
|
||||
.required=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
.placeholder=${this.placeholder}
|
||||
.defaultValue=${this.value}
|
||||
@value-changed=${this._handleChange}
|
||||
|
@@ -1,27 +1,19 @@
|
||||
import "@material/mwc-formfield/mwc-formfield";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
import type { SelectOption, SelectSelector } from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-checkbox";
|
||||
import "../ha-chip";
|
||||
import "../ha-chip-set";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-formfield";
|
||||
import "../ha-radio";
|
||||
import { SelectOption, SelectSelector } from "../../data/selector";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-select";
|
||||
|
||||
@customElement("ha-selector-select")
|
||||
export class HaSelectSelector extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public selector!: SelectSelector;
|
||||
@property() public selector!: SelectSelector;
|
||||
|
||||
@property() public value?: string | string[];
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@@ -29,254 +21,43 @@ export class HaSelectSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
@query("ha-combo-box", true) private comboBox!: HaComboBox;
|
||||
|
||||
private _filter = "";
|
||||
|
||||
protected render() {
|
||||
const options = this.selector.select.options.map((option) =>
|
||||
typeof option === "object" ? option : { value: option, label: option }
|
||||
);
|
||||
return html`<ha-select
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
.label=${this.label}
|
||||
.value=${this.value}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
@closed=${stopPropagation}
|
||||
@selected=${this._valueChanged}
|
||||
>
|
||||
${this.selector.select.options.map((item: string | SelectOption) => {
|
||||
const value = typeof item === "object" ? item.value : item;
|
||||
const label = typeof item === "object" ? item.label : item;
|
||||
|
||||
if (!this.selector.select.custom_value && this._mode === "list") {
|
||||
if (!this.selector.select.multiple || this.required) {
|
||||
return html`
|
||||
<div>
|
||||
${this.label}
|
||||
${options.map(
|
||||
(item: SelectOption) => html`
|
||||
<mwc-formfield .label=${item.label}>
|
||||
<ha-radio
|
||||
.checked=${item.value === this.value}
|
||||
.value=${item.value}
|
||||
.disabled=${this.disabled}
|
||||
@change=${this._valueChanged}
|
||||
></ha-radio>
|
||||
</mwc-formfield>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div>
|
||||
${this.label}${options.map(
|
||||
(item: SelectOption) => html`
|
||||
<ha-formfield .label=${item.label}>
|
||||
<ha-checkbox
|
||||
.checked=${this.value?.includes(item.value)}
|
||||
.value=${item.value}
|
||||
.disabled=${this.disabled}
|
||||
@change=${this._checkboxChanged}
|
||||
></ha-checkbox>
|
||||
</ha-formfield>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (this.selector.select.multiple) {
|
||||
const value =
|
||||
!this.value || this.value === "" ? [] : (this.value as string[]);
|
||||
|
||||
return html`
|
||||
<ha-chip-set>
|
||||
${value?.map(
|
||||
(item, idx) =>
|
||||
html`
|
||||
<ha-chip hasTrailingIcon>
|
||||
${options.find((option) => option.value === item)?.label ||
|
||||
item}
|
||||
<ha-svg-icon
|
||||
slot="trailing-icon"
|
||||
.path=${mdiClose}
|
||||
.idx=${idx}
|
||||
@click=${this._removeItem}
|
||||
></ha-svg-icon>
|
||||
</ha-chip>
|
||||
`
|
||||
)}
|
||||
</ha-chip-set>
|
||||
|
||||
<ha-combo-box
|
||||
item-value-path="value"
|
||||
item-label-path="label"
|
||||
.hass=${this.hass}
|
||||
.label=${this.label}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.value=${this._filter}
|
||||
.items=${options.filter((item) => !this.value?.includes(item.value))}
|
||||
@filter-changed=${this._filterChanged}
|
||||
@value-changed=${this._comboBoxValueChanged}
|
||||
></ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
if (this.selector.select.custom_value) {
|
||||
if (
|
||||
this.value !== undefined &&
|
||||
!options.find((option) => option.value === this.value)
|
||||
) {
|
||||
options.unshift({ value: this.value, label: this.value });
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-combo-box
|
||||
item-value-path="value"
|
||||
item-label-path="label"
|
||||
.hass=${this.hass}
|
||||
.label=${this.label}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.items=${options}
|
||||
.value=${this.value}
|
||||
@filter-changed=${this._filterChanged}
|
||||
@value-changed=${this._comboBoxValueChanged}
|
||||
></ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-select
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
.label=${this.label}
|
||||
.value=${this.value}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
@closed=${stopPropagation}
|
||||
@selected=${this._valueChanged}
|
||||
>
|
||||
${options.map(
|
||||
(item: SelectOption) => html`
|
||||
<mwc-list-item .value=${item.value}>${item.label}</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _mode(): "list" | "dropdown" {
|
||||
return (
|
||||
this.selector.select.mode ||
|
||||
(this.selector.select.options.length < 6 ? "list" : "dropdown")
|
||||
);
|
||||
return html`<mwc-list-item .value=${value}>${label}</mwc-list-item>`;
|
||||
})}
|
||||
</ha-select>`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail?.value || ev.target.value;
|
||||
if (this.disabled || !value) {
|
||||
if (this.disabled || !ev.target.value) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: value,
|
||||
value: ev.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
private _checkboxChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newValue: string[];
|
||||
const value: string = ev.target.value;
|
||||
const checked = ev.target.checked;
|
||||
|
||||
if (checked) {
|
||||
if (!this.value) {
|
||||
newValue = [value];
|
||||
} else if (this.value.includes(value)) {
|
||||
return;
|
||||
} else {
|
||||
newValue = [...this.value, value];
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-select {
|
||||
width: 100%;
|
||||
}
|
||||
} else {
|
||||
if (!this.value?.includes(value)) {
|
||||
return;
|
||||
}
|
||||
newValue = (this.value as string[]).filter((v) => v !== value);
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
`;
|
||||
}
|
||||
|
||||
private async _removeItem(ev) {
|
||||
const value: string[] = [...(this.value! as string[])];
|
||||
value.splice(ev.target.idx, 1);
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value,
|
||||
});
|
||||
await this.updateComplete;
|
||||
this._filterChanged();
|
||||
}
|
||||
|
||||
private _comboBoxValueChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
|
||||
if (this.disabled || newValue === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.selector.select.multiple) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue !== undefined && this.value?.includes(newValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this._filterChanged();
|
||||
this.comboBox.setInputValue("");
|
||||
}, 0);
|
||||
|
||||
const currentValue =
|
||||
!this.value || this.value === "" ? [] : (this.value as string[]);
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: [...currentValue, newValue],
|
||||
});
|
||||
}
|
||||
|
||||
private _filterChanged(ev?: CustomEvent): void {
|
||||
this._filter = ev?.detail.value || "";
|
||||
|
||||
const filteredItems = this.comboBox.items?.filter((item) => {
|
||||
if (this.selector.select.multiple && this.value?.includes(item.value)) {
|
||||
return false;
|
||||
}
|
||||
const label = item.label || item.value;
|
||||
return label.toLowerCase().includes(this._filter?.toLowerCase());
|
||||
});
|
||||
|
||||
if (this._filter && this.selector.select.custom_value) {
|
||||
filteredItems?.unshift({ label: this._filter, value: this._filter });
|
||||
}
|
||||
|
||||
this.comboBox.filteredItems = filteredItems;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-select,
|
||||
mwc-formfield,
|
||||
ha-formfield {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -134,8 +134,9 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
|
||||
private async _loadConfigEntries() {
|
||||
this._configEntries = (await getConfigEntries(this.hass)).filter(
|
||||
(entry) =>
|
||||
entry.domain === this.selector.target.device?.integration ||
|
||||
entry.domain === this.selector.target.entity?.integration
|
||||
entry.domain ===
|
||||
(this.selector.target.device?.integration ||
|
||||
this.selector.target.entity?.integration)
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -18,8 +18,6 @@ export class HaTextSelector extends LitElement {
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property() public selector!: StringSelector;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
@@ -34,8 +32,6 @@ export class HaTextSelector extends LitElement {
|
||||
.label=${this.label}
|
||||
.placeholder=${this.placeholder}
|
||||
.value=${this.value || ""}
|
||||
.helper=${this.helper}
|
||||
helperPersistent
|
||||
.disabled=${this.disabled}
|
||||
@input=${this._handleChange}
|
||||
autocapitalize="none"
|
||||
@@ -48,8 +44,6 @@ export class HaTextSelector extends LitElement {
|
||||
return html`<ha-textfield
|
||||
.value=${this.value || ""}
|
||||
.placeholder=${this.placeholder || ""}
|
||||
.helper=${this.helper}
|
||||
helperPersistent
|
||||
.disabled=${this.disabled}
|
||||
.type=${this._unmaskedPassword ? "text" : this.selector.text?.type}
|
||||
@input=${this._handleChange}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import "../../panels/lovelace/components/hui-theme-select-editor";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { ThemeSelector } from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-theme-picker";
|
||||
import type { ThemeSelector } from "../../data/selector";
|
||||
|
||||
@customElement("ha-selector-theme")
|
||||
export class HaThemeSelector extends LitElement {
|
||||
@@ -16,17 +16,13 @@ export class HaThemeSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-theme-picker
|
||||
<hui-theme-select-editor
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
></ha-theme-picker>
|
||||
></hui-theme-select-editor>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -16,15 +16,12 @@ export class HaTimeSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-time-input
|
||||
.value=${this.value}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.label=${this.label}
|
||||
enable-second
|
||||
></ha-time-input>
|
||||
|
@@ -8,9 +8,6 @@ import "./ha-selector-addon";
|
||||
import "./ha-selector-area";
|
||||
import "./ha-selector-attribute";
|
||||
import "./ha-selector-boolean";
|
||||
import "./ha-selector-color-rgb";
|
||||
import "./ha-selector-date";
|
||||
import "./ha-selector-datetime";
|
||||
import "./ha-selector-device";
|
||||
import "./ha-selector-duration";
|
||||
import "./ha-selector-entity";
|
||||
@@ -23,8 +20,6 @@ import "./ha-selector-time";
|
||||
import "./ha-selector-icon";
|
||||
import "./ha-selector-media";
|
||||
import "./ha-selector-theme";
|
||||
import "./ha-selector-location";
|
||||
import "./ha-selector-color-temp";
|
||||
|
||||
@customElement("ha-selector")
|
||||
export class HaSelector extends LitElement {
|
||||
@@ -44,8 +39,6 @@ export class HaSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
@property() public context?: Record<string, any>;
|
||||
|
||||
public focus() {
|
||||
this.shadowRoot?.getElementById("selector")?.focus();
|
||||
}
|
||||
@@ -65,7 +58,6 @@ export class HaSelector extends LitElement {
|
||||
disabled: this.disabled,
|
||||
required: this.required,
|
||||
helper: this.helper,
|
||||
context: this.context,
|
||||
id: "selector",
|
||||
})}
|
||||
`;
|
||||
|
@@ -471,7 +471,6 @@ export class HaServiceControl extends LitElement {
|
||||
}
|
||||
ha-settings-row {
|
||||
--paper-time-input-justify-content: flex-end;
|
||||
--settings-row-content-width: 100%;
|
||||
border-top: var(
|
||||
--service-control-items-border-top,
|
||||
1px solid var(--divider-color)
|
||||
@@ -490,6 +489,9 @@ export class HaServiceControl extends LitElement {
|
||||
margin: var(--service-control-padding, 0 16px);
|
||||
padding: 16px 0;
|
||||
}
|
||||
:host(:not([narrow])) ha-settings-row ha-selector {
|
||||
width: 60%;
|
||||
}
|
||||
.checkbox-spacer {
|
||||
width: 32px;
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ export class HaSettingsRow extends LitElement {
|
||||
<div secondary><slot name="description"></slot></div>
|
||||
</paper-item-body>
|
||||
</div>
|
||||
<div class="content"><slot></slot></div>
|
||||
<slot></slot>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -43,18 +43,6 @@ export class HaSettingsRow extends LitElement {
|
||||
);
|
||||
flex: 1;
|
||||
}
|
||||
.content {
|
||||
display: contents;
|
||||
}
|
||||
:host(:not([narrow])) .content {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex: 1;
|
||||
padding: 16px 0;
|
||||
}
|
||||
.content ::slotted(*) {
|
||||
width: var(--settings-row-content-width);
|
||||
}
|
||||
:host([narrow]) {
|
||||
align-items: normal;
|
||||
flex-direction: column;
|
||||
|
@@ -37,7 +37,6 @@ import { LocalStorage } from "../common/decorators/local-storage";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import { ActionHandlerDetail } from "../data/lovelace";
|
||||
@@ -45,7 +44,6 @@ import {
|
||||
PersistentNotification,
|
||||
subscribeNotifications,
|
||||
} from "../data/persistent_notification";
|
||||
import { updateCanInstall, UpdateEntity } from "../data/update";
|
||||
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant, PanelInfo, Route } from "../types";
|
||||
@@ -70,6 +68,7 @@ const SORT_VALUE_URL_PATHS = {
|
||||
|
||||
const PANEL_ICONS = {
|
||||
calendar: mdiCalendar,
|
||||
config: mdiCog,
|
||||
"developer-tools": mdiHammer,
|
||||
energy: mdiLightningBolt,
|
||||
history: mdiChartBox,
|
||||
@@ -191,8 +190,6 @@ class HaSidebar extends LitElement {
|
||||
|
||||
@state() private _notifications?: PersistentNotification[];
|
||||
|
||||
@state() private _updatesCount = 0;
|
||||
|
||||
@state() private _renderEmptySortable = false;
|
||||
|
||||
private _mouseLeaveTimeout?: number;
|
||||
@@ -238,7 +235,6 @@ class HaSidebar extends LitElement {
|
||||
changedProps.has("narrow") ||
|
||||
changedProps.has("alwaysExpand") ||
|
||||
changedProps.has("_externalConfig") ||
|
||||
changedProps.has("_updatesCount") ||
|
||||
changedProps.has("_notifications") ||
|
||||
changedProps.has("editMode") ||
|
||||
changedProps.has("_renderEmptySortable") ||
|
||||
@@ -294,12 +290,6 @@ class HaSidebar extends LitElement {
|
||||
toggleAttribute(this, "rtl", computeRTL(this.hass));
|
||||
}
|
||||
|
||||
this._updatesCount = Object.values(this.hass.states).filter(
|
||||
(entity) =>
|
||||
computeStateDomain(entity) === "update" &&
|
||||
updateCanInstall(entity as UpdateEntity)
|
||||
).length;
|
||||
|
||||
if (!SUPPORT_SCROLL_IF_NEEDED) {
|
||||
return;
|
||||
}
|
||||
@@ -397,37 +387,35 @@ class HaSidebar extends LitElement {
|
||||
icon?: string | null,
|
||||
iconPath?: string | null
|
||||
) {
|
||||
return urlPath === "config"
|
||||
? this._renderConfiguration(title)
|
||||
: html`
|
||||
<a
|
||||
role="option"
|
||||
href=${`/${urlPath}`}
|
||||
data-panel=${urlPath}
|
||||
tabindex="-1"
|
||||
@mouseenter=${this._itemMouseEnter}
|
||||
@mouseleave=${this._itemMouseLeave}
|
||||
>
|
||||
<paper-icon-item>
|
||||
${iconPath
|
||||
? html`<ha-svg-icon
|
||||
slot="item-icon"
|
||||
.path=${iconPath}
|
||||
></ha-svg-icon>`
|
||||
: html`<ha-icon slot="item-icon" .icon=${icon}></ha-icon>`}
|
||||
<span class="item-text">${title}</span>
|
||||
</paper-icon-item>
|
||||
${this.editMode
|
||||
? html`<ha-icon-button
|
||||
.label=${this.hass.localize("ui.sidebar.hide_panel")}
|
||||
.path=${mdiClose}
|
||||
class="hide-panel"
|
||||
.panel=${urlPath}
|
||||
@click=${this._hidePanel}
|
||||
></ha-icon-button>`
|
||||
: ""}
|
||||
</a>
|
||||
`;
|
||||
return html`
|
||||
<a
|
||||
role="option"
|
||||
href=${`/${urlPath}`}
|
||||
data-panel=${urlPath}
|
||||
tabindex="-1"
|
||||
@mouseenter=${this._itemMouseEnter}
|
||||
@mouseleave=${this._itemMouseLeave}
|
||||
>
|
||||
<paper-icon-item>
|
||||
${iconPath
|
||||
? html`<ha-svg-icon
|
||||
slot="item-icon"
|
||||
.path=${iconPath}
|
||||
></ha-svg-icon>`
|
||||
: html`<ha-icon slot="item-icon" .icon=${icon}></ha-icon>`}
|
||||
<span class="item-text">${title}</span>
|
||||
</paper-icon-item>
|
||||
${this.editMode
|
||||
? html`<ha-icon-button
|
||||
.label=${this.hass.localize("ui.sidebar.hide_panel")}
|
||||
.path=${mdiClose}
|
||||
class="hide-panel"
|
||||
.panel=${urlPath}
|
||||
@click=${this._hidePanel}
|
||||
></ha-icon-button>`
|
||||
: ""}
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderPanelsEdit(beforeSpacer: PanelInfo[]) {
|
||||
@@ -489,35 +477,6 @@ class HaSidebar extends LitElement {
|
||||
return html`<div class="spacer" disabled></div>`;
|
||||
}
|
||||
|
||||
private _renderConfiguration(title: string | null) {
|
||||
return html` <a
|
||||
class="configuration-container"
|
||||
role="option"
|
||||
href="/config"
|
||||
data-panel="config"
|
||||
tabindex="-1"
|
||||
@mouseenter=${this._itemMouseEnter}
|
||||
@mouseleave=${this._itemMouseLeave}
|
||||
>
|
||||
<paper-icon-item class="configuration" role="option">
|
||||
<ha-svg-icon slot="item-icon" .path=${mdiCog}></ha-svg-icon>
|
||||
${!this.alwaysExpand && this._updatesCount > 0
|
||||
? html`
|
||||
<span class="configuration-badge" slot="item-icon">
|
||||
${this._updatesCount}
|
||||
</span>
|
||||
`
|
||||
: ""}
|
||||
<span class="item-text">${title}</span>
|
||||
${this.alwaysExpand && this._updatesCount > 0
|
||||
? html`
|
||||
<span class="configuration-badge">${this._updatesCount}</span>
|
||||
`
|
||||
: ""}
|
||||
</paper-icon-item>
|
||||
</a>`;
|
||||
}
|
||||
|
||||
private _renderNotifications() {
|
||||
let notificationCount = this._notifications
|
||||
? this._notifications.length
|
||||
@@ -994,21 +953,18 @@ class HaSidebar extends LitElement {
|
||||
height: 1px;
|
||||
background-color: var(--divider-color);
|
||||
}
|
||||
.notifications-container,
|
||||
.configuration-container {
|
||||
.notifications-container {
|
||||
display: flex;
|
||||
margin-left: env(safe-area-inset-left);
|
||||
}
|
||||
:host([rtl]) .notifications-container,
|
||||
:host([rtl]) .configuration-container {
|
||||
:host([rtl]) .notifications-container {
|
||||
margin-left: initial;
|
||||
margin-right: env(safe-area-inset-right);
|
||||
}
|
||||
.notifications {
|
||||
cursor: pointer;
|
||||
}
|
||||
.notifications .item-text,
|
||||
.configuration .item-text {
|
||||
.notifications .item-text {
|
||||
flex: 1;
|
||||
}
|
||||
.profile {
|
||||
@@ -1032,8 +988,7 @@ class HaSidebar extends LitElement {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.notification-badge,
|
||||
.configuration-badge {
|
||||
.notification-badge {
|
||||
min-width: 20px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
@@ -1044,11 +999,7 @@ class HaSidebar extends LitElement {
|
||||
padding: 0px 6px;
|
||||
color: var(--text-accent-color, var(--text-primary-color));
|
||||
}
|
||||
.configuration-badge {
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
ha-svg-icon + .notification-badge,
|
||||
ha-svg-icon + .configuration-badge {
|
||||
ha-svg-icon + .notification-badge {
|
||||
position: absolute;
|
||||
bottom: 14px;
|
||||
left: 26px;
|
||||
|
@@ -19,11 +19,13 @@ export class HaTextArea extends TextAreaBase {
|
||||
textfieldStyles,
|
||||
textareaStyles,
|
||||
css`
|
||||
:host([autogrow]) {
|
||||
max-height: 200px;
|
||||
}
|
||||
:host([autogrow]) .mdc-text-field {
|
||||
position: relative;
|
||||
min-height: 74px;
|
||||
min-width: 178px;
|
||||
max-height: 200px;
|
||||
}
|
||||
:host([autogrow]) .mdc-text-field:after {
|
||||
content: attr(data-value);
|
||||
|
@@ -9,12 +9,6 @@ export class HaTextField extends TextFieldBase {
|
||||
|
||||
@property({ attribute: "error-message" }) public errorMessage?: string;
|
||||
|
||||
// @ts-ignore
|
||||
@property({ type: Boolean }) public icon?: boolean;
|
||||
|
||||
// @ts-ignore
|
||||
@property({ type: Boolean }) public iconTrailing?: boolean;
|
||||
|
||||
override updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (
|
||||
@@ -59,11 +53,6 @@ export class HaTextField extends TextFieldBase {
|
||||
padding-right: var(--text-field-suffix-padding-right, 0px);
|
||||
}
|
||||
|
||||
.mdc-text-field:not(.mdc-text-field--disabled)
|
||||
.mdc-text-field__affix--suffix {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.mdc-text-field__icon {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
@@ -2,8 +2,8 @@ import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { useAmPm } from "../common/datetime/use_am_pm";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { FrontendLocaleData } from "../data/translation";
|
||||
import "./ha-base-time-input";
|
||||
import { FrontendLocaleData } from "../data/translation";
|
||||
import type { TimeChangedEvent } from "./ha-base-time-input";
|
||||
|
||||
@customElement("ha-time-input")
|
||||
@@ -16,8 +16,6 @@ export class HaTimeInput extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "enable-second" })
|
||||
public enableSecond = false;
|
||||
|
||||
@@ -45,7 +43,6 @@ export class HaTimeInput extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._timeChanged}
|
||||
.enableSecond=${this.enableSecond}
|
||||
.required=${this.required}
|
||||
></ha-base-time-input>
|
||||
`;
|
||||
}
|
||||
|
@@ -31,10 +31,6 @@ export class HaYamlEditor extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean }) public readOnly = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@state() private _yaml = "";
|
||||
|
||||
public setValue(value): void {
|
||||
@@ -61,11 +57,10 @@ export class HaYamlEditor extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
${this.label ? html`<p>${this.label}${this.required ? "*" : ""}</p>` : ""}
|
||||
${this.label ? html`<p>${this.label}</p>` : ""}
|
||||
<ha-code-editor
|
||||
.hass=${this.hass}
|
||||
.value=${this._yaml}
|
||||
.readOnly=${this.readOnly}
|
||||
mode="yaml"
|
||||
autocomplete-entities
|
||||
.error=${this.isValid === false}
|
||||
|
@@ -488,14 +488,6 @@ export class HaMap extends ReactiveElement {
|
||||
text-align: center;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.leaflet-pane {
|
||||
z-index: 0 !important;
|
||||
}
|
||||
.leaflet-control,
|
||||
.leaflet-top,
|
||||
.leaflet-bottom {
|
||||
z-index: 1 !important;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user