Compare commits

..

6 Commits

Author SHA1 Message Date
Aidan Timson
ba22a12a20 Fix 2026-03-03 14:46:42 +00:00
Aidan Timson
098b54f749 Fix 2026-03-03 14:46:15 +00:00
Aidan Timson
4c6a7091a6 Filtering 2026-03-03 14:46:15 +00:00
Aidan Timson
322cb35526 More types 2026-03-03 14:46:15 +00:00
Aidan Timson
c34f6bea2b Always show 2026-03-03 14:46:15 +00:00
Aidan Timson
41bf0652b0 Setup log classification 2026-03-03 14:46:15 +00:00
623 changed files with 17042 additions and 29091 deletions

View File

@@ -464,14 +464,7 @@ this.hass.localize("ui.panel.config.updates.update_available", {
### Pull Requests
When creating a pull request, you **must** use the PR template located at `.github/PULL_REQUEST_TEMPLATE.md`. Read the template file and use its full content as the PR body, filling in each section appropriately.
- Do not omit, reorder, or rewrite the template sections
- Check the appropriate "Type of change" box based on the changes
- Do not check the checklist items on behalf of the user — those are the user's responsibility to review and check
- If the PR includes UI changes, remind the user to add screenshots or a short video to the PR after creating it
- Be simple and user friendly — explain what the change does, not implementation details
- Use markdown so the user can copy it
When creating a pull request, you **must** use the PR template located at `.github/PULL_REQUEST_TEMPLATE.md`. Read the template file and use its full content as the PR body, filling in each section appropriately. Do not omit, reorder, or rewrite the template sections. Do not check the checklist items on behalf of the user — those are the user's responsibility to review and check. If the PR includes UI changes, remind the user to add screenshots or a short video to the PR after creating it.
### Text and Copy Guidelines

View File

@@ -26,7 +26,7 @@ jobs:
ref: dev
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -61,7 +61,7 @@ jobs:
ref: master
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -26,7 +26,7 @@ jobs:
- name: Check out files from GitHub
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -37,7 +37,7 @@ jobs:
- name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
- name: Setup lint cache
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: |
node_modules/.cache/prettier
@@ -60,7 +60,7 @@ jobs:
- name: Check out files from GitHub
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -78,7 +78,7 @@ jobs:
- name: Check out files from GitHub
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
uses: github/codeql-action/autobuild@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -57,4 +57,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4

View File

@@ -27,7 +27,7 @@ jobs:
ref: dev
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -62,7 +62,7 @@ jobs:
ref: master
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -24,7 +24,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -28,7 +28,7 @@ jobs:
python-version: ${{ env.PYTHON_VERSION }}
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: ".nvmrc"
cache: yarn

View File

@@ -18,6 +18,6 @@ jobs:
pull-requests: read
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@139054aeaa9adc52ab36ddf67437541f039b88e2 # v7.1.1
- uses: release-drafter/release-drafter@6db134d15f3909ccc9eefd369f02bd1e9cffdf97 # v6.2.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -37,7 +37,7 @@ jobs:
uses: home-assistant/actions/helpers/verify-version@master
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -62,7 +62,7 @@ jobs:
skip-existing: true
- name: Upload release assets
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
files: |
dist/*.whl
@@ -100,7 +100,7 @@ jobs:
- name: Checkout the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: ".nvmrc"
cache: yarn
@@ -115,6 +115,6 @@ jobs:
- name: Tar folder
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
- name: Upload release asset
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz

2
.nvmrc
View File

@@ -1 +1 @@
24.14.1
24.14.0

View File

@@ -31,7 +31,7 @@ index 8795ddcaa77aea7b0356417e4bc4b19e2b3f860c..fcdc68342d9ac53936c9ed40a9ccfc2f
@@ -129,7 +129,10 @@ export async function injectManifest(
searchString: options.injectionPoint!,
});
- filesToWrite[options.swDest] = source;
+ filesToWrite[options.swDest] = source.replace(
+ url!,

942
.yarn/releases/yarn-4.12.0.cjs vendored Executable file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -8,4 +8,4 @@ enableGlobalCache: false
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.13.0.cjs
yarnPath: .yarn/releases/yarn-4.12.0.cjs

View File

@@ -40,24 +40,18 @@ const convertToJSON = async (
throw e;
}
// Convert to JSON
const parts = localeData.split("} else {");
const firstBlock = parts[0];
const obj = INTL_POLYFILLS[pkg];
const dataRegex = new RegExp(
`Intl\\.${obj}\\.${addFunc}\\((?<data>.*)\\)`,
"s"
);
localeData = firstBlock.match(dataRegex)?.groups?.data;
localeData = localeData.match(dataRegex)?.groups?.data;
if (!localeData) {
throw Error(`Failed to extract data for language ${lang} from ${pkg}`);
}
// Parse to validate JSON, then stringify to minify
try {
localeData = JSON.stringify(JSON.parse(localeData));
await writeFile(join(outDir, `${pkg}/${lang}.json`), localeData);
} catch (e) {
throw Error(`Failed to parse JSON for language ${lang} from ${pkg}: ${e}`);
}
localeData = JSON.stringify(JSON.parse(localeData));
await writeFile(join(outDir, `${pkg}/${lang}.json`), localeData);
};
gulp.task("clean-locale-data", async () => deleteSync([outDir]));

View File

@@ -57,12 +57,6 @@ const runDevServer = async ({
directory: contentBase,
watch: true,
},
client: {
overlay: {
runtimeErrors: (error) =>
!error?.message?.includes("ResizeObserver loop"),
},
},
proxy,
},
compiler

View File

@@ -1,7 +1,8 @@
import type { EntityInput } from "../../../../src/fake_data/entities/types";
import type { Entity } from "../../../../src/fake_data/entity";
import { convertEntities } from "../../../../src/fake_data/entity";
export const castDemoEntities: () => EntityInput[] = () =>
Object.values({
export const castDemoEntities: () => Entity[] = () =>
convertEntities({
"light.reading_light": {
entity_id: "light.reading_light",
state: "on",

View File

@@ -1,7 +1,8 @@
import { convertEntities } from "../../../../src/fake_data/entity";
import type { DemoConfig } from "../types";
export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
Object.values({
convertEntities({
"todo.shopping_list": {
entity_id: "todo.shopping_list",
state: "2",

View File

@@ -1,7 +1,8 @@
import { convertEntities } from "../../../../src/fake_data/entity";
import type { DemoConfig } from "../types";
export const demoEntitiesJimpower: DemoConfig["entities"] = () =>
Object.values({
convertEntities({
"todo.shopping_list": {
entity_id: "todo.shopping_list",
state: "2",

View File

@@ -1,7 +1,8 @@
import { convertEntities } from "../../../../src/fake_data/entity";
import type { DemoConfig } from "../types";
export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
Object.values({
convertEntities({
"todo.shopping_list": {
entity_id: "todo.shopping_list",
state: "2",

View File

@@ -1,7 +1,8 @@
import { convertEntities } from "../../../../src/fake_data/entity";
import type { DemoConfig } from "../types";
export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
Object.values({
convertEntities({
"cover.living_room_garden_shutter": {
entity_id: "cover.living_room_garden_shutter",
state: "open",
@@ -141,7 +142,7 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
},
},
"device_tracker.car": {
entity_id: "device_tracker.car",
entity_id: "sensor.outdoor_humidity",
state: "not_home",
attributes: {
friendly_name: "Car",
@@ -199,7 +200,7 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
},
},
"binary_sensor.kitchen_motion": {
entity_id: "binary_sensor.kitchen_motion",
entity_id: "light.kitchen_motion",
state: "on",
attributes: {
device_class: "motion",
@@ -335,7 +336,7 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
},
},
"sensor.rain": {
entity_id: "sensor.rain",
entity_id: "sensor.moon_phase",
state: "7.2",
attributes: {
state_class: "total_increasing",
@@ -565,7 +566,7 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
},
},
"update.home_assistant_core_update": {
entity_id: "update.home_assistant_core_update",
entity_id: "update.home_assistant_supervisor_update",
state: "off",
attributes: {
auto_update: false,

View File

@@ -1,7 +1,8 @@
import { convertEntities } from "../../../../src/fake_data/entity";
import type { DemoConfig } from "../types";
export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
Object.values({
convertEntities({
"todo.shopping_list": {
entity_id: "todo.shopping_list",
state: "2",

View File

@@ -1,7 +1,7 @@
import type { TemplateResult } from "lit";
import type { LocalizeFunc } from "../../../src/common/translations/localize";
import type { LovelaceConfig } from "../../../src/data/lovelace/config/types";
import type { EntityInput } from "../../../src/fake_data/entities/types";
import type { Entity } from "../../../src/fake_data/entity";
export interface DemoConfig {
index?: number;
@@ -12,6 +12,6 @@ export interface DemoConfig {
| string
| ((localize: LocalizeFunc) => string | TemplateResult<1>);
lovelace: (localize: LocalizeFunc) => LovelaceConfig;
entities: (localize: LocalizeFunc) => EntityInput[];
entities: (localize: LocalizeFunc) => Entity[];
theme: () => Record<string, string> | null;
}

View File

@@ -1,5 +1,7 @@
import { convertEntities } from "../../../src/fake_data/entity";
export const mapEntities = () =>
Object.values({
convertEntities({
"zone.home": {
entity_id: "zone.home",
state: "zoning",
@@ -49,7 +51,7 @@ export const mapEntities = () =>
});
export const energyEntities = () =>
Object.values({
convertEntities({
"sensor.grid_fossil_fuel_percentage": {
entity_id: "sensor.grid_fossil_fuel_percentage",
state: "88.6",

View File

@@ -1,253 +1,175 @@
import { getEntity } from "../../../src/fake_data/entity";
export const createMediaPlayerEntities = () => [
{
entity_id: "media_player.music_paused",
state: "paused",
attributes: {
friendly_name: "Pausing The Music",
media_content_type: "music",
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set
supported_features: 64063,
entity_picture: "/images/album_cover_2.jpg",
media_duration: 300,
media_position: 50,
media_position_updated_at: new Date(
// 23 seconds in
new Date().getTime() - 23000
).toISOString(),
volume_level: 0.5,
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
source: "AirPlay",
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
sound_mode: "Music",
},
},
{
entity_id: "media_player.music_playing",
state: "playing",
attributes: {
friendly_name: "Playing The Music",
media_content_type: "music",
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set + Browse Media + Grouping
supported_features: 784959,
entity_picture: "/images/album_cover.jpg",
media_duration: 300,
media_position: 0,
media_position_updated_at: new Date(
// 23 seconds in
new Date().getTime() - 23000
).toISOString(),
volume_level: 0.5,
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
sound_mode: "Music",
group_members: ["media_player.playing", "media_player.stream_playing"],
},
},
{
entity_id: "media_player.stream_playing",
state: "playing",
attributes: {
friendly_name: "Playing the Stream",
media_content_type: "movie",
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
entity_picture: "/images/frenck.jpg",
// Pause + Next Track + Play + Browse Media
supported_features: 147489,
},
},
{
entity_id: "media_player.stream_paused",
state: "paused",
attributes: {
friendly_name: "Paused the Stream",
media_content_type: "movie",
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
entity_picture: "/images/frenck.jpg",
// Pause + Next Track + Play
supported_features: 16417,
},
},
{
entity_id: "media_player.stream_playing_previous",
state: "playing",
attributes: {
friendly_name: 'Playing the Stream (with "previous" support)',
media_content_type: "movie",
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
entity_picture: "/images/frenck.jpg",
// Pause + Previous Track + Play
supported_features: 16401,
},
},
{
entity_id: "media_player.tv_playing",
state: "playing",
attributes: {
friendly_name: "Playing non-skip TV Show",
media_content_type: "tvshow",
media_title: "Chapter 1",
media_series_title: "House of Cards",
app_name: "Netflix",
entity_picture: "/images/netflix.jpg",
// Pause
supported_features: 1,
},
},
{
entity_id: "media_player.sonos_idle",
state: "idle",
attributes: {
friendly_name: "Sonos Idle",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set
supported_features: 64063,
volume_level: 0.33,
is_volume_muted: true,
},
},
{
entity_id: "media_player.idle_browse_media",
state: "idle",
attributes: {
friendly_name: "Idle waiting for Browse Media (e.g. Spotify)",
// Pause + Seek + Volume Set + Previous Track + Next Track + Play Media +
// Select Source + Play + Shuffle Set + Browse Media
supported_features: 182839,
volume_level: 0.79,
},
},
{
entity_id: "media_player.theater_off",
state: "off",
attributes: {
friendly_name: "TV Off",
// On + Off + Play + Next + Pause
supported_features: 16801,
},
},
{
entity_id: "media_player.theater_on",
state: "on",
attributes: {
friendly_name: "TV On",
// On + Off + Play + Next + Pause
supported_features: 16801,
},
},
{
entity_id: "media_player.theater_off_static",
state: "off",
attributes: {
friendly_name: "TV Off (cannot be switched on)",
// Off + Next + Pause
supported_features: 289,
},
},
{
entity_id: "media_player.theater_on_static",
state: "on",
attributes: {
friendly_name: "TV On (cannot be switched off)",
// On + Next + Pause
supported_features: 161,
},
},
{
entity_id: "media_player.android_cast",
state: "playing",
attributes: {
friendly_name: "Casting App (no supported features)",
media_title: "Android Screen Casting",
app_name: "Screen Mirroring",
},
},
{
entity_id: "media_player.image_display",
state: "playing",
attributes: {
friendly_name: "Digital Picture Frame",
media_content_type: "image",
media_title: "Famous Painting",
media_artist: "Famous Artist",
entity_picture: "/images/sunflowers.jpg",
// On + Off + Browse Media
supported_features: 131456,
},
},
{
entity_id: "media_player.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Player Unavailable",
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21437,
},
},
{
entity_id: "media_player.unknown",
state: "unknown",
attributes: {
friendly_name: "Player Unknown",
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21437,
},
},
{
entity_id: "media_player.playing",
state: "playing",
attributes: {
friendly_name: "Player Playing (no Pause support)",
// Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21436,
volume_level: 1,
},
},
{
entity_id: "media_player.idle",
state: "idle",
attributes: {
friendly_name: "Player Idle",
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21437,
volume_level: 0,
},
},
{
entity_id: "media_player.receiver_on",
state: "on",
attributes: {
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
volume_level: 0.63,
is_volume_muted: false,
source: "TV",
sound_mode: "Movie",
friendly_name: "Receiver (selectable sources)",
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
supported_features: 84364,
},
},
{
entity_id: "media_player.receiver_off",
state: "off",
attributes: {
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
friendly_name: "Receiver (selectable sources)",
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
supported_features: 84364,
},
},
getEntity("media_player", "music_paused", "paused", {
friendly_name: "Pausing The Music",
media_content_type: "music",
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set
supported_features: 64063,
entity_picture: "/images/album_cover_2.jpg",
media_duration: 300,
media_position: 50,
media_position_updated_at: new Date(
// 23 seconds in
new Date().getTime() - 23000
).toISOString(),
volume_level: 0.5,
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
source: "AirPlay",
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
sound_mode: "Music",
}),
getEntity("media_player", "music_playing", "playing", {
friendly_name: "Playing The Music",
media_content_type: "music",
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set + Browse Media + Grouping
supported_features: 784959,
entity_picture: "/images/album_cover.jpg",
media_duration: 300,
media_position: 0,
media_position_updated_at: new Date(
// 23 seconds in
new Date().getTime() - 23000
).toISOString(),
volume_level: 0.5,
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
sound_mode: "Music",
group_members: ["media_player.playing", "media_player.stream_playing"],
}),
getEntity("media_player", "stream_playing", "playing", {
friendly_name: "Playing the Stream",
media_content_type: "movie",
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
entity_picture: "/images/frenck.jpg",
// Pause + Next Track + Play + Browse Media
supported_features: 147489,
}),
getEntity("media_player", "stream_paused", "paused", {
friendly_name: "Paused the Stream",
media_content_type: "movie",
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
entity_picture: "/images/frenck.jpg",
// Pause + Next Track + Play
supported_features: 16417,
}),
getEntity("media_player", "stream_playing_previous", "playing", {
friendly_name: 'Playing the Stream (with "previous" support)',
media_content_type: "movie",
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
entity_picture: "/images/frenck.jpg",
// Pause + Previous Track + Play
supported_features: 16401,
}),
getEntity("media_player", "tv_playing", "playing", {
friendly_name: "Playing non-skip TV Show",
media_content_type: "tvshow",
media_title: "Chapter 1",
media_series_title: "House of Cards",
app_name: "Netflix",
entity_picture: "/images/netflix.jpg",
// Pause
supported_features: 1,
}),
getEntity("media_player", "sonos_idle", "idle", {
friendly_name: "Sonos Idle",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set
supported_features: 64063,
volume_level: 0.33,
is_volume_muted: true,
}),
getEntity("media_player", "idle_browse_media", "idle", {
friendly_name: "Idle waiting for Browse Media (e.g. Spotify)",
// Pause + Seek + Volume Set + Previous Track + Next Track + Play Media +
// Select Source + Play + Shuffle Set + Browse Media
supported_features: 182839,
volume_level: 0.79,
}),
getEntity("media_player", "theater_off", "off", {
friendly_name: "TV Off",
// On + Off + Play + Next + Pause
supported_features: 16801,
}),
getEntity("media_player", "theater_on", "on", {
friendly_name: "TV On",
// On + Off + Play + Next + Pause
supported_features: 16801,
}),
getEntity("media_player", "theater_off_static", "off", {
friendly_name: "TV Off (cannot be switched on)",
// Off + Next + Pause
supported_features: 289,
}),
getEntity("media_player", "theater_on_static", "on", {
friendly_name: "TV On (cannot be switched off)",
// On + Next + Pause
supported_features: 161,
}),
getEntity("media_player", "android_cast", "playing", {
friendly_name: "Casting App (no supported features)",
media_title: "Android Screen Casting",
app_name: "Screen Mirroring",
}),
getEntity("media_player", "image_display", "playing", {
friendly_name: "Digital Picture Frame",
media_content_type: "image",
media_title: "Famous Painting",
media_artist: "Famous Artist",
entity_picture: "/images/sunflowers.jpg",
// On + Off + Browse Media
supported_features: 131456,
}),
getEntity("media_player", "unavailable", "unavailable", {
friendly_name: "Player Unavailable",
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21437,
}),
getEntity("media_player", "unknown", "unknown", {
friendly_name: "Player Unknown",
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21437,
}),
getEntity("media_player", "playing", "playing", {
friendly_name: "Player Playing (no Pause support)",
// Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21436,
volume_level: 1,
}),
getEntity("media_player", "idle", "idle", {
friendly_name: "Player Idle",
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21437,
volume_level: 0,
}),
getEntity("media_player", "receiver_on", "on", {
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
volume_level: 0.63,
is_volume_muted: false,
source: "TV",
sound_mode: "Movie",
friendly_name: "Receiver (selectable sources)",
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
supported_features: 84364,
}),
getEntity("media_player", "receiver_off", "off", {
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
friendly_name: "Receiver (selectable sources)",
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
supported_features: 84364,
}),
];

View File

@@ -1,82 +1,72 @@
import { getEntity } from "../../../src/fake_data/entity";
export const createPlantEntities = () => [
{
entity_id: "plant.lemon_tree",
state: "ok",
attributes: {
problem: "none",
sensors: {
moisture: "sensor.lemon_tree_moisture",
battery: "sensor.lemon_tree_battery",
temperature: "sensor.lemon_tree_temperature",
conductivity: "sensor.lemon_tree_conductivity",
brightness: "sensor.lemon_tree_brightness",
},
unit_of_measurement_dict: {
temperature: "°C",
moisture: "%",
brightness: "lx",
battery: "%",
conductivity: "μS/cm",
},
moisture: 54,
battery: 95,
temperature: 15.6,
conductivity: 1,
brightness: 12,
max_brightness: 20,
friendly_name: "Lemon Tree",
getEntity("plant", "lemon_tree", "ok", {
problem: "none",
sensors: {
moisture: "sensor.lemon_tree_moisture",
battery: "sensor.lemon_tree_battery",
temperature: "sensor.lemon_tree_temperature",
conductivity: "sensor.lemon_tree_conductivity",
brightness: "sensor.lemon_tree_brightness",
},
},
{
entity_id: "plant.apple_tree",
state: "ok",
attributes: {
problem: "brightness",
sensors: {
moisture: "sensor.apple_tree_moisture",
battery: "sensor.apple_tree_battery",
temperature: "sensor.apple_tree_temperature",
conductivity: "sensor.apple_tree_conductivity",
brightness: "sensor.apple_tree_brightness",
},
unit_of_measurement_dict: {
temperature: "°C",
moisture: "%",
brightness: "lx",
battery: "%",
conductivity: "μS/cm",
},
moisture: 54,
battery: 2,
temperature: 15.6,
conductivity: 1,
brightness: 25,
max_brightness: 20,
friendly_name: "Apple Tree",
unit_of_measurement_dict: {
temperature: "°C",
moisture: "%",
brightness: "lx",
battery: "%",
conductivity: "μS/cm",
},
},
{
entity_id: "plant.sunflowers",
state: "ok",
attributes: {
problem: "moisture, temperature, conductivity",
sensors: {
moisture: "sensor.sunflowers_moisture",
temperature: "sensor.sunflowers_temperature",
conductivity: "sensor.sunflowers_conductivity",
brightness: "sensor.sunflowers_brightness",
},
unit_of_measurement_dict: {
temperature: "°C",
moisture: "%",
brightness: "lx",
conductivity: "μS/cm",
},
moisture: 54,
temperature: 15.6,
conductivity: 1,
brightness: 25,
entity_picture: "/images/sunflowers.jpg",
moisture: 54,
battery: 95,
temperature: 15.6,
conductivity: 1,
brightness: 12,
max_brightness: 20,
friendly_name: "Lemon Tree",
}),
getEntity("plant", "apple_tree", "ok", {
problem: "brightness",
sensors: {
moisture: "sensor.apple_tree_moisture",
battery: "sensor.apple_tree_battery",
temperature: "sensor.apple_tree_temperature",
conductivity: "sensor.apple_tree_conductivity",
brightness: "sensor.apple_tree_brightness",
},
},
unit_of_measurement_dict: {
temperature: "°C",
moisture: "%",
brightness: "lx",
battery: "%",
conductivity: "μS/cm",
},
moisture: 54,
battery: 2,
temperature: 15.6,
conductivity: 1,
brightness: 25,
max_brightness: 20,
friendly_name: "Apple Tree",
}),
getEntity("plant", "sunflowers", "ok", {
problem: "moisture, temperature, conductivity",
sensors: {
moisture: "sensor.sunflowers_moisture",
temperature: "sensor.sunflowers_temperature",
conductivity: "sensor.sunflowers_conductivity",
brightness: "sensor.sunflowers_brightness",
},
unit_of_measurement_dict: {
temperature: "°C",
moisture: "%",
brightness: "lx",
conductivity: "μS/cm",
},
moisture: 54,
temperature: 15.6,
conductivity: 1,
brightness: 25,
entity_picture: "/images/sunflowers.jpg",
}),
];

View File

@@ -5,24 +5,17 @@ import "../../../../src/components/ha-card";
import "../../../../src/components/ha-yaml-editor";
import type { Action } from "../../../../src/data/script";
import { describeAction } from "../../../../src/data/script_i18n";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../../src/types";
const ENTITIES = [
{
entity_id: "scene.kitchen_morning",
state: "scening",
attributes: {
friendly_name: "Kitchen Morning",
},
},
{
entity_id: "media_player.kitchen",
state: "playing",
attributes: {
friendly_name: "Sonos Kitchen",
},
},
getEntity("scene", "kitchen_morning", "scening", {
friendly_name: "Kitchen Morning",
}),
getEntity("media_player", "kitchen", "playing", {
friendly_name: "Sonos Kitchen",
}),
];
const ACTIONS = [

View File

@@ -5,31 +5,20 @@ import "../../../../src/components/ha-card";
import "../../../../src/components/ha-yaml-editor";
import type { Condition } from "../../../../src/data/automation";
import { describeCondition } from "../../../../src/data/automation_i18n";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../../src/types";
const ENTITIES = [
{
entity_id: "light.kitchen",
state: "on",
attributes: {
friendly_name: "Kitchen Light",
},
},
{
entity_id: "device_tracker.person",
state: "home",
attributes: {
friendly_name: "Person",
},
},
{
entity_id: "zone.home",
state: "",
attributes: {
friendly_name: "Home",
},
},
getEntity("light", "kitchen", "on", {
friendly_name: "Kitchen Light",
}),
getEntity("device_tracker", "person", "home", {
friendly_name: "Person",
}),
getEntity("zone", "home", "", {
friendly_name: "Home",
}),
];
const conditions: Condition[] = [

View File

@@ -5,31 +5,20 @@ import "../../../../src/components/ha-card";
import "../../../../src/components/ha-yaml-editor";
import type { LegacyTrigger } from "../../../../src/data/automation";
import { describeTrigger } from "../../../../src/data/automation_i18n";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../../src/types";
const ENTITIES = [
{
entity_id: "light.kitchen",
state: "on",
attributes: {
friendly_name: "Kitchen Light",
},
},
{
entity_id: "person.person",
state: "",
attributes: {
friendly_name: "Person",
},
},
{
entity_id: "zone.home",
state: "",
attributes: {
friendly_name: "Home",
},
},
getEntity("light", "kitchen", "on", {
friendly_name: "Kitchen Light",
}),
getEntity("person", "person", "", {
friendly_name: "Person",
}),
getEntity("zone", "home", "", {
friendly_name: "Home",
}),
];
const triggers = [

View File

@@ -12,53 +12,34 @@ import "../../../../src/components/ha-form/ha-form";
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
import type { AreaRegistryEntry } from "../../../../src/data/area/area_registry";
import type { DeviceRegistryEntry } from "../../../../src/data/device/device_registry";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../../src/types";
import "../../components/demo-black-white-row";
const ENTITIES = [
{
entity_id: "alarm_control_panel.alarm",
state: "disarmed",
attributes: {
friendly_name: "Alarm",
},
},
{
entity_id: "media_player.livingroom",
state: "playing",
attributes: {
friendly_name: "Livingroom",
media_content_type: "music",
device_class: "tv",
},
},
{
entity_id: "media_player.lounge",
state: "idle",
attributes: {
friendly_name: "Lounge",
supported_features: 444983,
device_class: "speaker",
},
},
{
entity_id: "light.bedroom",
state: "on",
attributes: {
friendly_name: "Bedroom",
effect: "colorloop",
effect_list: ["colorloop", "random"],
},
},
{
entity_id: "switch.coffee",
state: "off",
attributes: {
friendly_name: "Coffee",
device_class: "switch",
},
},
getEntity("alarm_control_panel", "alarm", "disarmed", {
friendly_name: "Alarm",
}),
getEntity("media_player", "livingroom", "playing", {
friendly_name: "Livingroom",
media_content_type: "music",
device_class: "tv",
}),
getEntity("media_player", "lounge", "idle", {
friendly_name: "Lounge",
supported_features: 444983,
device_class: "speaker",
}),
getEntity("light", "bedroom", "on", {
friendly_name: "Bedroom",
effect: "colorloop",
effect_list: ["colorloop", "random"],
}),
getEntity("switch", "coffee", "off", {
friendly_name: "Coffee",
device_class: "switch",
}),
];
const DEVICES: DeviceRegistryEntry[] = [
@@ -480,12 +461,6 @@ const SCHEMAS: {
},
{ type: "string", name: "path", default: "/" },
{ type: "boolean", name: "ssl", default: false },
{
type: "string",
name: "comments",
default: "disabled field",
disabled: true,
},
],
},
];

View File

@@ -1,82 +0,0 @@
---
title: Input
---
# Input `<ha-input>`
A text input component supporting Home Assistant theming and validation, based on webawesome input.
Supports multiple input types including text, number, password, email, search, and more.
## Implementation
### Example usage
```html
<ha-input label="Name" value="Hello"></ha-input>
<ha-input label="Email" type="email" placeholder="you@example.com"></ha-input>
<ha-input label="Password" type="password" password-toggle></ha-input>
<ha-input label="Required" required></ha-input>
<ha-input label="Disabled" disabled value="Can't touch this"></ha-input>
```
### API
This component is based on the webawesome input component.
**Slots**
- `start`: Content placed before the input (usually for icons or prefixes).
- `end`: Content placed after the input (usually for icons or suffixes).
- `label`: Custom label content. Overrides the `label` property.
- `hint`: Custom hint content. Overrides the `hint` property.
- `clear-icon`: Custom clear icon.
- `show-password-icon`: Custom show password icon.
- `hide-password-icon`: Custom hide password icon.
**Properties/Attributes**
| Name | Type | Default | Description |
| -------------------- | ---------------------------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- |
| appearance | "material"/"outlined" | "material" | Sets the input appearance style. "material" is the default filled style, "outlined" uses a bordered style. |
| type | "text"/"number"/"password"/"email"/"search"/"tel"/"url"/"date"/"datetime-local"/"time"/"color" | "text" | Sets the input type. |
| value | String | - | The current value of the input. |
| label | String | "" | The input's label text. |
| hint | String | "" | The input's hint/helper text. |
| placeholder | String | "" | Placeholder text shown when the input is empty. |
| with-clear | Boolean | false | Adds a clear button when the input is not empty. |
| readonly | Boolean | false | Makes the input readonly. |
| disabled | Boolean | false | Disables the input and prevents user interaction. |
| required | Boolean | false | Makes the input a required field. |
| password-toggle | Boolean | false | Adds a button to toggle the password visibility. |
| without-spin-buttons | Boolean | false | Hides the browser's built-in spin buttons for number inputs. |
| auto-validate | Boolean | false | Validates the input on blur instead of on form submit. |
| invalid | Boolean | false | Marks the input as invalid. |
| inset-label | Boolean | false | Uses an inset label style where the label stays inside the input. |
| validation-message | String | "" | Custom validation message shown when the input is invalid. |
| pattern | String | - | A regular expression pattern to validate input against. |
| minlength | Number | - | The minimum length of input that will be considered valid. |
| maxlength | Number | - | The maximum length of input that will be considered valid. |
| min | Number/String | - | The input's minimum value. Only applies to date and number input types. |
| max | Number/String | - | The input's maximum value. Only applies to date and number input types. |
| step | Number/"any" | - | Specifies the granularity that the value must adhere to. |
**CSS Custom Properties**
- `--ha-input-padding-top` - Padding above the input.
- `--ha-input-padding-bottom` - Padding below the input. Defaults to `var(--ha-space-2)`.
- `--ha-input-text-align` - Text alignment of the input. Defaults to `start`.
- `--ha-input-required-marker` - The marker shown after the label for required fields. Defaults to `"*"`.
---
## Derivatives
The following components extend or wrap `ha-input` for specific use cases:
- **`<ha-input-search>`** — A pre-configured search input with a magnify icon, clear button, and localized "Search" placeholder. Extends `ha-input`.
- **`<ha-input-copy>`** — A read-only input with a copy-to-clipboard button. Supports optional value masking with a reveal toggle.
- **`<ha-input-multi>`** — A dynamic list of text inputs for managing arrays of strings. Supports adding, removing, and drag-and-drop reordering.

View File

@@ -1,232 +0,0 @@
import { ContextProvider } from "@lit/context";
import { mdiMagnify } from "@mdi/js";
import type { TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement } from "lit/decorators";
import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/input/ha-input";
import "../../../../src/components/input/ha-input-copy";
import "../../../../src/components/input/ha-input-multi";
import "../../../../src/components/input/ha-input-search";
import { localizeContext } from "../../../../src/data/context";
const LOCALIZE_KEYS: Record<string, string> = {
"ui.common.copy": "Copy",
"ui.common.show": "Show",
"ui.common.hide": "Hide",
"ui.common.add": "Add",
"ui.common.remove": "Remove",
"ui.common.search": "Search",
"ui.common.copied_clipboard": "Copied to clipboard",
};
@customElement("demo-components-ha-input")
export class DemoHaInput extends LitElement {
constructor() {
super();
// Provides localizeContext for ha-input-copy, ha-input-multi and ha-input-search
// eslint-disable-next-line no-new
new ContextProvider(this, {
context: localizeContext,
initialValue: ((key: string) => LOCALIZE_KEYS[key] ?? key) as any,
});
}
protected render(): TemplateResult {
return html`
${["light", "dark"].map(
(mode) => html`
<div class=${mode}>
<ha-card header="ha-input in ${mode}">
<div class="card-content">
<h3>Basic</h3>
<div class="row">
<ha-input label="Default"></ha-input>
<ha-input label="With value" value="Hello"></ha-input>
<ha-input
label="With placeholder"
placeholder="Type here..."
></ha-input>
</div>
<h3>Input types</h3>
<div class="row">
<ha-input label="Text" type="text" value="Text"></ha-input>
<ha-input label="Number" type="number" value="42"></ha-input>
<ha-input
label="Email"
type="email"
placeholder="you@example.com"
></ha-input>
</div>
<div class="row">
<ha-input
label="Password"
type="password"
value="secret"
password-toggle
></ha-input>
<ha-input label="URL" type="url" placeholder="https://...">
</ha-input>
<ha-input label="Date" type="date"></ha-input>
</div>
<h3>States</h3>
<div class="row">
<ha-input
label="Disabled"
disabled
value="Disabled"
></ha-input>
<ha-input
label="Readonly"
readonly
value="Readonly"
></ha-input>
<ha-input label="Required" required></ha-input>
</div>
<div class="row">
<ha-input
label="Invalid"
invalid
validation-message="This field is required"
value=""
></ha-input>
<ha-input label="With hint" hint="This is a hint"></ha-input>
<ha-input
label="With clear"
with-clear
value="Clear me"
></ha-input>
</div>
<h3>With slots</h3>
<div class="row">
<ha-input label="With prefix">
<span slot="start">$</span>
</ha-input>
<ha-input label="With suffix">
<span slot="end">kg</span>
</ha-input>
<ha-input label="With icon">
<ha-svg-icon .path=${mdiMagnify} slot="start"></ha-svg-icon>
</ha-input>
</div>
<h3>Appearance: outlined</h3>
<div class="row">
<ha-input
appearance="outlined"
label="Outlined"
value="Hello"
></ha-input>
<ha-input
appearance="outlined"
label="Outlined disabled"
disabled
value="Disabled"
></ha-input>
<ha-input
appearance="outlined"
label="Outlined invalid"
invalid
validation-message="Required"
></ha-input>
</div>
<div class="row">
<ha-input
appearance="outlined"
placeholder="Placeholder only"
></ha-input>
</div>
</div>
</ha-card>
<ha-card header="Derivatives in ${mode}">
<div class="card-content">
<h3>ha-input-search</h3>
<ha-input-search label="Search label"></ha-input-search>
<ha-input-search appearance="outlined"></ha-input-search>
<h3>ha-input-copy</h3>
<ha-input-copy
value="my-api-token-123"
masked-value="••••••••••••••••••"
masked-toggle
></ha-input-copy>
<h3>ha-input-multi</h3>
<ha-input-multi
label="URL"
add-label="Add URL"
.value=${["https://example.com"]}
></ha-input-multi>
</div>
</ha-card>
</div>
`
)}
`;
}
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
);
}
static styles = css`
:host {
display: flex;
justify-content: center;
}
.dark,
.light {
display: block;
background-color: var(--primary-background-color);
padding: 0 50px;
}
ha-card {
margin: 24px auto;
}
.card-content {
display: flex;
flex-direction: column;
gap: var(--ha-space-2);
}
h3 {
margin: var(--ha-space-4) 0 var(--ha-space-1) 0;
font-size: var(--ha-font-size-l);
font-weight: var(--ha-font-weight-medium);
}
h3:first-child {
margin-top: 0;
}
.row {
display: flex;
gap: var(--ha-space-4);
}
.row > * {
flex: 1;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"demo-components-ha-input": DemoHaInput;
}
}

View File

@@ -8,7 +8,6 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
import { mockFloorRegistry } from "../../../../demo/src/stubs/floor_registry";
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry";
import type { HASSDomEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-formfield";
import "../../../../src/components/ha-selector/ha-selector";
import "../../../../src/components/ha-settings-row";
@@ -17,59 +16,33 @@ import type { BlueprintInput } from "../../../../src/data/blueprint";
import type { DeviceRegistryEntry } from "../../../../src/data/device/device_registry";
import type { FloorRegistryEntry } from "../../../../src/data/floor_registry";
import type { LabelRegistryEntry } from "../../../../src/data/label/label_registry";
import {
showDialog,
type ShowDialogParams,
} from "../../../../src/dialogs/make-dialog-manager";
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import type { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
import type { HomeAssistant } from "../../../../src/types";
import "../../components/demo-black-white-row";
const ENTITIES = [
{
entity_id: "alarm_control_panel.alarm",
state: "disarmed",
attributes: {
friendly_name: "Alarm",
},
},
{
entity_id: "media_player.livingroom",
state: "playing",
attributes: {
friendly_name: "Livingroom",
},
},
{
entity_id: "media_player.lounge",
state: "idle",
attributes: {
friendly_name: "Lounge",
supported_features: 444983,
},
},
{
entity_id: "light.bedroom",
state: "on",
attributes: {
friendly_name: "Bedroom",
},
},
{
entity_id: "switch.coffee",
state: "off",
attributes: {
friendly_name: "Coffee",
},
},
{
entity_id: "number.number",
state: "5",
attributes: {
friendly_name: "Number",
},
},
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",
}),
getEntity("number", "number", 5, {
friendly_name: "Number",
}),
];
const DEVICES: DeviceRegistryEntry[] = [
@@ -638,15 +611,14 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
};
};
private _dialogManager = (e: HASSDomEvent<ShowDialogParams<unknown>>) => {
const { dialogTag, dialogImport, dialogParams, addHistory, parentElement } =
e.detail;
private _dialogManager = (e) => {
const { dialogTag, dialogImport, dialogParams, addHistory } = e.detail;
showDialog(
this,
this.shadowRoot!,
dialogTag,
dialogParams,
dialogImport,
parentElement,
addHistory
);
};

View File

@@ -28,12 +28,9 @@ This element is based on webawesome `wa-tooltip` it only sets some css tokens an
In your theme settings use this without the prefixed `--`.
- `--ha-tooltip-background-color` (Default: `var(--secondary-background-color)`)
- `--ha-tooltip-text-color` (Default: `var(--primary-text-color)`)
- `--ha-tooltip-font-family` (Default: `var(--ha-font-family-body)`)
- `--ha-tooltip-font-size` (Default: `var(--ha-font-size-s)`)
- `--ha-tooltip-font-weight` (Default: `var(--ha-font-weight-normal)`)
- `--ha-tooltip-line-height` (Default: `var(--ha-line-height-condensed)`)
- `--ha-tooltip-padding` (Default: 8px)
- `--ha-tooltip-border-radius` (Default: `var(--ha-border-radius-sm)`)
- `--ha-tooltip-border-radius` (Default: 4px)
- `--ha-tooltip-arrow-size` (Default: 8px)
- `--wa-tooltip-font-family` (Default: `var(--ha-font-family-body)`)
- `--ha-tooltip-font-size` (Default: `var(--ha-font-size-s)`)
- `--wa-tooltip-font-weight` (Default: `var(--ha-font-weight-normal)`)
- `--wa-tooltip-line-height` (Default: `var(--ha-line-height-condensed)`)

View File

@@ -1,40 +1,25 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
{
entity_id: "alarm_control_panel.alarm",
state: "disarmed",
attributes: {
friendly_name: "Alarm",
},
},
{
entity_id: "alarm_control_panel.alarm_armed",
state: "armed_home",
attributes: {
friendly_name: "Alarm",
},
},
{
entity_id: "alarm_control_panel.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Alarm",
},
},
{
entity_id: "alarm_control_panel.alarm_code",
state: "disarmed",
attributes: {
friendly_name: "Alarm",
code_format: "number",
},
},
getEntity("alarm_control_panel", "alarm", "disarmed", {
friendly_name: "Alarm",
}),
getEntity("alarm_control_panel", "alarm_armed", "armed_home", {
friendly_name: "Alarm",
}),
getEntity("alarm_control_panel", "unavailable", "unavailable", {
friendly_name: "Alarm",
}),
getEntity("alarm_control_panel", "alarm_code", "disarmed", {
friendly_name: "Alarm",
code_format: "number",
}),
];
const CONFIGS = [

View File

@@ -1,79 +1,44 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Bed Light",
},
},
{
entity_id: "switch.bed_ac",
state: "on",
attributes: {
friendly_name: "Ecobee",
},
},
{
entity_id: "sensor.bed_temp",
state: "72",
attributes: {
friendly_name: "Bedroom Temp",
device_class: "temperature",
unit_of_measurement: "°F",
},
},
{
entity_id: "light.living_room_light",
state: "off",
attributes: {
friendly_name: "Living Room Light",
},
},
{
entity_id: "fan.living_room",
state: "on",
attributes: {
friendly_name: "Living Room Fan",
},
},
{
entity_id: "sensor.office_humidity",
state: "73",
attributes: {
friendly_name: "Office Humidity",
device_class: "humidity",
unit_of_measurement: "%",
},
},
{
entity_id: "light.office",
state: "on",
attributes: {
friendly_name: "Office Light",
},
},
{
entity_id: "fan.kitchen",
state: "on",
attributes: {
friendly_name: "Kitchen Fan",
},
},
{
entity_id: "binary_sensor.kitchen_door",
state: "on",
attributes: {
friendly_name: "Office Door",
device_class: "door",
},
},
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
}),
getEntity("switch", "bed_ac", "on", {
friendly_name: "Ecobee",
}),
getEntity("sensor", "bed_temp", "72", {
friendly_name: "Bedroom Temp",
device_class: "temperature",
unit_of_measurement: "°F",
}),
getEntity("light", "living_room_light", "off", {
friendly_name: "Living Room Light",
}),
getEntity("fan", "living_room", "on", {
friendly_name: "Living Room Fan",
}),
getEntity("sensor", "office_humidity", "73", {
friendly_name: "Office Humidity",
device_class: "humidity",
unit_of_measurement: "%",
}),
getEntity("light", "office", "on", {
friendly_name: "Office Light",
}),
getEntity("fan", "kitchen", "on", {
friendly_name: "Kitchen Fan",
}),
getEntity("binary_sensor", "kitchen_door", "on", {
friendly_name: "Office Door",
device_class: "door",
}),
];
// TODO: Update image here

View File

@@ -1,39 +1,24 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
{
entity_id: "light.controller_1",
state: "on",
attributes: {
friendly_name: "Controller 1",
},
},
{
entity_id: "light.controller_2",
state: "on",
attributes: {
friendly_name: "Controller 2",
},
},
{
entity_id: "light.floor",
state: "off",
attributes: {
friendly_name: "Floor light",
},
},
{
entity_id: "light.kitchen",
state: "on",
attributes: {
friendly_name: "Kitchen light",
},
},
getEntity("light", "controller_1", "on", {
friendly_name: "Controller 1",
}),
getEntity("light", "controller_2", "on", {
friendly_name: "Controller 2",
}),
getEntity("light", "floor", "off", {
friendly_name: "Floor light",
}),
getEntity("light", "kitchen", "on", {
friendly_name: "Kitchen light",
}),
];
const CONFIGS = [

View File

@@ -1,257 +1,150 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Bed Light",
},
},
{
entity_id: "group.kitchen",
state: "on",
attributes: {
entity_id: ["light.bed_light"],
order: 8,
friendly_name: "Kitchen Group",
},
},
{
entity_id: "lock.kitchen_door",
state: "locked",
attributes: {
friendly_name: "Kitchen Lock",
},
},
{
entity_id: "cover.kitchen_window",
state: "open",
attributes: {
friendly_name: "Kitchen Window",
supported_features: 11,
},
},
{
entity_id: "scene.romantic_lights",
state: "scening",
attributes: {
entity_id: ["light.bed_light", "light.ceiling_lights"],
friendly_name: "Romantic Scene",
},
},
{
entity_id: "device_tracker.demo_paulus",
state: "home",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
},
},
{
entity_id: "climate.ecobee",
state: "auto",
attributes: {
current_temperature: 73,
min_temp: 45,
max_temp: 95,
temperature: null,
target_temp_high: 75,
target_temp_low: 70,
fan_mode: "Auto Low",
fan_list: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
operation_mode: "auto",
operation_list: ["heat", "cool", "auto", "off"],
hold_mode: "home",
swing_mode: "Auto",
swing_list: ["Auto", "1", "2", "3", "Off"],
unit_of_measurement: "°F",
friendly_name: "Ecobee",
supported_features: 1014,
},
},
{
entity_id: "input_number.number",
state: "5",
attributes: {
min: 0,
max: 10,
step: 1,
mode: "slider",
unit_of_measurement: "dB",
friendly_name: "Number",
icon: "mdi:bell-ring",
},
},
{
entity_id: "input_boolean.toggle",
state: "on",
attributes: {
friendly_name: "Toggle",
},
},
{
entity_id: "input_datetime.date_and_time",
state: "2022-01-10 00:00:00",
attributes: {
has_date: true,
has_time: true,
editable: true,
year: 2022,
month: 1,
day: 10,
hour: 0,
minute: 0,
second: 0,
timestamp: 1641801600,
friendly_name: "Date and Time",
},
},
{
entity_id: "sensor.humidity",
state: "23.2",
attributes: {
friendly_name: "Humidity",
unit_of_measurement: "%",
},
},
{
entity_id: "input_select.dropdown",
state: "Soda",
attributes: {
friendly_name: "Dropdown",
options: ["Soda", "Beer", "Wine"],
},
},
{
entity_id: "input_text.text",
state: "Inspiration",
attributes: {
friendly_name: "Text",
mode: "text",
},
},
{
entity_id: "timer.timer",
state: "idle",
attributes: {
friendly_name: "Timer",
duration: "0:05:00",
},
},
{
entity_id: "counter.counter",
state: "3",
attributes: {
friendly_name: "Counter",
initial: 0,
step: 1,
minimum: 0,
maximum: 10,
},
},
{
entity_id: "text.message",
state: "Hello!",
attributes: {
friendly_name: "Message",
},
},
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
}),
getEntity("group", "kitchen", "on", {
entity_id: ["light.bed_light"],
order: 8,
friendly_name: "Kitchen Group",
}),
getEntity("lock", "kitchen_door", "locked", {
friendly_name: "Kitchen Lock",
}),
getEntity("cover", "kitchen_window", "open", {
friendly_name: "Kitchen Window",
supported_features: 11,
}),
getEntity("scene", "romantic_lights", "scening", {
entity_id: ["light.bed_light", "light.ceiling_lights"],
friendly_name: "Romantic Scene",
}),
getEntity("device_tracker", "demo_paulus", "home", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
}),
getEntity("climate", "ecobee", "auto", {
current_temperature: 73,
min_temp: 45,
max_temp: 95,
temperature: null,
target_temp_high: 75,
target_temp_low: 70,
fan_mode: "Auto Low",
fan_list: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
operation_mode: "auto",
operation_list: ["heat", "cool", "auto", "off"],
hold_mode: "home",
swing_mode: "Auto",
swing_list: ["Auto", "1", "2", "3", "Off"],
unit_of_measurement: "°F",
friendly_name: "Ecobee",
supported_features: 1014,
}),
getEntity("input_number", "number", 5, {
min: 0,
max: 10,
step: 1,
mode: "slider",
unit_of_measurement: "dB",
friendly_name: "Number",
icon: "mdi:bell-ring",
}),
getEntity("input_boolean", "toggle", "on", {
friendly_name: "Toggle",
}),
getEntity("input_datetime", "date_and_time", "2022-01-10 00:00:00", {
has_date: true,
has_time: true,
editable: true,
year: 2022,
month: 1,
day: 10,
hour: 0,
minute: 0,
second: 0,
timestamp: 1641801600,
friendly_name: "Date and Time",
}),
getEntity("sensor", "humidity", "23.2", {
friendly_name: "Humidity",
unit_of_measurement: "%",
}),
getEntity("input_select", "dropdown", "Soda", {
friendly_name: "Dropdown",
options: ["Soda", "Beer", "Wine"],
}),
getEntity("input_text", "text", "Inspiration", {
friendly_name: "Text",
mode: "text",
}),
getEntity("timer", "timer", "idle", {
friendly_name: "Timer",
duration: "0:05:00",
}),
getEntity("counter", "counter", "3", {
friendly_name: "Counter",
initial: 0,
step: 1,
minimum: 0,
maximum: 10,
}),
getEntity("text", "message", "Hello!", {
friendly_name: "Message",
}),
{
entity_id: "light.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Bed Light",
},
},
{
entity_id: "lock.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Kitchen Door",
},
},
{
entity_id: "cover.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Kitchen Window",
supported_features: 11,
},
},
{
entity_id: "scene.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Romantic Scene",
},
},
{
entity_id: "device_tracker.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Paulus",
},
},
{
entity_id: "climate.unavailable",
state: "unavailable",
attributes: {
unit_of_measurement: "°F",
friendly_name: "Ecobee",
supported_features: 1014,
},
},
{
entity_id: "input_number.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Allowed Noise",
icon: "mdi:bell-ring",
},
},
{
entity_id: "input_select.unavailable",
state: "unavailable",
attributes: {
unit_of_measurement: "dB",
friendly_name: "Who cooks",
icon: "mdi:cheff",
},
},
{
entity_id: "text.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Message",
},
},
{
entity_id: "event.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Empty remote",
},
},
{
entity_id: "event.doorbell",
state: "2023-07-17T21:26:11.615+00:00",
attributes: {
friendly_name: "Doorbell",
device_class: "doorbell",
event_type: "Ding-Dong",
},
},
getEntity("light", "unavailable", "unavailable", {
friendly_name: "Bed Light",
}),
getEntity("lock", "unavailable", "unavailable", {
friendly_name: "Kitchen Door",
}),
getEntity("cover", "unavailable", "unavailable", {
friendly_name: "Kitchen Window",
supported_features: 11,
}),
getEntity("scene", "unavailable", "unavailable", {
friendly_name: "Romantic Scene",
}),
getEntity("device_tracker", "unavailable", "unavailable", {
friendly_name: "Paulus",
}),
getEntity("climate", "unavailable", "unavailable", {
unit_of_measurement: "°F",
friendly_name: "Ecobee",
supported_features: 1014,
}),
getEntity("input_number", "unavailable", "unavailable", {
friendly_name: "Allowed Noise",
icon: "mdi:bell-ring",
}),
getEntity("input_select", "unavailable", "unavailable", {
unit_of_measurement: "dB",
friendly_name: "Who cooks",
icon: "mdi:cheff",
}),
getEntity("text", "unavailable", "unavailable", {
friendly_name: "Message",
}),
getEntity("event", "unavailable", "unavailable", {
friendly_name: "Empty remote",
}),
getEntity("event", "doorbell", "2023-07-17T21:26:11.615+00:00", {
friendly_name: "Doorbell",
device_class: "doorbell",
event_type: "Ding-Dong",
}),
];
const CONFIGS = [

View File

@@ -1,18 +1,15 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Bed Light",
},
},
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
}),
];
const CONFIGS = [

View File

@@ -1,117 +1,74 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
{
entity_id: "device_tracker.demo_paulus",
state: "work",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 25,
friendly_name: "Paulus",
},
},
{
entity_id: "device_tracker.demo_anne_therese",
state: "school",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 50,
friendly_name: "Anne Therese",
},
},
{
entity_id: "device_tracker.demo_home_boy",
state: "home",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 75,
friendly_name: "Home Boy",
},
},
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Bed Light",
},
},
{
entity_id: "light.kitchen_lights",
state: "on",
attributes: {
friendly_name: "Kitchen Lights",
},
},
{
entity_id: "light.ceiling_lights",
state: "off",
attributes: {
friendly_name: "Ceiling Lights",
},
},
{
entity_id: "sensor.battery_1",
state: "20",
attributes: {
device_class: "battery",
friendly_name: "Battery 1",
unit_of_measurement: "%",
},
},
{
entity_id: "sensor.battery_2",
state: "35",
attributes: {
device_class: "battery",
friendly_name: "Battery 2",
unit_of_measurement: "%",
},
},
{
entity_id: "sensor.battery_3",
state: "40",
attributes: {
device_class: "battery",
friendly_name: "Battery 3",
unit_of_measurement: "%",
},
},
{
entity_id: "sensor.battery_4",
state: "80",
attributes: {
device_class: "battery",
friendly_name: "Battery 4",
unit_of_measurement: "%",
},
},
{
entity_id: "input_number.min_battery_level",
state: "30",
attributes: {
mode: "slider",
step: 10,
min: 0,
max: 100,
icon: "mdi:battery-alert-variant",
friendly_name: "Minimum Battery Level",
unit_of_measurement: "%",
},
},
getEntity("device_tracker", "demo_paulus", "work", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 25,
friendly_name: "Paulus",
}),
getEntity("device_tracker", "demo_anne_therese", "school", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 50,
friendly_name: "Anne Therese",
}),
getEntity("device_tracker", "demo_home_boy", "home", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 75,
friendly_name: "Home Boy",
}),
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
}),
getEntity("light", "kitchen_lights", "on", {
friendly_name: "Kitchen Lights",
}),
getEntity("light", "ceiling_lights", "off", {
friendly_name: "Ceiling Lights",
}),
getEntity("sensor", "battery_1", 20, {
device_class: "battery",
friendly_name: "Battery 1",
unit_of_measurement: "%",
}),
getEntity("sensor", "battery_2", 35, {
device_class: "battery",
friendly_name: "Battery 2",
unit_of_measurement: "%",
}),
getEntity("sensor", "battery_3", 40, {
device_class: "battery",
friendly_name: "Battery 3",
unit_of_measurement: "%",
}),
getEntity("sensor", "battery_4", 80, {
device_class: "battery",
friendly_name: "Battery 4",
unit_of_measurement: "%",
}),
getEntity("input_number", "min_battery_level", 30, {
mode: "slider",
step: 10,
min: 0,
max: 100,
icon: "mdi:battery-alert-variant",
friendly_name: "Minimum Battery Level",
unit_of_measurement: "%",
}),
];
const CONFIGS = [

View File

@@ -1,30 +1,23 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
{ entity_id: "sensor.brightness", state: "12", attributes: {} },
{ entity_id: "sensor.brightness_medium", state: "53", attributes: {} },
{ entity_id: "sensor.brightness_high", state: "87", attributes: {} },
{ entity_id: "plant.bonsai", state: "ok", attributes: {} },
{ entity_id: "sensor.not_working", state: "unavailable", attributes: {} },
{
entity_id: "sensor.outside_humidity",
state: "54",
attributes: {
unit_of_measurement: "%",
},
},
{
entity_id: "sensor.outside_temperature",
state: "15.6",
attributes: {
unit_of_measurement: "°C",
},
},
getEntity("sensor", "brightness", "12", {}),
getEntity("sensor", "brightness_medium", "53", {}),
getEntity("sensor", "brightness_high", "87", {}),
getEntity("plant", "bonsai", "ok", {}),
getEntity("sensor", "not_working", "unavailable", {}),
getEntity("sensor", "outside_humidity", "54", {
unit_of_measurement: "%",
}),
getEntity("sensor", "outside_temperature", "15.6", {
unit_of_measurement: "°C",
}),
];
const CONFIGS = [
@@ -54,19 +47,6 @@ const CONFIGS = [
needle: true
`,
},
{
heading: "Rendering needle and severity levels",
config: `
- type: gauge
entity: sensor.brightness_high
name: Brightness High
needle: true
severity:
red: 75
green: 0
yellow: 50
`,
},
{
heading: "Setting severity levels",
config: `
@@ -134,21 +114,6 @@ const CONFIGS = [
entity: sensor.not_working
`,
},
{
heading: "Lower minimum",
config: `
- type: gauge
entity: sensor.brightness_high
needle: true
severity:
green: 0
yellow: 0.45
red: 0.9
min: -0.05
name: " "
max: 1.9
unit: GBP/h`,
},
];
@customElement("demo-lovelace-gauge-card")

View File

@@ -1,89 +1,62 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
{
entity_id: "device_tracker.demo_paulus",
state: "home",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
},
},
{
entity_id: "media_player.living_room",
state: "playing",
attributes: {
volume_level: 1,
is_volume_muted: false,
media_content_id: "eyU3bRy2x44",
media_content_type: "movie",
media_duration: 300,
media_position: 45.017773,
media_position_updated_at: "2018-07-19T10:44:45.919514+00:00",
media_title: "♥♥ The Best Fireplace Video (3 hours)",
app_name: "YouTube",
sound_mode: "Dummy Music",
sound_mode_list: ["Dummy Music", "Dummy Movie"],
shuffle: false,
friendly_name: "Living Room",
entity_picture:
"/api/media_player_proxy/media_player.living_room?token=e925f8db7f7bd1f317e4524dcb8333d60f6019219a3799a22604b5787f243567&cache=bc2ffb49c4f67034",
supported_features: 115597,
},
},
{
entity_id: "sun.sun",
state: "below_horizon",
attributes: {
next_dawn: "2018-07-19T20:48:47+00:00",
next_dusk: "2018-07-20T11:46:06+00:00",
next_midnight: "2018-07-19T16:17:28+00:00",
next_noon: "2018-07-20T04:17:26+00:00",
next_rising: "2018-07-19T21:16:31+00:00",
next_setting: "2018-07-20T11:18:22+00:00",
elevation: 67.69,
azimuth: 338.55,
friendly_name: "Sun",
},
},
{
entity_id: "cover.kitchen_window",
state: "open",
attributes: {
friendly_name: "Kitchen Window",
supported_features: 11,
},
},
{
entity_id: "light.kitchen_lights",
state: "on",
attributes: {
friendly_name: "Kitchen Lights",
},
},
{
entity_id: "light.ceiling_lights",
state: "off",
attributes: {
friendly_name: "Ceiling Lights",
},
},
{
entity_id: "lock.kitchen_door",
state: "locked",
attributes: {
friendly_name: "Kitchen Door",
},
},
getEntity("device_tracker", "demo_paulus", "home", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
}),
getEntity("media_player", "living_room", "playing", {
volume_level: 1,
is_volume_muted: false,
media_content_id: "eyU3bRy2x44",
media_content_type: "movie",
media_duration: 300,
media_position: 45.017773,
media_position_updated_at: "2018-07-19T10:44:45.919514+00:00",
media_title: "♥♥ The Best Fireplace Video (3 hours)",
app_name: "YouTube",
sound_mode: "Dummy Music",
sound_mode_list: ["Dummy Music", "Dummy Movie"],
shuffle: false,
friendly_name: "Living Room",
entity_picture:
"/api/media_player_proxy/media_player.living_room?token=e925f8db7f7bd1f317e4524dcb8333d60f6019219a3799a22604b5787f243567&cache=bc2ffb49c4f67034",
supported_features: 115597,
}),
getEntity("sun", "sun", "below_horizon", {
next_dawn: "2018-07-19T20:48:47+00:00",
next_dusk: "2018-07-20T11:46:06+00:00",
next_midnight: "2018-07-19T16:17:28+00:00",
next_noon: "2018-07-20T04:17:26+00:00",
next_rising: "2018-07-19T21:16:31+00:00",
next_setting: "2018-07-20T11:18:22+00:00",
elevation: 67.69,
azimuth: 338.55,
friendly_name: "Sun",
}),
getEntity("cover", "kitchen_window", "open", {
friendly_name: "Kitchen Window",
supported_features: 11,
}),
getEntity("light", "kitchen_lights", "on", {
friendly_name: "Kitchen Lights",
}),
getEntity("light", "ceiling_lights", "off", {
friendly_name: "Ceiling Lights",
}),
getEntity("lock", "kitchen_door", "locked", {
friendly_name: "Kitchen Door",
}),
];
const CONFIGS = [

View File

@@ -2,69 +2,46 @@ import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { mockHistory } from "../../../../demo/src/stubs/history";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
{
entity_id: "light.kitchen_lights",
state: "on",
attributes: {
friendly_name: "Kitchen Lights",
},
},
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Bed Lights",
},
},
{
entity_id: "device_tracker.demo_paulus",
state: "work",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
},
},
{
entity_id: "device_tracker.demo_anne_therese",
state: "school",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Anne Therese",
},
},
{
entity_id: "device_tracker.demo_home_boy",
state: "home",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Home Boy",
},
},
{
entity_id: "sensor.illumination",
state: "23",
attributes: {
friendly_name: "Illumination",
unit_of_measurement: "lx",
},
},
getEntity("light", "kitchen_lights", "on", {
friendly_name: "Kitchen Lights",
}),
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Lights",
}),
getEntity("device_tracker", "demo_paulus", "work", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
}),
getEntity("device_tracker", "demo_anne_therese", "school", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Anne Therese",
}),
getEntity("device_tracker", "demo_home_boy", "home", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Home Boy",
}),
getEntity("sensor", "illumination", "23", {
friendly_name: "Illumination",
unit_of_measurement: "lx",
}),
];
const CONFIGS = [

View File

@@ -1,44 +1,29 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Bed Light",
brightness: 255,
},
},
{
entity_id: "light.dim_on",
state: "on",
attributes: {
friendly_name: "Dining Room",
supported_features: 1,
brightness: 100,
},
},
{
entity_id: "light.dim_off",
state: "off",
attributes: {
friendly_name: "Dining Room",
supported_features: 1,
},
},
{
entity_id: "light.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Lost Light",
supported_features: 1,
},
},
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
brightness: 255,
}),
getEntity("light", "dim_on", "on", {
friendly_name: "Dining Room",
supported_features: 1,
brightness: 100,
}),
getEntity("light", "dim_off", "off", {
friendly_name: "Dining Room",
supported_features: 1,
}),
getEntity("light", "unavailable", "unavailable", {
friendly_name: "Lost Light",
supported_features: 1,
}),
];
const CONFIGS = [

View File

@@ -1,86 +1,59 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
const ENTITIES = [
{
entity_id: "device_tracker.demo_paulus",
state: "not_home",
attributes: {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
},
},
{
entity_id: "device_tracker.demo_home_boy",
state: "home",
attributes: {
source_type: "gps",
latitude: 32.87334,
longitude: 117.22745,
gps_accuracy: 20,
battery: 53,
friendly_name: "Home Boy",
},
},
{
entity_id: "zone.home",
state: "zoning",
attributes: {
latitude: 32.87354,
longitude: 117.22765,
radius: 100,
friendly_name: "Home",
icon: "mdi:home",
},
},
{
entity_id: "zone.bushfire",
state: "zoning",
attributes: {
latitude: -33.8611,
longitude: 151.203,
radius: 35000,
friendly_name: "Bushfire Zone",
icon: "mdi:home",
},
},
{
entity_id: "geo_location.nelsons_creek",
state: "15",
attributes: {
source: "bushfire_demo",
latitude: -34.07792,
longitude: 151.03219,
friendly_name: "Nelsons Creek",
},
},
{
entity_id: "geo_location.forest_rd_nowra_hill",
state: "8",
attributes: {
source: "bushfire_demo",
latitude: -33.69452,
longitude: 151.19577,
friendly_name: "Forest Rd, Nowra Hill",
},
},
{
entity_id: "geo_location.stoney_ridge_rd_kremnos",
state: "20",
attributes: {
source: "bushfire_demo",
latitude: -33.66584,
longitude: 150.97209,
friendly_name: "Stoney Ridge Rd, Kremnos",
},
},
getEntity("device_tracker", "demo_paulus", "not_home", {
source_type: "gps",
latitude: 32.877105,
longitude: 117.232185,
gps_accuracy: 91,
battery: 71,
friendly_name: "Paulus",
}),
getEntity("device_tracker", "demo_home_boy", "home", {
source_type: "gps",
latitude: 32.87334,
longitude: 117.22745,
gps_accuracy: 20,
battery: 53,
friendly_name: "Home Boy",
}),
getEntity("zone", "home", "zoning", {
latitude: 32.87354,
longitude: 117.22765,
radius: 100,
friendly_name: "Home",
icon: "mdi:home",
}),
getEntity("zone", "bushfire", "zoning", {
latitude: -33.8611,
longitude: 151.203,
radius: 35000,
friendly_name: "Bushfire Zone",
icon: "mdi:home",
}),
getEntity("geo_location", "nelsons_creek", "15", {
source: "bushfire_demo",
latitude: -34.07792,
longitude: 151.03219,
friendly_name: "Nelsons Creek",
}),
getEntity("geo_location", "forest_rd_nowra_hill", "8", {
source: "bushfire_demo",
latitude: -33.69452,
longitude: 151.19577,
friendly_name: "Forest Rd, Nowra Hill",
}),
getEntity("geo_location", "stoney_ridge_rd_kremnos", "20", {
source: "bushfire_demo",
latitude: -33.66584,
longitude: 150.97209,
friendly_name: "Stoney Ridge Rd, Kremnos",
}),
];
const CONFIGS = [

View File

@@ -1,19 +1,16 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
{
entity_id: "person.paulus",
state: "home",
attributes: {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
},
},
getEntity("person", "paulus", "home", {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
}),
];
const CONFIGS = [

View File

@@ -1,63 +1,40 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Bed Light",
},
},
{
entity_id: "group.all_lights",
state: "on",
attributes: {
entity_id: ["light.bed_light"],
order: 8,
friendly_name: "All Lights",
},
},
{
entity_id: "camera.demo_camera",
state: "idle",
attributes: {
access_token:
"2f5bb163fb91cd8770a9494fa5e7eab172d8d34f4aba806eb6b59411b8c720b8",
friendly_name: "Demo camera",
entity_picture:
"/api/camera_proxy/camera.demo_camera?token=2f5bb163fb91cd8770a9494fa5e7eab172d8d34f4aba806eb6b59411b8c720b8",
},
},
{
entity_id: "binary_sensor.movement_backyard",
state: "on",
attributes: {
friendly_name: "Movement Backyard",
device_class: "motion",
},
},
{
entity_id: "person.paulus",
state: "home",
attributes: {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
},
},
{
entity_id: "sensor.battery",
state: "35",
attributes: {
device_class: "battery",
friendly_name: "Battery",
unit_of_measurement: "%",
},
},
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
}),
getEntity("group", "all_lights", "on", {
entity_id: ["light.bed_light"],
order: 8,
friendly_name: "All Lights",
}),
getEntity("camera", "demo_camera", "idle", {
access_token:
"2f5bb163fb91cd8770a9494fa5e7eab172d8d34f4aba806eb6b59411b8c720b8",
friendly_name: "Demo camera",
entity_picture:
"/api/camera_proxy/camera.demo_camera?token=2f5bb163fb91cd8770a9494fa5e7eab172d8d34f4aba806eb6b59411b8c720b8",
}),
getEntity("binary_sensor", "movement_backyard", "on", {
friendly_name: "Movement Backyard",
device_class: "motion",
}),
getEntity("person", "paulus", "home", {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
}),
getEntity("sensor", "battery", 35, {
device_class: "battery",
friendly_name: "Battery",
unit_of_measurement: "%",
}),
];
const CONFIGS = [

View File

@@ -1,33 +1,22 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
{
entity_id: "light.kitchen_lights",
state: "on",
attributes: {
friendly_name: "Kitchen Lights",
},
},
{
entity_id: "light.bed_light",
state: "off",
attributes: {
friendly_name: "Bed Light",
},
},
{
entity_id: "person.paulus",
state: "home",
attributes: {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
},
},
getEntity("light", "kitchen_lights", "on", {
friendly_name: "Kitchen Lights",
}),
getEntity("light", "bed_light", "off", {
friendly_name: "Bed Light",
}),
getEntity("person", "paulus", "home", {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
}),
];
const CONFIGS = [

View File

@@ -1,58 +1,35 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
{
entity_id: "switch.decorative_lights",
state: "on",
attributes: {
friendly_name: "Decorative Lights",
},
},
{
entity_id: "light.ceiling_lights",
state: "on",
attributes: {
friendly_name: "Ceiling Lights",
},
},
{
entity_id: "binary_sensor.movement_backyard",
state: "on",
attributes: {
friendly_name: "Movement Backyard",
device_class: "moving",
},
},
{
entity_id: "binary_sensor.basement_floor_wet",
state: "off",
attributes: {
friendly_name: "Basement Floor Wet",
device_class: "moisture",
},
},
{
entity_id: "person.paulus",
state: "home",
attributes: {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
},
},
{
entity_id: "sensor.battery",
state: "35",
attributes: {
device_class: "battery",
friendly_name: "Battery",
unit_of_measurement: "%",
},
},
getEntity("switch", "decorative_lights", "on", {
friendly_name: "Decorative Lights",
}),
getEntity("light", "ceiling_lights", "on", {
friendly_name: "Ceiling Lights",
}),
getEntity("binary_sensor", "movement_backyard", "on", {
friendly_name: "Movement Backyard",
device_class: "moving",
}),
getEntity("binary_sensor", "basement_floor_wet", "off", {
friendly_name: "Basement Floor Wet",
device_class: "moisture",
}),
getEntity("person", "paulus", "home", {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
}),
getEntity("sensor", "battery", 35, {
device_class: "battery",
friendly_name: "Battery",
unit_of_measurement: "%",
}),
];
const CONFIGS = [

View File

@@ -1,123 +1,100 @@
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
{
entity_id: "climate.ecobee",
state: "auto",
attributes: {
current_temperature: 73,
min_temp: 45,
max_temp: 95,
temperature: null,
target_temp_high: 75,
target_temp_low: 70,
fan_mode: "Auto Low",
fan_modes: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
hvac_modes: ["heat", "cool", "auto", "off"],
swing_mode: "Auto",
swing_modes: ["Auto", "1", "2", "3", "Off"],
friendly_name: "Ecobee",
supported_features: 59,
preset_mode: "eco",
preset_modes: ["away", "eco"],
},
},
{
entity_id: "climate.nest",
state: "heat",
attributes: {
current_temperature: 17,
min_temp: 15,
max_temp: 25,
temperature: 19,
fan_mode: "Auto Low",
fan_modes: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
hvac_modes: ["heat", "cool", "auto", "off"],
swing_mode: "Auto",
swing_modes: ["Auto", "1", "2", "3", "Off"],
friendly_name: "Nest",
supported_features: 43,
},
},
{
entity_id: "climate.overkiz_radiator",
state: "heat",
attributes: {
current_temperature: 18,
min_temp: 7,
max_temp: 35,
temperature: 20,
hvac_modes: ["heat", "auto", "off"],
friendly_name: "Overkiz radiator",
supported_features: 17,
preset_mode: "comfort",
preset_modes: [
"none",
"frost_protection",
"eco",
"comfort",
"comfort-1",
"comfort-2",
"auto",
"boost",
"external",
"prog",
],
},
},
{
entity_id: "climate.overkiz_towel_dryer",
state: "heat",
attributes: {
current_temperature: null,
min_temp: 7,
max_temp: 35,
hvac_modes: ["heat", "off"],
friendly_name: "Overkiz towel dryer",
supported_features: 16,
preset_mode: "eco",
preset_modes: [
"none",
"frost_protection",
"eco",
"comfort",
"comfort-1",
"comfort-2",
],
},
},
{
entity_id: "climate.sensibo",
state: "fan_only",
attributes: {
current_temperature: null,
temperature: null,
min_temp: 0,
max_temp: 1,
target_temp_step: 1,
hvac_modes: ["fan_only", "off"],
friendly_name: "Sensibo purifier",
fan_modes: ["low", "high"],
fan_mode: "low",
swing_modes: ["both", "rangefull", "off"],
swing_mode: "rangefull",
swing_horizontal_modes: ["both", "rangefull", "off"],
swing_horizontal_mode: "both",
supported_features: 553,
},
},
{
entity_id: "climate.unavailable",
state: "unavailable",
attributes: {
supported_features: 43,
},
},
getEntity("climate", "ecobee", "auto", {
current_temperature: 73,
min_temp: 45,
max_temp: 95,
temperature: null,
target_temp_high: 75,
target_temp_low: 70,
fan_mode: "Auto Low",
fan_modes: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
hvac_modes: ["heat", "cool", "auto", "off"],
swing_mode: "Auto",
swing_modes: ["Auto", "1", "2", "3", "Off"],
friendly_name: "Ecobee",
supported_features: 59,
preset_mode: "eco",
preset_modes: ["away", "eco"],
}),
getEntity("climate", "nest", "heat", {
current_temperature: 17,
min_temp: 15,
max_temp: 25,
temperature: 19,
fan_mode: "Auto Low",
fan_modes: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
hvac_modes: ["heat", "cool", "auto", "off"],
swing_mode: "Auto",
swing_modes: ["Auto", "1", "2", "3", "Off"],
friendly_name: "Nest",
supported_features: 43,
}),
getEntity("climate", "overkiz_radiator", "heat", {
current_temperature: 18,
min_temp: 7,
max_temp: 35,
temperature: 20,
hvac_modes: ["heat", "auto", "off"],
friendly_name: "Overkiz radiator",
supported_features: 17,
preset_mode: "comfort",
preset_modes: [
"none",
"frost_protection",
"eco",
"comfort",
"comfort-1",
"comfort-2",
"auto",
"boost",
"external",
"prog",
],
}),
getEntity("climate", "overkiz_towel_dryer", "heat", {
current_temperature: null,
min_temp: 7,
max_temp: 35,
hvac_modes: ["heat", "off"],
friendly_name: "Overkiz towel dryer",
supported_features: 16,
preset_mode: "eco",
preset_modes: [
"none",
"frost_protection",
"eco",
"comfort",
"comfort-1",
"comfort-2",
],
}),
getEntity("climate", "sensibo", "fan_only", {
current_temperature: null,
temperature: null,
min_temp: 0,
max_temp: 1,
target_temp_step: 1,
hvac_modes: ["fan_only", "off"],
friendly_name: "Sensibo purifier",
fan_modes: ["low", "high"],
fan_mode: "low",
swing_modes: ["both", "rangefull", "off"],
swing_mode: "rangefull",
swing_horizontal_modes: ["both", "rangefull", "off"],
swing_horizontal_mode: "both",
supported_features: 553,
}),
getEntity("climate", "unavailable", "unavailable", {
supported_features: 43,
}),
];
const CONFIGS = [

View File

@@ -6,6 +6,7 @@ import { LightColorMode } from "../../../../src/data/light";
import { LockEntityFeature } from "../../../../src/data/lock";
import { MediaPlayerEntityFeature } from "../../../../src/data/media-player";
import { VacuumEntityFeature } from "../../../../src/data/vacuum";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
@@ -13,154 +14,102 @@ import { ClimateEntityFeature } from "../../../../src/data/climate";
import { FanEntityFeature } from "../../../../src/data/fan";
const ENTITIES = [
{
entity_id: "switch.tv_outlet",
state: "on",
attributes: {
friendly_name: "TV outlet",
device_class: "outlet",
},
},
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Bed Light",
supported_color_modes: [LightColorMode.HS, LightColorMode.COLOR_TEMP],
},
},
{
entity_id: "light.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Unavailable entity",
},
},
{
entity_id: "lock.front_door",
state: "locked",
attributes: {
friendly_name: "Front Door Lock",
device_class: "lock",
supported_features: LockEntityFeature.OPEN,
},
},
{
entity_id: "media_player.living_room",
state: "playing",
attributes: {
friendly_name: "Living room speaker",
supported_features: MediaPlayerEntityFeature.VOLUME_SET,
},
},
{
entity_id: "climate.thermostat",
state: "heat",
attributes: {
current_temperature: 73,
min_temp: 45,
max_temp: 95,
temperature: 80,
hvac_modes: ["heat", "cool", "auto", "off"],
friendly_name: "Thermostat",
hvac_action: "heating",
},
},
{
entity_id: "person.paulus",
state: "home",
attributes: {
friendly_name: "Paulus",
},
},
{
entity_id: "vacuum.first_floor_vacuum",
state: "docked",
attributes: {
friendly_name: "First floor vacuum",
supported_features:
VacuumEntityFeature.START +
VacuumEntityFeature.STOP +
VacuumEntityFeature.RETURN_HOME,
},
},
{
entity_id: "cover.kitchen_shutter",
state: "open",
attributes: {
friendly_name: "Kitchen shutter",
device_class: "shutter",
supported_features:
CoverEntityFeature.CLOSE +
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP,
},
},
{
entity_id: "cover.pergola_roof",
state: "open",
attributes: {
friendly_name: "Pergola Roof",
supported_features:
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT,
},
},
{
entity_id: "input_number.counter",
state: "1.0",
attributes: {
friendly_name: "Counter",
initial: 0,
min: 0,
max: 100,
step: 1,
mode: "slider",
},
},
{
entity_id: "climate.dual_thermostat",
state: "heat/cool",
attributes: {
friendly_name: "Dual thermostat",
hvac_modes: ["off", "cool", "heat_cool", "auto", "dry", "fan_only"],
min_temp: 7,
max_temp: 35,
fan_modes: ["on_low", "on_high", "auto_low", "auto_high", "off"],
preset_modes: ["home", "eco", "away"],
swing_modes: ["auto", "1", "2", "3", "off"],
switch_horizontal_modes: ["auto", "4", "5", "6", "off"],
current_temperature: 23,
target_temp_high: 24,
target_temp_low: 21,
fan_mode: "auto_low",
preset_mode: "home",
swing_mode: "auto",
swing_horizontal_mode: "off",
supported_features:
ClimateEntityFeature.TURN_ON +
ClimateEntityFeature.TURN_OFF +
ClimateEntityFeature.SWING_MODE +
ClimateEntityFeature.SWING_HORIZONTAL_MODE +
ClimateEntityFeature.PRESET_MODE +
ClimateEntityFeature.FAN_MODE +
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
},
},
{
entity_id: "fan.fan_demo",
state: "on",
attributes: {
friendly_name: "Ceiling fan",
device_class: "fan",
direction: "reverse",
supported_features:
FanEntityFeature.DIRECTION +
FanEntityFeature.SET_SPEED +
FanEntityFeature.OSCILLATE,
},
},
getEntity("switch", "tv_outlet", "on", {
friendly_name: "TV outlet",
device_class: "outlet",
}),
getEntity("light", "bed_light", "on", {
friendly_name: "Bed Light",
supported_color_modes: [LightColorMode.HS, LightColorMode.COLOR_TEMP],
}),
getEntity("light", "unavailable", "unavailable", {
friendly_name: "Unavailable entity",
}),
getEntity("lock", "front_door", "locked", {
friendly_name: "Front Door Lock",
device_class: "lock",
supported_features: LockEntityFeature.OPEN,
}),
getEntity("media_player", "living_room", "playing", {
friendly_name: "Living room speaker",
supported_features: MediaPlayerEntityFeature.VOLUME_SET,
}),
getEntity("climate", "thermostat", "heat", {
current_temperature: 73,
min_temp: 45,
max_temp: 95,
temperature: 80,
hvac_modes: ["heat", "cool", "auto", "off"],
friendly_name: "Thermostat",
hvac_action: "heating",
}),
getEntity("person", "paulus", "home", {
friendly_name: "Paulus",
}),
getEntity("vacuum", "first_floor_vacuum", "docked", {
friendly_name: "First floor vacuum",
supported_features:
VacuumEntityFeature.START +
VacuumEntityFeature.STOP +
VacuumEntityFeature.RETURN_HOME,
}),
getEntity("cover", "kitchen_shutter", "open", {
friendly_name: "Kitchen shutter",
device_class: "shutter",
supported_features:
CoverEntityFeature.CLOSE +
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP,
}),
getEntity("cover", "pergola_roof", "open", {
friendly_name: "Pergola Roof",
supported_features:
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT,
}),
getEntity("input_number", "counter", "1.0", {
friendly_name: "Counter",
initial: 0,
min: 0,
max: 100,
step: 1,
mode: "slider",
}),
getEntity("climate", "dual_thermostat", "heat/cool", {
friendly_name: "Dual thermostat",
hvac_modes: ["off", "cool", "heat_cool", "auto", "dry", "fan_only"],
min_temp: 7,
max_temp: 35,
fan_modes: ["on_low", "on_high", "auto_low", "auto_high", "off"],
preset_modes: ["home", "eco", "away"],
swing_modes: ["auto", "1", "2", "3", "off"],
switch_horizontal_modes: ["auto", "4", "5", "6", "off"],
current_temperature: 23,
target_temp_high: 24,
target_temp_low: 21,
fan_mode: "auto_low",
preset_mode: "home",
swing_mode: "auto",
swing_horizontal_mode: "off",
supported_features:
ClimateEntityFeature.TURN_ON +
ClimateEntityFeature.TURN_OFF +
ClimateEntityFeature.SWING_MODE +
ClimateEntityFeature.SWING_HORIZONTAL_MODE +
ClimateEntityFeature.PRESET_MODE +
ClimateEntityFeature.FAN_MODE +
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
}),
getEntity("fan", "fan_demo", "on", {
friendly_name: "Ceiling fan",
device_class: "fan",
direction: "reverse",
supported_features:
FanEntityFeature.DIRECTION +
FanEntityFeature.SET_SPEED +
FanEntityFeature.OSCILLATE,
}),
];
const CONFIGS = [

View File

@@ -3,25 +3,18 @@ import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators";
import { mockIcons } from "../../../../demo/src/stubs/icons";
import { mockTodo } from "../../../../demo/src/stubs/todo";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
const ENTITIES = [
{
entity_id: "todo.shopping_list",
state: "2",
attributes: {
friendly_name: "Shopping List",
supported_features: 15,
},
},
{
entity_id: "todo.read_only",
state: "2",
attributes: {
friendly_name: "Read only",
},
},
getEntity("todo", "shopping_list", "2", {
friendly_name: "Shopping List",
supported_features: 15,
}),
getEntity("todo", "read_only", "2", {
friendly_name: "Read only",
}),
];
const CONFIGS = [

View File

@@ -3,135 +3,108 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
import { ClimateEntityFeature } from "../../../../src/data/climate";
const ENTITIES = [
{
entity_id: "climate.radiator",
state: "heat",
attributes: {
friendly_name: "Basic heater",
hvac_modes: ["heat", "off"],
hvac_mode: "heat",
current_temperature: 18,
temperature: 20,
min_temp: 10,
max_temp: 30,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE,
},
},
{
entity_id: "climate.ac",
state: "cool",
attributes: {
friendly_name: "Basic air conditioning",
hvac_modes: ["cool", "off"],
hvac_mode: "cool",
current_temperature: 18,
temperature: 20,
min_temp: 10,
max_temp: 30,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE,
},
},
{
entity_id: "climate.fan",
state: "fan_only",
attributes: {
friendly_name: "Basic fan",
hvac_modes: ["fan_only", "off"],
hvac_mode: "fan_only",
fan_modes: ["low", "high"],
fan_mode: "low",
current_temperature: null,
temperature: null,
min_temp: 0,
max_temp: 1,
target_temp_step: 1,
supported_features:
// eslint-disable-next-line no-bitwise
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE,
},
},
{
entity_id: "climate.hvac",
state: "auto",
attributes: {
friendly_name: "Basic hvac",
hvac_modes: ["auto", "off"],
hvac_mode: "auto",
current_temperature: 18,
min_temp: 10,
max_temp: 30,
target_temp_step: 1,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
target_temp_low: 20,
target_temp_high: 25,
},
},
{
entity_id: "climate.advanced",
state: "auto",
attributes: {
friendly_name: "Advanced hvac",
supported_features:
// eslint-disable-next-line no-bitwise
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE |
ClimateEntityFeature.TARGET_HUMIDITY |
ClimateEntityFeature.PRESET_MODE,
hvac_modes: ["auto", "off"],
hvac_mode: "auto",
preset_modes: ["eco", "comfort", "boost"],
preset_mode: "eco",
current_temperature: 18,
min_temp: 10,
max_temp: 30,
target_temp_step: 1,
target_temp_low: 20,
target_temp_high: 25,
current_humidity: 40,
min_humidity: 0,
max_humidity: 100,
humidity: 50,
},
},
{
entity_id: "climate.towel_dryer",
state: "heat",
attributes: {
friendly_name: "Preset only heater",
hvac_modes: ["heat", "off"],
hvac_mode: "heat",
preset_modes: [
"none",
"frost_protection",
"eco",
"comfort",
"comfort-1",
"comfort-2",
],
preset_mode: "eco",
current_temperature: null,
min_temp: 7,
max_temp: 35,
supported_features: ClimateEntityFeature.PRESET_MODE,
},
},
{
entity_id: "climate.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Unavailable heater",
hvac_modes: ["heat", "off"],
hvac_mode: "heat",
min_temp: 10,
max_temp: 30,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE,
},
},
getEntity("climate", "radiator", "heat", {
friendly_name: "Basic heater",
hvac_modes: ["heat", "off"],
hvac_mode: "heat",
current_temperature: 18,
temperature: 20,
min_temp: 10,
max_temp: 30,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE,
}),
getEntity("climate", "ac", "cool", {
friendly_name: "Basic air conditioning",
hvac_modes: ["cool", "off"],
hvac_mode: "cool",
current_temperature: 18,
temperature: 20,
min_temp: 10,
max_temp: 30,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE,
}),
getEntity("climate", "fan", "fan_only", {
friendly_name: "Basic fan",
hvac_modes: ["fan_only", "off"],
hvac_mode: "fan_only",
fan_modes: ["low", "high"],
fan_mode: "low",
current_temperature: null,
temperature: null,
min_temp: 0,
max_temp: 1,
target_temp_step: 1,
supported_features:
// eslint-disable-next-line no-bitwise
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE,
}),
getEntity("climate", "hvac", "auto", {
friendly_name: "Basic hvac",
hvac_modes: ["auto", "off"],
hvac_mode: "auto",
current_temperature: 18,
min_temp: 10,
max_temp: 30,
target_temp_step: 1,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
target_temp_low: 20,
target_temp_high: 25,
}),
getEntity("climate", "advanced", "auto", {
friendly_name: "Advanced hvac",
supported_features:
// eslint-disable-next-line no-bitwise
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE |
ClimateEntityFeature.TARGET_HUMIDITY |
ClimateEntityFeature.PRESET_MODE,
hvac_modes: ["auto", "off"],
hvac_mode: "auto",
preset_modes: ["eco", "comfort", "boost"],
preset_mode: "eco",
current_temperature: 18,
min_temp: 10,
max_temp: 30,
target_temp_step: 1,
target_temp_low: 20,
target_temp_high: 25,
current_humidity: 40,
min_humidity: 0,
max_humidity: 100,
humidity: 50,
}),
getEntity("climate", "towel_dryer", "heat", {
friendly_name: "Preset only heater",
hvac_modes: ["heat", "off"],
hvac_mode: "heat",
preset_modes: [
"none",
"frost_protection",
"eco",
"comfort",
"comfort-1",
"comfort-2",
],
preset_mode: "eco",
current_temperature: null,
min_temp: 7,
max_temp: 35,
supported_features: ClimateEntityFeature.PRESET_MODE,
}),
getEntity("climate", "unavailable", "unavailable", {
friendly_name: "Unavailable heater",
hvac_modes: ["heat", "off"],
hvac_mode: "heat",
min_temp: 10,
max_temp: 30,
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE,
}),
];
@customElement("demo-more-info-climate")
@@ -144,7 +117,7 @@ class DemoMoreInfoClimate extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}

View File

@@ -4,189 +4,138 @@ import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import { CoverEntityFeature } from "../../../../src/data/cover";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
{
entity_id: "cover.position_buttons",
state: "on",
attributes: {
friendly_name: "Position Buttons",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE,
},
},
{
entity_id: "cover.position_slider_half",
state: "on",
attributes: {
friendly_name: "Position Half-Open",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION,
current_position: 50,
},
},
{
entity_id: "cover.position_slider_open",
state: "on",
attributes: {
friendly_name: "Position Open",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION,
current_position: 100,
},
},
{
entity_id: "cover.position_slider_closed",
state: "on",
attributes: {
friendly_name: "Position Closed",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION,
current_position: 0,
},
},
{
entity_id: "cover.tilt_buttons",
state: "on",
attributes: {
friendly_name: "Tilt Buttons",
supported_features:
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT,
},
},
{
entity_id: "cover.tilt_slider_half",
state: "on",
attributes: {
friendly_name: "Tilt Half-Open",
supported_features:
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_tilt_position: 50,
},
},
{
entity_id: "cover.tilt_slider_open",
state: "on",
attributes: {
friendly_name: "Tilt Open",
supported_features:
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_tilt_position: 100,
},
},
{
entity_id: "cover.tilt_slider_closed",
state: "on",
attributes: {
friendly_name: "Tilt Closed",
supported_features:
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_tilt_position: 0,
},
},
{
entity_id: "cover.position_slider_tilt_slider",
state: "on",
attributes: {
friendly_name: "Both Sliders",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_position: 30,
current_tilt_position: 70,
},
},
{
entity_id: "cover.position_tilt_slider",
state: "on",
attributes: {
friendly_name: "Position & Tilt Slider",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_tilt_position: 70,
},
},
{
entity_id: "cover.position_slider_tilt",
state: "on",
attributes: {
friendly_name: "Position Slider & Tilt",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT,
current_position: 30,
},
},
{
entity_id: "cover.position_slider_only_tilt_slider",
state: "on",
attributes: {
friendly_name: "Position Slider Only & Tilt Buttons",
supported_features:
CoverEntityFeature.SET_POSITION +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT,
current_position: 30,
},
},
{
entity_id: "cover.position_slider_only_tilt",
state: "on",
attributes: {
friendly_name: "Position Slider Only & Tilt",
supported_features:
CoverEntityFeature.SET_POSITION +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_position: 30,
current_tilt_position: 70,
},
},
getEntity("cover", "position_buttons", "on", {
friendly_name: "Position Buttons",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE,
}),
getEntity("cover", "position_slider_half", "on", {
friendly_name: "Position Half-Open",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION,
current_position: 50,
}),
getEntity("cover", "position_slider_open", "on", {
friendly_name: "Position Open",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION,
current_position: 100,
}),
getEntity("cover", "position_slider_closed", "on", {
friendly_name: "Position Closed",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION,
current_position: 0,
}),
getEntity("cover", "tilt_buttons", "on", {
friendly_name: "Tilt Buttons",
supported_features:
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT,
}),
getEntity("cover", "tilt_slider_half", "on", {
friendly_name: "Tilt Half-Open",
supported_features:
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_tilt_position: 50,
}),
getEntity("cover", "tilt_slider_open", "on", {
friendly_name: "Tilt Open",
supported_features:
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_tilt_position: 100,
}),
getEntity("cover", "tilt_slider_closed", "on", {
friendly_name: "Tilt Closed",
supported_features:
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_tilt_position: 0,
}),
getEntity("cover", "position_slider_tilt_slider", "on", {
friendly_name: "Both Sliders",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_position: 30,
current_tilt_position: 70,
}),
getEntity("cover", "position_tilt_slider", "on", {
friendly_name: "Position & Tilt Slider",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_tilt_position: 70,
}),
getEntity("cover", "position_slider_tilt", "on", {
friendly_name: "Position Slider & Tilt",
supported_features:
CoverEntityFeature.OPEN +
CoverEntityFeature.STOP +
CoverEntityFeature.CLOSE +
CoverEntityFeature.SET_POSITION +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT,
current_position: 30,
}),
getEntity("cover", "position_slider_only_tilt_slider", "on", {
friendly_name: "Position Slider Only & Tilt Buttons",
supported_features:
CoverEntityFeature.SET_POSITION +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT,
current_position: 30,
}),
getEntity("cover", "position_slider_only_tilt", "on", {
friendly_name: "Position Slider Only & Tilt",
supported_features:
CoverEntityFeature.SET_POSITION +
CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT +
CoverEntityFeature.CLOSE_TILT +
CoverEntityFeature.SET_TILT_POSITION,
current_position: 30,
current_tilt_position: 70,
}),
];
@customElement("demo-more-info-cover")
@@ -199,7 +148,7 @@ class DemoMoreInfoCover extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}

View File

@@ -3,24 +3,21 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
import { FanEntityFeature } from "../../../../src/data/fan";
const ENTITIES = [
{
entity_id: "fan.fan",
state: "on",
attributes: {
friendly_name: "Fan",
device_class: "fan",
supported_features:
FanEntityFeature.OSCILLATE +
FanEntityFeature.DIRECTION +
FanEntityFeature.SET_SPEED,
},
},
getEntity("fan", "fan", "on", {
friendly_name: "Fan",
device_class: "fan",
supported_features:
FanEntityFeature.OSCILLATE +
FanEntityFeature.DIRECTION +
FanEntityFeature.SET_SPEED,
}),
];
@customElement("demo-more-info-fan")
@@ -33,7 +30,7 @@ class DemoMoreInfoFan extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}

View File

@@ -3,38 +3,27 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
{
entity_id: "humidifier.humidifier",
state: "on",
attributes: {
friendly_name: "Humidifier",
device_class: "humidifier",
current_humidity: 50,
humidity: 30,
},
},
{
entity_id: "humidifier.dehumidifier",
state: "on",
attributes: {
friendly_name: "Dehumidifier",
device_class: "dehumidifier",
current_humidity: 50,
humidity: 30,
},
},
{
entity_id: "humidifier.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Unavailable humidifier",
},
},
getEntity("humidifier", "humidifier", "on", {
friendly_name: "Humidifier",
device_class: "humidifier",
current_humidity: 50,
humidity: 30,
}),
getEntity("humidifier", "dehumidifier", "on", {
friendly_name: "Dehumidifier",
device_class: "dehumidifier",
current_humidity: 50,
humidity: 30,
}),
getEntity("humidifier", "unavailable", "unavailable", {
friendly_name: "Unavailable humidifier",
}),
];
@customElement("demo-more-info-humidifier")
@@ -47,7 +36,7 @@ class DemoMoreInfoHumidifier extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}

View File

@@ -3,37 +3,30 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
{
entity_id: "input_number.box1",
state: "0",
attributes: {
friendly_name: "Box1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "box",
unit_of_measurement: "items",
},
},
{
entity_id: "input_number.slider1",
state: "0",
attributes: {
friendly_name: "Slider1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "slider",
unit_of_measurement: "items",
},
},
getEntity("input_number", "box1", 0, {
friendly_name: "Box1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "box",
unit_of_measurement: "items",
}),
getEntity("input_number", "slider1", 0, {
friendly_name: "Slider1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "slider",
unit_of_measurement: "items",
}),
];
@customElement("demo-more-info-input-number")
@@ -46,7 +39,7 @@ class DemoMoreInfoInputNumber extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}

View File

@@ -3,19 +3,16 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
{
entity_id: "input_text.text",
state: "Inspiration",
attributes: {
friendly_name: "Text",
mode: "text",
},
},
getEntity("input_text", "text", "Inspiration", {
friendly_name: "Text",
mode: "text",
}),
];
@customElement("demo-more-info-input-text")
@@ -28,7 +25,7 @@ class DemoMoreInfoInputText extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}

View File

@@ -4,172 +4,137 @@ import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import { LightColorMode, LightEntityFeature } from "../../../../src/data/light";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
{
entity_id: "light.bed_light",
state: "on",
attributes: {
friendly_name: "Basic Light",
},
},
{
entity_id: "light.kitchen_light",
state: "on",
attributes: {
friendly_name: "Brightness Light",
brightness: 200,
supported_color_modes: [LightColorMode.BRIGHTNESS],
color_mode: LightColorMode.BRIGHTNESS,
},
},
{
entity_id: "light.color_temperature_light",
state: "on",
attributes: {
friendly_name: "White Color Temperature Light",
brightness: 128,
color_temp: 75,
min_mireds: 30,
max_mireds: 150,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
],
color_mode: LightColorMode.COLOR_TEMP,
},
},
{
entity_id: "light.color_hs_light",
state: "on",
attributes: {
friendly_name: "Color HS Light",
brightness: 255,
hs_color: [30, 100],
rgb_color: [30, 100, 255],
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.HS,
],
color_mode: LightColorMode.HS,
effect_list: ["random", "colorloop"],
},
},
{
entity_id: "light.color_rgb_ct_light",
state: "on",
attributes: {
friendly_name: "Color RGB + CT Light",
brightness: 255,
color_temp: 75,
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.RGB,
],
color_mode: LightColorMode.COLOR_TEMP,
effect_list: ["random", "colorloop"],
},
},
{
entity_id: "light.color_RGB_light",
state: "on",
attributes: {
friendly_name: "Color Effects Light",
brightness: 255,
rgb_color: [30, 100, 255],
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [LightColorMode.BRIGHTNESS, LightColorMode.RGB],
color_mode: LightColorMode.RGB,
effect_list: ["random", "colorloop"],
},
},
{
entity_id: "light.color_rgbw_light",
state: "on",
attributes: {
friendly_name: "Color RGBW Light",
brightness: 255,
rgbw_color: [30, 100, 255, 125],
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.RGBW,
],
color_mode: LightColorMode.RGBW,
effect_list: ["random", "colorloop"],
},
},
{
entity_id: "light.color_rgbww_light",
state: "on",
attributes: {
friendly_name: "Color RGBWW Light",
brightness: 255,
rgbww_color: [30, 100, 255, 125, 10],
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.RGBWW,
],
color_mode: LightColorMode.RGBWW,
effect_list: ["random", "colorloop"],
},
},
{
entity_id: "light.color_xy_light",
state: "on",
attributes: {
friendly_name: "Color XY Light",
brightness: 255,
xy_color: [30, 100],
rgb_color: [30, 100, 255],
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.XY,
],
color_mode: LightColorMode.XY,
effect_list: ["random", "colorloop"],
},
},
getEntity("light", "bed_light", "on", {
friendly_name: "Basic Light",
}),
getEntity("light", "kitchen_light", "on", {
friendly_name: "Brightness Light",
brightness: 200,
supported_color_modes: [LightColorMode.BRIGHTNESS],
color_mode: LightColorMode.BRIGHTNESS,
}),
getEntity("light", "color_temperature_light", "on", {
friendly_name: "White Color Temperature Light",
brightness: 128,
color_temp: 75,
min_mireds: 30,
max_mireds: 150,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
],
color_mode: LightColorMode.COLOR_TEMP,
}),
getEntity("light", "color_hs_light", "on", {
friendly_name: "Color HS Light",
brightness: 255,
hs_color: [30, 100],
rgb_color: [30, 100, 255],
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.HS,
],
color_mode: LightColorMode.HS,
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_rgb_ct_light", "on", {
friendly_name: "Color RGB + CT Light",
brightness: 255,
color_temp: 75,
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.RGB,
],
color_mode: LightColorMode.COLOR_TEMP,
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_RGB_light", "on", {
friendly_name: "Color Effects Light",
brightness: 255,
rgb_color: [30, 100, 255],
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [LightColorMode.BRIGHTNESS, LightColorMode.RGB],
color_mode: LightColorMode.RGB,
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_rgbw_light", "on", {
friendly_name: "Color RGBW Light",
brightness: 255,
rgbw_color: [30, 100, 255, 125],
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.RGBW,
],
color_mode: LightColorMode.RGBW,
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_rgbww_light", "on", {
friendly_name: "Color RGBWW Light",
brightness: 255,
rgbww_color: [30, 100, 255, 125, 10],
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.RGBWW,
],
color_mode: LightColorMode.RGBWW,
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_xy_light", "on", {
friendly_name: "Color XY Light",
brightness: 255,
xy_color: [30, 100],
rgb_color: [30, 100, 255],
min_mireds: 30,
max_mireds: 150,
supported_features:
LightEntityFeature.EFFECT +
LightEntityFeature.FLASH +
LightEntityFeature.TRANSITION,
supported_color_modes: [
LightColorMode.BRIGHTNESS,
LightColorMode.COLOR_TEMP,
LightColorMode.XY,
],
color_mode: LightColorMode.XY,
effect_list: ["random", "colorloop"],
}),
];
@customElement("demo-more-info-light")
@@ -182,7 +147,7 @@ class DemoMoreInfoLight extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}

View File

@@ -3,26 +3,19 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
{
entity_id: "lock.lock",
state: "locked",
attributes: {
friendly_name: "Lock",
device_class: "lock",
},
},
{
entity_id: "lock.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Unavailable lock",
},
},
getEntity("lock", "lock", "locked", {
friendly_name: "Lock",
device_class: "lock",
}),
getEntity("lock", "unavailable", "unavailable", {
friendly_name: "Unavailable lock",
}),
];
@customElement("demo-more-info-lock")
@@ -35,7 +28,7 @@ class DemoMoreInfoLock extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}

View File

@@ -20,7 +20,7 @@ class DemoMoreInfoMediaPlayer extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}

View File

@@ -3,63 +3,48 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
{
entity_id: "number.box1",
state: "0",
attributes: {
friendly_name: "Box1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "box",
unit_of_measurement: "items",
},
},
{
entity_id: "number.slider1",
state: "0",
attributes: {
friendly_name: "Slider1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "slider",
unit_of_measurement: "items",
},
},
{
entity_id: "number.auto1",
state: "0",
attributes: {
friendly_name: "Auto1",
min: 0,
max: 1000,
step: 1,
initial: 0,
mode: "auto",
unit_of_measurement: "items",
},
},
{
entity_id: "number.auto2",
state: "0",
attributes: {
friendly_name: "Auto2",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "auto",
unit_of_measurement: "items",
},
},
getEntity("number", "box1", 0, {
friendly_name: "Box1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "box",
unit_of_measurement: "items",
}),
getEntity("number", "slider1", 0, {
friendly_name: "Slider1",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "slider",
unit_of_measurement: "items",
}),
getEntity("number", "auto1", 0, {
friendly_name: "Auto1",
min: 0,
max: 1000,
step: 1,
initial: 0,
mode: "auto",
unit_of_measurement: "items",
}),
getEntity("number", "auto2", 0, {
friendly_name: "Auto2",
min: 0,
max: 100,
step: 1,
initial: 0,
mode: "auto",
unit_of_measurement: "items",
}),
];
@customElement("demo-more-info-number")
@@ -72,7 +57,7 @@ class DemoMoreInfoNumber extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}

View File

@@ -3,26 +3,19 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
{
entity_id: "scene.romantic_lights",
state: "scening",
attributes: {
entity_id: ["light.bed_light", "light.ceiling_lights"],
friendly_name: "Romantic Scene",
},
},
{
entity_id: "scene.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Romantic Scene",
},
},
getEntity("scene", "romantic_lights", "scening", {
entity_id: ["light.bed_light", "light.ceiling_lights"],
friendly_name: "Romantic Scene",
}),
getEntity("scene", "unavailable", "unavailable", {
friendly_name: "Romantic Scene",
}),
];
@customElement("demo-more-info-scene")
@@ -35,7 +28,7 @@ class DemoMoreInfoScene extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}

View File

@@ -3,19 +3,16 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
{
entity_id: "timer.timer",
state: "idle",
attributes: {
friendly_name: "Timer",
duration: "0:05:00",
},
},
getEntity("timer", "timer", "idle", {
friendly_name: "Timer",
duration: "0:05:00",
}),
];
@customElement("demo-more-info-timer")
@@ -28,7 +25,7 @@ class DemoMoreInfoTimer extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}

View File

@@ -3,6 +3,7 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
@@ -22,208 +23,124 @@ const base_attributes = {
};
const ENTITIES = [
{
entity_id: "update.update1",
state: "on",
attributes: {
...base_attributes,
friendly_name: "Update",
},
},
{
entity_id: "update.update2",
state: "on",
attributes: {
...base_attributes,
title: null,
friendly_name: "Update without title",
},
},
{
entity_id: "update.update3",
state: "on",
attributes: {
...base_attributes,
release_url: null,
friendly_name: "Update without release_url",
},
},
{
entity_id: "update.update4",
state: "on",
attributes: {
...base_attributes,
release_summary: null,
friendly_name: "Update without release_summary",
},
},
{
entity_id: "update.update5",
state: "off",
attributes: {
...base_attributes,
installed_version: "1.2.3",
friendly_name: "No update",
},
},
{
entity_id: "update.update6",
state: "off",
attributes: {
...base_attributes,
skipped_version: "1.2.3",
friendly_name: "Skipped version",
},
},
{
entity_id: "update.update7",
state: "on",
attributes: {
...base_attributes,
supported_features:
base_attributes.supported_features + UpdateEntityFeature.BACKUP,
friendly_name: "With backup support",
},
},
{
entity_id: "update.update8",
state: "on",
attributes: {
...base_attributes,
in_progress: true,
friendly_name: "With true in_progress",
},
},
{
entity_id: "update.update9",
state: "on",
attributes: {
...base_attributes,
in_progress: 25,
supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
friendly_name: "With 25 in_progress",
},
},
{
entity_id: "update.update10",
state: "on",
attributes: {
...base_attributes,
in_progress: 50,
supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
friendly_name: "With 50 in_progress",
},
},
{
entity_id: "update.update11",
state: "on",
attributes: {
...base_attributes,
in_progress: 75,
supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
friendly_name: "With 75 in_progress",
},
},
{
entity_id: "update.update12",
state: "unavailable",
attributes: {
...base_attributes,
in_progress: 50,
friendly_name: "Unavailable",
},
},
{
entity_id: "update.update13",
state: "on",
attributes: {
...base_attributes,
supported_features: 0,
friendly_name: "No install support",
},
},
{
entity_id: "update.update14",
state: "off",
attributes: {
...base_attributes,
installed_version: null,
friendly_name: "Update without installed_version",
},
},
{
entity_id: "update.update15",
state: "off",
attributes: {
...base_attributes,
latest_version: null,
friendly_name: "Update without latest_version",
},
},
{
entity_id: "update.update16",
state: "off",
attributes: {
...base_attributes,
friendly_name: "Update with release notes",
supported_features:
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
},
},
{
entity_id: "update.update17",
state: "off",
attributes: {
...base_attributes,
friendly_name: "Update with release notes error",
supported_features:
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
},
},
{
entity_id: "update.update18",
state: "off",
attributes: {
...base_attributes,
friendly_name: "Update with release notes loading",
supported_features:
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
},
},
{
entity_id: "update.update19",
state: "on",
attributes: {
...base_attributes,
friendly_name: "Update with auto update",
auto_update: true,
},
},
{
entity_id: "update.update20",
state: "on",
attributes: {
...base_attributes,
in_progress: true,
title: undefined,
friendly_name: "Installing without title",
},
},
{
entity_id: "update.update21",
state: "on",
attributes: {
...base_attributes,
in_progress: true,
friendly_name:
"Update with in_progress true and UpdateEntityFeature.PROGRESS",
supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
},
},
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,
installed_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 + UpdateEntityFeature.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 + UpdateEntityFeature.PROGRESS,
friendly_name: "With 25 in_progress",
}),
getEntity("update", "update10", "on", {
...base_attributes,
in_progress: 50,
supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
friendly_name: "With 50 in_progress",
}),
getEntity("update", "update11", "on", {
...base_attributes,
in_progress: 75,
supported_features:
base_attributes.supported_features + UpdateEntityFeature.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,
installed_version: null,
friendly_name: "Update without installed_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 + UpdateEntityFeature.RELEASE_NOTES,
}),
getEntity("update", "update17", "off", {
...base_attributes,
friendly_name: "Update with release notes error",
supported_features:
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
}),
getEntity("update", "update18", "off", {
...base_attributes,
friendly_name: "Update with release notes loading",
supported_features:
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
}),
getEntity("update", "update19", "on", {
...base_attributes,
friendly_name: "Update with auto update",
auto_update: true,
}),
getEntity("update", "update20", "on", {
...base_attributes,
in_progress: true,
title: undefined,
friendly_name: "Installing without title",
}),
getEntity("update", "update21", "on", {
...base_attributes,
in_progress: true,
friendly_name:
"Update with in_progress true and UpdateEntityFeature.PROGRESS",
supported_features:
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
}),
];
@customElement("demo-more-info-update")
@@ -236,7 +153,7 @@ class DemoMoreInfoUpdate extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}

View File

@@ -3,23 +3,20 @@ import { html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
import { VacuumEntityFeature } from "../../../../src/data/vacuum";
const ENTITIES = [
{
entity_id: "vacuum.first_floor_vacuum",
state: "docked",
attributes: {
friendly_name: "First floor vacuum",
supported_features:
VacuumEntityFeature.START +
VacuumEntityFeature.STOP +
VacuumEntityFeature.RETURN_HOME,
},
},
getEntity("vacuum", "first_floor_vacuum", "docked", {
friendly_name: "First floor vacuum",
supported_features:
VacuumEntityFeature.START +
VacuumEntityFeature.STOP +
VacuumEntityFeature.RETURN_HOME,
}),
];
@customElement("demo-more-info-vacuum")
@@ -32,7 +29,7 @@ class DemoMoreInfoVacuum extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}

View File

@@ -4,46 +4,39 @@ import { customElement, property, query } from "lit/decorators";
import "../../../../src/components/ha-card";
import { WaterHeaterEntityFeature } from "../../../../src/data/water_heater";
import "../../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../../src/fake_data/entity";
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-more-infos";
const ENTITIES = [
{
entity_id: "water_heater.basic",
state: "eco",
attributes: {
friendly_name: "Basic heater",
operation_list: ["heat_pump", "eco", "performance", "off"],
operation_mode: "eco",
away_mode: "off",
target_temp_step: 1,
current_temperature: 55,
temperature: 60,
min_temp: 20,
max_temp: 70,
supported_features:
// eslint-disable-next-line no-bitwise
WaterHeaterEntityFeature.TARGET_TEMPERATURE |
WaterHeaterEntityFeature.OPERATION_MODE |
WaterHeaterEntityFeature.AWAY_MODE,
},
},
{
entity_id: "water_heater.unavailable",
state: "unavailable",
attributes: {
friendly_name: "Unavailable heater",
operation_list: ["heat_pump", "eco", "performance", "off"],
operation_mode: "off",
min_temp: 20,
max_temp: 70,
supported_features:
// eslint-disable-next-line no-bitwise
WaterHeaterEntityFeature.TARGET_TEMPERATURE |
WaterHeaterEntityFeature.OPERATION_MODE,
},
},
getEntity("water_heater", "basic", "eco", {
friendly_name: "Basic heater",
operation_list: ["heat_pump", "eco", "performance", "off"],
operation_mode: "eco",
away_mode: "off",
target_temp_step: 1,
current_temperature: 55,
temperature: 60,
min_temp: 20,
max_temp: 70,
supported_features:
// eslint-disable-next-line no-bitwise
WaterHeaterEntityFeature.TARGET_TEMPERATURE |
WaterHeaterEntityFeature.OPERATION_MODE |
WaterHeaterEntityFeature.AWAY_MODE,
}),
getEntity("water_heater", "unavailable", "unavailable", {
friendly_name: "Unavailable heater",
operation_list: ["heat_pump", "eco", "performance", "off"],
operation_mode: "off",
min_temp: 20,
max_temp: 70,
supported_features:
// eslint-disable-next-line no-bitwise
WaterHeaterEntityFeature.TARGET_TEMPERATURE |
WaterHeaterEntityFeature.OPERATION_MODE,
}),
];
@customElement("demo-more-info-water-heater")
@@ -56,7 +49,7 @@ class DemoMoreInfoWaterHeater extends LitElement {
return html`
<demo-more-infos
.hass=${this.hass}
.entities=${ENTITIES.map((ent) => ent.entity_id)}
.entities=${ENTITIES.map((ent) => ent.entityId)}
></demo-more-infos>
`;
}

View File

@@ -118,7 +118,7 @@ class HaLandingPage extends LandingPageBaseElement {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
makeDialogManager(this);
makeDialogManager(this, this.shadowRoot!);
if (window.innerWidth > 450) {
import("../../src/resources/particles");

View File

@@ -26,33 +26,33 @@
"license": "Apache-2.0",
"type": "module",
"dependencies": {
"@babel/runtime": "7.29.2",
"@babel/runtime": "7.28.6",
"@braintree/sanitize-url": "7.1.2",
"@codemirror/autocomplete": "6.20.1",
"@codemirror/commands": "6.10.3",
"@codemirror/autocomplete": "6.20.0",
"@codemirror/commands": "6.10.2",
"@codemirror/language": "6.12.2",
"@codemirror/legacy-modes": "6.5.2",
"@codemirror/search": "6.6.0",
"@codemirror/state": "6.6.0",
"@codemirror/view": "6.40.0",
"@codemirror/state": "6.5.4",
"@codemirror/view": "6.39.15",
"@date-fns/tz": "1.4.1",
"@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "7.3.1",
"@formatjs/intl-displaynames": "7.3.1",
"@formatjs/intl-durationformat": "0.10.3",
"@formatjs/intl-getcanonicallocales": "3.2.2",
"@formatjs/intl-listformat": "8.3.1",
"@formatjs/intl-locale": "5.3.1",
"@formatjs/intl-numberformat": "9.3.1",
"@formatjs/intl-pluralrules": "6.3.1",
"@formatjs/intl-relativetimeformat": "12.3.1",
"@formatjs/intl-datetimeformat": "7.2.2",
"@formatjs/intl-displaynames": "7.2.1",
"@formatjs/intl-durationformat": "0.10.1",
"@formatjs/intl-getcanonicallocales": "3.2.1",
"@formatjs/intl-listformat": "8.2.1",
"@formatjs/intl-locale": "5.2.1",
"@formatjs/intl-numberformat": "9.2.2",
"@formatjs/intl-pluralrules": "6.2.2",
"@formatjs/intl-relativetimeformat": "12.2.2",
"@fullcalendar/core": "6.1.20",
"@fullcalendar/daygrid": "6.1.20",
"@fullcalendar/interaction": "6.1.20",
"@fullcalendar/list": "6.1.20",
"@fullcalendar/luxon3": "6.1.20",
"@fullcalendar/timegrid": "6.1.20",
"@home-assistant/webawesome": "3.3.1-ha.0",
"@home-assistant/webawesome": "3.2.1-ha.3",
"@lezer/highlight": "1.2.3",
"@lit-labs/motion": "1.1.0",
"@lit-labs/observers": "2.1.0",
@@ -72,6 +72,7 @@
"@material/mwc-list": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
"@material/mwc-radio": "0.27.0",
"@material/mwc-select": "0.27.0",
"@material/mwc-snackbar": "0.27.0",
"@material/mwc-switch": "0.27.0",
"@material/mwc-textarea": "0.27.0",
"@material/mwc-textfield": "0.27.0",
@@ -87,13 +88,14 @@
"@tsparticles/engine": "3.9.1",
"@tsparticles/preset-links": "3.2.0",
"@vibrant/color": "4.0.4",
"@vue/web-component-wrapper": "1.3.0",
"@webcomponents/scoped-custom-element-registry": "0.0.10",
"@webcomponents/webcomponentsjs": "2.8.0",
"barcode-detector": "3.1.1",
"cally": "0.9.2",
"app-datepicker": "5.1.1",
"barcode-detector": "3.1.0",
"color-name": "2.1.0",
"comlink": "4.4.2",
"core-js": "3.49.0",
"core-js": "3.48.0",
"cropperjs": "1.6.2",
"culori": "4.0.2",
"date-fns": "4.1.0",
@@ -108,7 +110,7 @@
"hls.js": "1.6.15",
"home-assistant-js-websocket": "9.6.0",
"idb-keyval": "6.2.2",
"intl-messageformat": "11.2.0",
"intl-messageformat": "11.1.2",
"js-yaml": "4.1.1",
"leaflet": "1.9.4",
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
@@ -116,7 +118,7 @@
"lit": "3.3.2",
"lit-html": "3.3.2",
"luxon": "3.7.2",
"marked": "17.0.5",
"marked": "17.0.3",
"memoize-one": "6.0.0",
"node-vibrant": "4.0.4",
"object-hash": "3.0.0",
@@ -129,6 +131,9 @@
"stacktrace-js": "2.0.2",
"superstruct": "2.0.2",
"tinykeys": "3.0.0",
"ua-parser-js": "2.0.9",
"vue": "2.7.16",
"vue2-daterange-picker": "0.6.8",
"weekstart": "2.0.0",
"workbox-cacheable-response": "7.4.0",
"workbox-core": "7.4.0",
@@ -140,17 +145,17 @@
},
"devDependencies": {
"@babel/core": "7.29.0",
"@babel/helper-define-polyfill-provider": "0.6.8",
"@babel/helper-define-polyfill-provider": "0.6.6",
"@babel/plugin-transform-runtime": "7.29.0",
"@babel/preset-env": "7.29.2",
"@bundle-stats/plugin-webpack-filter": "4.22.0",
"@html-eslint/eslint-plugin": "0.58.1",
"@babel/preset-env": "7.29.0",
"@bundle-stats/plugin-webpack-filter": "4.21.10",
"@html-eslint/eslint-plugin": "0.57.1",
"@lokalise/node-api": "15.6.1",
"@octokit/auth-oauth-device": "8.0.3",
"@octokit/plugin-retry": "8.1.0",
"@octokit/rest": "22.0.1",
"@rsdoctor/rspack-plugin": "1.5.5",
"@rspack/core": "1.7.9",
"@rsdoctor/rspack-plugin": "1.5.2",
"@rspack/core": "1.7.6",
"@rspack/dev-server": "1.2.1",
"@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.25",
@@ -168,13 +173,14 @@
"@types/qrcode": "1.5.6",
"@types/sortablejs": "1.15.9",
"@types/tar": "7.0.87",
"@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29",
"@vitest/coverage-v8": "4.1.0",
"babel-loader": "10.1.1",
"@vitest/coverage-v8": "4.0.18",
"babel-loader": "10.0.0",
"babel-plugin-template-html-minifier": "4.1.0",
"browserslist-useragent-regexp": "4.1.3",
"del": "8.0.1",
"eslint": "9.39.4",
"eslint": "9.39.3",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-prettier": "10.1.8",
"eslint-import-resolver-webpack": "0.13.10",
@@ -184,7 +190,7 @@
"eslint-plugin-unused-imports": "4.4.1",
"eslint-plugin-wc": "3.1.0",
"fancy-log": "2.0.0",
"fs-extra": "11.3.4",
"fs-extra": "11.3.3",
"glob": "13.0.6",
"gulp": "5.0.1",
"gulp-brotli": "3.0.0",
@@ -192,9 +198,9 @@
"gulp-rename": "2.1.0",
"html-minifier-terser": "7.2.0",
"husky": "9.1.7",
"jsdom": "29.0.1",
"jsdom": "28.1.0",
"jszip": "3.10.1",
"lint-staged": "16.4.0",
"lint-staged": "16.3.0",
"lit-analyzer": "2.0.3",
"lodash.merge": "4.6.2",
"lodash.template": "4.5.0",
@@ -202,32 +208,33 @@
"pinst": "3.0.0",
"prettier": "3.8.1",
"rspack-manifest-plugin": "5.2.1",
"serve": "14.2.6",
"sinon": "21.0.3",
"tar": "7.5.12",
"terser-webpack-plugin": "5.4.0",
"serve": "14.2.5",
"sinon": "21.0.1",
"tar": "7.5.9",
"terser-webpack-plugin": "5.3.16",
"ts-lit-plugin": "2.0.2",
"typescript": "5.9.3",
"typescript-eslint": "8.57.1",
"typescript-eslint": "8.56.1",
"vite-tsconfig-paths": "6.1.1",
"vitest": "4.1.0",
"vitest": "4.0.18",
"webpack-stats-plugin": "1.1.3",
"webpackbar": "7.0.0",
"workbox-build": "patch:workbox-build@npm%3A7.4.0#~/.yarn/patches/workbox-build-npm-7.4.0-c84561662c.patch"
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
},
"resolutions": {
"@material/mwc-button@^0.25.3": "^0.27.0",
"lit": "3.3.2",
"lit-html": "3.3.2",
"clean-css": "5.3.3",
"@lit/reactive-element": "2.1.2",
"@fullcalendar/daygrid": "6.1.20",
"globals": "17.4.0",
"globals": "17.3.0",
"tslib": "2.8.1",
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
"glob@^10.2.2": "^10.5.0"
},
"packageManager": "yarn@4.13.0",
"packageManager": "yarn@4.12.0",
"volta": {
"node": "24.14.1"
"node": "24.14.0"
}
}

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20260325.3"
version = "20260128.0"
license = "Apache-2.0"
license-files = ["LICENSE*"]
description = "The Home Assistant frontend"

View File

@@ -24,6 +24,11 @@
"extends": ["monorepo:material-components-web"],
"enabled": false
},
{
"description": "Vue is only used by date range which is only v2",
"matchPackageNames": ["vue"],
"allowedVersions": "< 3"
},
{
"description": "Group MDI packages",
"groupName": "Material Design Icons",

View File

@@ -1,7 +1,10 @@
/* eslint-disable lit/prefer-static-styles */
import type { TemplateResult } from "lit";
import { html } from "lit";
import { customElement } from "lit/decorators";
import { HaFormString } from "../components/ha-form/ha-form-string";
import "../components/ha-icon-button";
import "../components/input/ha-input";
import "./ha-auth-textfield";
@customElement("ha-auth-form-string")
export class HaAuthFormString extends HaFormString {
@@ -9,9 +12,63 @@ export class HaAuthFormString extends HaFormString {
return this;
}
public connectedCallback(): void {
super.connectedCallback();
this.style.position = "relative";
public reportValidity(): boolean {
return this.querySelector("ha-auth-textfield")?.reportValidity() ?? true;
}
protected render(): TemplateResult {
return html`
<style>
ha-auth-form-string {
display: block;
position: relative;
}
ha-auth-form-string[own-margin] {
margin-bottom: 5px;
}
ha-auth-form-string ha-auth-textfield {
display: block !important;
}
ha-auth-form-string ha-icon-button {
position: absolute;
top: 8px;
right: 8px;
inset-inline-start: initial;
inset-inline-end: 8px;
--ha-icon-button-size: 40px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
direction: var(--direction);
}
</style>
<ha-auth-textfield
.type=${!this.isPassword
? this.stringType
: this.unmaskedPassword
? "text"
: "password"}
.label=${this.label}
.value=${this.data || ""}
.helper=${this.helper}
helperPersistent
.disabled=${this.disabled}
.required=${this.schema.required}
.autoValidate=${this.schema.required}
.name=${this.schema.name}
.autocomplete=${this.schema.autocomplete}
?autofocus=${this.schema.autofocus}
.suffix=${this.isPassword
? // reserve some space for the icon.
html`<div style="width: 24px"></div>`
: this.schema.description?.suffix}
.validationMessage=${this.schema.required
? this.localize?.("ui.panel.page-authorize.form.error_required")
: undefined}
@input=${this._valueChanged}
@change=${this._valueChanged}
></ha-auth-textfield>
${this.renderIcon()}
`;
}
}

View File

@@ -1,9 +1,9 @@
/* eslint-disable lit/prefer-static-styles */
import { html } from "lit";
import { customElement, property } from "lit/decorators";
import type { LocalizeFunc } from "../common/translations/localize";
import { HaForm } from "../components/ha-form/ha-form";
import "./ha-auth-form-string";
import type { LocalizeFunc } from "../common/translations/localize";
const localizeBaseKey = "ui.panel.page-authorize.form";
@@ -34,9 +34,6 @@ export class HaAuthForm extends HaForm {
protected render() {
return html`
<style>
ha-auth-form {
--ha-input-required-marker: "";
}
ha-auth-form .root > * {
display: block;
}

View File

@@ -0,0 +1,264 @@
/* eslint-disable lit/value-after-constraints */
/* eslint-disable lit/prefer-static-styles */
import { floatingLabel } from "@material/mwc-floating-label/mwc-floating-label-directive";
import type { TemplateResult } from "lit";
import { html } from "lit";
import { customElement } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { live } from "lit/directives/live";
import { HaTextField } from "../components/ha-textfield";
@customElement("ha-auth-textfield")
export class HaAuthTextField extends HaTextField {
protected renderLabel(): TemplateResult | string {
return !this.label
? ""
: html`
<span
.floatingLabelFoundation=${floatingLabel(
this.label
) as unknown as any}
.id=${this.name}
>${this.label}</span
>
`;
}
protected renderInput(shouldRenderHelperText: boolean): TemplateResult {
const minOrUndef = this.minLength === -1 ? undefined : this.minLength;
const maxOrUndef = this.maxLength === -1 ? undefined : this.maxLength;
const autocapitalizeOrUndef = this.autocapitalize
? (this.autocapitalize as
| "off"
| "none"
| "on"
| "sentences"
| "words"
| "characters")
: undefined;
const showValidationMessage = this.validationMessage && !this.isUiValid;
const ariaLabelledbyOrUndef = this.label ? this.name : undefined;
const ariaControlsOrUndef = shouldRenderHelperText
? "helper-text"
: undefined;
const ariaDescribedbyOrUndef =
this.focused || this.helperPersistent || showValidationMessage
? "helper-text"
: undefined;
// TODO: live() directive needs casting for lit-analyzer
// https://github.com/runem/lit-analyzer/pull/91/files
// TODO: lit-analyzer labels min/max as (number|string) instead of string
return html`<input
aria-labelledby=${ifDefined(ariaLabelledbyOrUndef)}
aria-controls=${ifDefined(ariaControlsOrUndef)}
aria-describedby=${ifDefined(ariaDescribedbyOrUndef)}
class="mdc-text-field__input"
type=${this.type}
.value=${live(this.value) as unknown as string}
?disabled=${this.disabled}
placeholder=${this.placeholder}
?required=${this.required}
?readonly=${this.readOnly}
minlength=${ifDefined(minOrUndef)}
maxlength=${ifDefined(maxOrUndef)}
pattern=${ifDefined(this.pattern ? this.pattern : undefined)}
min=${ifDefined(this.min === "" ? undefined : (this.min as number))}
max=${ifDefined(this.max === "" ? undefined : (this.max as number))}
step=${ifDefined(this.step === null ? undefined : (this.step as number))}
size=${ifDefined(this.size === null ? undefined : this.size)}
name=${ifDefined(this.name === "" ? undefined : this.name)}
inputmode=${ifDefined(this.inputMode)}
autocapitalize=${ifDefined(autocapitalizeOrUndef)}
?autofocus=${this.autofocus}
@input=${this.handleInputChange}
@focus=${this.onInputFocus}
@blur=${this.onInputBlur}
/>`;
}
public render() {
return html`
<style>
ha-auth-textfield {
display: inline-flex;
flex-direction: column;
outline: none;
}
ha-auth-textfield:not([disabled]):hover
:not(.mdc-text-field--invalid):not(.mdc-text-field--focused)
mwc-notched-outline {
--mdc-notched-outline-border-color: var(
--mdc-text-field-outlined-hover-border-color,
rgba(0, 0, 0, 0.87)
);
}
ha-auth-textfield:not([disabled])
.mdc-text-field:not(.mdc-text-field--outlined) {
background-color: var(--mdc-text-field-fill-color, whitesmoke);
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--invalid
mwc-notched-outline {
--mdc-notched-outline-border-color: var(
--mdc-text-field-error-color,
var(--mdc-theme-error, #b00020)
);
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--invalid
+ .mdc-text-field-helper-line
.mdc-text-field-character-counter,
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--invalid
.mdc-text-field__icon {
color: var(
--mdc-text-field-error-color,
var(--mdc-theme-error, #b00020)
);
}
ha-auth-textfield:not([disabled])
.mdc-text-field:not(.mdc-text-field--invalid):not(
.mdc-text-field--focused
)
.mdc-floating-label,
ha-auth-textfield:not([disabled])
.mdc-text-field:not(.mdc-text-field--invalid):not(
.mdc-text-field--focused
)
.mdc-floating-label::after {
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--focused
mwc-notched-outline {
--mdc-notched-outline-stroke-width: 2px;
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
mwc-notched-outline {
--mdc-notched-outline-border-color: var(
--mdc-text-field-focused-label-color,
var(--mdc-theme-primary, rgba(98, 0, 238, 0.87))
);
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
.mdc-floating-label {
color: #6200ee;
color: var(--mdc-theme-primary, #6200ee);
}
ha-auth-textfield:not([disabled])
.mdc-text-field
.mdc-text-field__input {
color: var(--mdc-text-field-ink-color, rgba(0, 0, 0, 0.87));
}
ha-auth-textfield:not([disabled])
.mdc-text-field
.mdc-text-field__input::placeholder {
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
}
ha-auth-textfield:not([disabled])
.mdc-text-field-helper-line
.mdc-text-field-helper-text:not(
.mdc-text-field-helper-text--validation-msg
),
ha-auth-textfield:not([disabled])
.mdc-text-field-helper-line:not(.mdc-text-field--invalid)
.mdc-text-field-character-counter {
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
}
ha-auth-textfield[disabled]
.mdc-text-field:not(.mdc-text-field--outlined) {
background-color: var(--mdc-text-field-disabled-fill-color, #fafafa);
}
ha-auth-textfield[disabled]
.mdc-text-field.mdc-text-field--outlined
mwc-notched-outline {
--mdc-notched-outline-border-color: var(
--mdc-text-field-outlined-disabled-border-color,
rgba(0, 0, 0, 0.06)
);
}
ha-auth-textfield[disabled]
.mdc-text-field:not(.mdc-text-field--invalid):not(
.mdc-text-field--focused
)
.mdc-floating-label,
ha-auth-textfield[disabled]
.mdc-text-field:not(.mdc-text-field--invalid):not(
.mdc-text-field--focused
)
.mdc-floating-label::after {
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
}
ha-auth-textfield[disabled] .mdc-text-field .mdc-text-field__input,
ha-auth-textfield[disabled]
.mdc-text-field
.mdc-text-field__input::placeholder {
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
}
ha-auth-textfield[disabled]
.mdc-text-field-helper-line
.mdc-text-field-helper-text,
ha-auth-textfield[disabled]
.mdc-text-field-helper-line
.mdc-text-field-character-counter {
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
}
ha-auth-textfield:not([disabled])
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
.mdc-floating-label {
color: var(--mdc-theme-primary, #6200ee);
}
ha-auth-textfield[no-spinner] input::-webkit-outer-spin-button,
ha-auth-textfield[no-spinner] input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
ha-auth-textfield[no-spinner] input[type="number"] {
-moz-appearance: textfield;
}
</style>
${super.render()}
`;
}
protected createRenderRoot() {
// add parent style to light dom
const style = document.createElement("style");
style.textContent = HaTextField.elementStyles as unknown as string;
this.append(style);
return this;
}
public firstUpdated() {
super.firstUpdated();
if (this.autofocus) {
this.focus();
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-auth-textfield": HaAuthTextField;
}
}

View File

@@ -58,11 +58,9 @@ export class CastManager {
this._eventListeners[event].push(listener);
return () => {
const listeners = this._eventListeners[event];
const index = listeners.indexOf(listener);
if (index !== -1) {
listeners.splice(index, 1);
}
this._eventListeners[event].splice(
this._eventListeners[event].indexOf(listener)
);
};
}

View File

@@ -32,17 +32,9 @@ const YAML_ONLY_THEMES_COLORS = new Set([
"disabled",
]);
export function computeCssVariableName(color: string): string {
if (THEME_COLORS.has(color) || YAML_ONLY_THEMES_COLORS.has(color)) {
return `--${color}-color`;
}
return color;
}
export function computeCssColor(color: string): string {
const cssVarName = computeCssVariableName(color);
if (cssVarName !== color) {
return `var(${cssVarName})`;
if (THEME_COLORS.has(color) || YAML_ONLY_THEMES_COLORS.has(color)) {
return `var(--${color}-color)`;
}
return color;
}

View File

@@ -1,17 +1,17 @@
import {
addDays,
subHours,
endOfDay,
endOfMonth,
endOfQuarter,
endOfWeek,
endOfYear,
startOfDay,
startOfMonth,
startOfQuarter,
startOfWeek,
startOfYear,
startOfQuarter,
endOfQuarter,
subDays,
subHours,
subMonths,
} from "date-fns";
import type { HomeAssistant } from "../../types";
@@ -33,89 +33,83 @@ export type DateRange =
| "now-24h";
export const calcDateRange = (
locale: HomeAssistant["locale"],
hassConfig: HomeAssistant["config"],
hass: HomeAssistant,
range: DateRange
): [Date, Date] => {
const today = new Date();
const weekStartsOn = firstWeekdayIndex(locale);
const weekStartsOn = firstWeekdayIndex(hass.locale);
switch (range) {
case "today":
return [
calcDate(today, startOfDay, locale, hassConfig, {
calcDate(today, startOfDay, hass.locale, hass.config, {
weekStartsOn,
}),
calcDate(today, endOfDay, locale, hassConfig, {
calcDate(today, endOfDay, hass.locale, hass.config, {
weekStartsOn,
}),
];
case "yesterday":
return [
calcDate(addDays(today, -1), startOfDay, locale, hassConfig, {
calcDate(addDays(today, -1), startOfDay, hass.locale, hass.config, {
weekStartsOn,
}),
calcDate(addDays(today, -1), endOfDay, locale, hassConfig, {
calcDate(addDays(today, -1), endOfDay, hass.locale, hass.config, {
weekStartsOn,
}),
];
case "this_week":
return [
calcDate(today, startOfWeek, locale, hassConfig, {
calcDate(today, startOfWeek, hass.locale, hass.config, {
weekStartsOn,
}),
calcDate(today, endOfWeek, locale, hassConfig, {
calcDate(today, endOfWeek, hass.locale, hass.config, {
weekStartsOn,
}),
];
case "this_month":
return [
calcDate(today, startOfMonth, locale, hassConfig),
calcDate(today, endOfMonth, locale, hassConfig),
calcDate(today, startOfMonth, hass.locale, hass.config),
calcDate(today, endOfMonth, hass.locale, hass.config),
];
case "this_quarter":
return [
calcDate(today, startOfQuarter, locale, hassConfig),
calcDate(today, endOfQuarter, locale, hassConfig),
calcDate(today, startOfQuarter, hass.locale, hass.config),
calcDate(today, endOfQuarter, hass.locale, hass.config),
];
case "this_year":
return [
calcDate(today, startOfYear, locale, hassConfig),
calcDate(today, endOfYear, locale, hassConfig),
calcDate(today, startOfYear, hass.locale, hass.config),
calcDate(today, endOfYear, hass.locale, hass.config),
];
case "now-7d":
return [
calcDate(today, subDays, locale, hassConfig, 7),
calcDate(today, subDays, locale, hassConfig, 0),
calcDate(today, subDays, hass.locale, hass.config, 7),
calcDate(today, subDays, hass.locale, hass.config, 0),
];
case "now-30d":
return [
calcDate(today, subDays, locale, hassConfig, 30),
calcDate(today, subDays, locale, hassConfig, 0),
calcDate(today, subDays, hass.locale, hass.config, 30),
calcDate(today, subDays, hass.locale, hass.config, 0),
];
case "now-12m":
return [
calcDate(
today,
(date) => subMonths(startOfMonth(date), 11),
locale,
hassConfig
),
calcDate(today, endOfMonth, locale, hassConfig),
calcDate(today, subMonths, hass.locale, hass.config, 12),
calcDate(today, subMonths, hass.locale, hass.config, 0),
];
case "now-1h":
return [
calcDate(today, subHours, locale, hassConfig, 1),
calcDate(today, subHours, locale, hassConfig, 0),
calcDate(today, subHours, hass.locale, hass.config, 1),
calcDate(today, subHours, hass.locale, hass.config, 0),
];
case "now-12h":
return [
calcDate(today, subHours, locale, hassConfig, 12),
calcDate(today, subHours, locale, hassConfig, 0),
calcDate(today, subHours, hass.locale, hass.config, 12),
calcDate(today, subHours, hass.locale, hass.config, 0),
];
case "now-24h":
return [
calcDate(today, subHours, locale, hassConfig, 24),
calcDate(today, subHours, locale, hassConfig, 0),
calcDate(today, subHours, hass.locale, hass.config, 24),
calcDate(today, subHours, hass.locale, hass.config, 0),
];
}
return [today, today];

View File

@@ -166,21 +166,6 @@ const formatDateMonthMem = memoizeOne(
})
);
// Aug
export const formatDateMonthShort = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => formatDateMonthShortMem(locale, config.time_zone).format(dateObj);
const formatDateMonthShortMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat(locale.language, {
month: "short",
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
// 2021
export const formatDateYear = (
dateObj: Date,
@@ -261,36 +246,3 @@ const formatDateWeekdayShortDateMem = memoizeOne(
timeZone: resolveTimeZone(locale.time_zone, serverTimeZone),
})
);
/**
* Format a date as YYYY-MM-DD. Uses "en-CA" because it's the only
* Intl locale that natively outputs ISO 8601 date format.
* Locale/config are only used to resolve the time zone.
*/
export const formatISODateOnly = (
dateObj: Date,
locale: FrontendLocaleData,
config: HassConfig
) => {
const timeZone = resolveTimeZone(locale.time_zone, config.time_zone);
const formatter = new Intl.DateTimeFormat("en-CA", {
year: "numeric",
month: "2-digit",
day: "2-digit",
timeZone,
});
return formatter.format(dateObj);
};
// 2026-08-10/2026-08-15
export const formatCallyDateRange = (
start: Date,
end: Date,
locale: FrontendLocaleData,
config: HassConfig
) => {
const startDate = formatISODateOnly(start, locale, config);
const endDate = formatISODateOnly(end, locale, config);
return `${startDate}/${endDate}`;
};

View File

@@ -11,11 +11,11 @@ export const canOverrideAlphanumericInput = (composedPath: EventTarget[]) => {
const el = composedPath[0] as Element;
if (
el.tagName === "TEXTAREA" ||
el.parentElement?.tagName === "HA-SELECT" ||
el.parentElement?.tagName === "HA-DROPDOWN"
) {
if (el.tagName === "TEXTAREA") {
return false;
}
if (el.parentElement?.tagName === "HA-SELECT") {
return false;
}

View File

@@ -5,7 +5,6 @@ import { computeAreaName } from "./compute_area_name";
import { computeDeviceName } from "./compute_device_name";
import { computeEntityName, entityUseDeviceName } from "./compute_entity_name";
import { computeFloorName } from "./compute_floor_name";
import { computeStateName } from "./compute_state_name";
import { getEntityContext } from "./context/get_entity_context";
const DEFAULT_SEPARATOR = " ";
@@ -30,23 +29,14 @@ export interface EntityNameOptions {
export const computeEntityNameDisplay = (
stateObj: HassEntity,
name: string | EntityNameItem | EntityNameItem[] | undefined,
name: EntityNameItem | EntityNameItem[] | undefined,
entities: HomeAssistant["entities"],
devices: HomeAssistant["devices"],
areas: HomeAssistant["areas"],
floors: HomeAssistant["floors"],
options?: EntityNameOptions
) => {
if (typeof name === "string") {
return name;
}
// If no name config is provided, fall back to the friendly name
if (!name) {
return computeStateName(stateObj);
}
let items = ensureArray(name);
let items = ensureArray(name || DEFAULT_ENTITY_NAME);
const separator = options?.separator ?? DEFAULT_SEPARATOR;

View File

@@ -142,8 +142,6 @@ const computeStateToPartsFromEntityAttributes = (
group: "value",
decimal: "value",
fraction: "value",
minusSign: "value",
plusSign: "value",
literal: "literal",
currency: "unit",
};
@@ -154,7 +152,7 @@ const computeStateToPartsFromEntityAttributes = (
const type = TYPE_MAP[part.type];
if (!type) continue;
const last = valueParts[valueParts.length - 1];
// Merge consecutive value parts (e.g. "-" + "12" + "." + "00" → "-12.00")
// Merge consecutive numeric parts (e.g. "1" + "," + "234" + "." + "56" → "1,234.56")
if (type === "value" && last?.type === "value") {
last.value += part.value;
} else {
@@ -255,7 +253,6 @@ const computeStateToPartsFromEntityAttributes = (
"conversation",
"event",
"image",
"infrared",
"input_button",
"notify",
"scene",

View File

@@ -29,7 +29,6 @@ export const FIXED_DOMAIN_STATES = {
device_tracker: ["home", "not_home"],
fan: ["on", "off"],
humidifier: ["on", "off"],
infrared: [],
input_boolean: ["on", "off"],
input_button: [],
lawn_mower: ["error", "paused", "mowing", "returning", "docked"],
@@ -271,8 +270,6 @@ export const getStates = (
result.push(...state.attributes.preset_modes);
} else if (attribute === "swing_mode") {
result.push(...state.attributes.swing_modes);
} else if (attribute === "swing_horizontal_mode") {
result.push(...state.attributes.swing_horizontal_modes);
}
break;
case "device_tracker":

View File

@@ -6,9 +6,7 @@ export function stateActive(stateObj: HassEntity, state?: string): boolean {
const domain = computeDomain(stateObj.entity_id);
const compareState = state !== undefined ? state : stateObj?.state;
if (
["button", "event", "infrared", "input_button", "scene"].includes(domain)
) {
if (["button", "event", "input_button", "scene"].includes(domain)) {
return compareState !== UNAVAILABLE;
}

View File

@@ -1,11 +0,0 @@
/**
* Indicates whether the current browser supports the Popover API.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/Popover_API
*/
export const popoverSupported = globalThis?.HTMLElement?.prototype
? Object.prototype.hasOwnProperty.call(
globalThis.HTMLElement.prototype,
"popover"
)
: false;

View File

@@ -1,5 +1,24 @@
import { deepActiveElement } from "../dom/deep-active-element";
const getClipboardFallbackRoot = (): HTMLElement => {
const activeElement = deepActiveElement();
if (activeElement instanceof HTMLElement) {
let root: Node = activeElement.getRootNode();
let host: HTMLElement | null = null;
while (root instanceof ShadowRoot && root.host instanceof HTMLElement) {
host = root.host;
root = root.host.getRootNode();
}
if (host) {
return host;
}
}
return document.body;
};
export const copyToClipboard = async (str, rootEl?: HTMLElement) => {
if (navigator.clipboard) {
try {
@@ -10,7 +29,7 @@ export const copyToClipboard = async (str, rootEl?: HTMLElement) => {
}
}
const root = rootEl || deepActiveElement()?.getRootNode() || document.body;
const root = rootEl || getClipboardFallbackRoot();
const el = document.createElement("textarea");
el.value = str;

View File

@@ -1,8 +1,9 @@
// From: https://davidwalsh.name/javascript-debounce-function
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge. The trailing edge only fires if there were additional calls
// during the wait period.
// leading edge and on the trailing.
export const debounce = <T extends any[]>(
func: (...args: T) => void,
@@ -10,35 +11,20 @@ export const debounce = <T extends any[]>(
immediate = false
) => {
let timeout: number | undefined;
let trailingArgs: T | undefined;
const debouncedFunc = (...args: T): void => {
const isLeading = immediate && !timeout;
if (timeout) {
trailingArgs = args;
}
clearTimeout(timeout);
timeout = window.setTimeout(() => {
const later = () => {
timeout = undefined;
if (trailingArgs) {
func(...trailingArgs);
trailingArgs = undefined;
} else if (!immediate) {
func(...args);
}
}, wait);
if (isLeading) {
func(...args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = window.setTimeout(later, wait);
if (callNow) {
func(...args);
}
};
debouncedFunc.cancel = () => {
clearTimeout(timeout);
trailingArgs = undefined;
};
return debouncedFunc;
};

View File

@@ -5,41 +5,12 @@ import {
formatDateMonthYear,
formatDateVeryShort,
formatDateWeekdayShort,
formatDateYear,
} from "../../common/datetime/format_date";
import {
formatTime,
formatTimeWithSeconds,
} from "../../common/datetime/format_time";
export function getPeriodicAxisLabelConfig(
period: string,
locale: FrontendLocaleData,
config: HassConfig
):
| {
formatter: (value: number) => string;
}
| undefined {
if (period === "month") {
return {
formatter: (value: number) => {
const date = new Date(value);
return date.getMonth() === 0
? `{bold|${formatDateMonthYear(date, locale, config)}}`
: formatDateMonth(date, locale, config);
},
};
}
if (period === "year") {
return {
formatter: (value: number) =>
formatDateYear(new Date(value), locale, config),
};
}
return undefined;
}
export function formatTimeLabel(
value: number | Date,
locale: FrontendLocaleData,

View File

@@ -44,7 +44,6 @@ export type CustomLegendOption = ECOption["legend"] & {
id?: string;
secondaryIds?: string[]; // Other dataset IDs that should be controlled by this legend item.
name: string;
value?: string; // Current value to display next to the name in the legend.
itemStyle?: Record<string, any>;
}[];
};
@@ -280,23 +279,18 @@ export class HaChartBase extends LitElement {
<div class="chart"></div>
</div>
${this._renderLegend()}
<div class="top-controls ${classMap({ small: this.smallControls })}">
<slot name="search"></slot>
<div
class="chart-controls ${classMap({ small: this.smallControls })}"
>
${this._isZoomed && !this.hideResetButton
? html`<ha-icon-button
class="zoom-reset"
.path=${mdiRestart}
@click=${this._handleZoomReset}
title=${this.hass.localize(
"ui.components.history_charts.zoom_reset"
)}
></ha-icon-button>`
: nothing}
<slot name="button"></slot>
</div>
<div class="chart-controls ${classMap({ small: this.smallControls })}">
${this._isZoomed && !this.hideResetButton
? html`<ha-icon-button
class="zoom-reset"
.path=${mdiRestart}
@click=${this._handleZoomReset}
title=${this.hass.localize(
"ui.components.history_charts.zoom_reset"
)}
></ha-icon-button>`
: nothing}
<slot name="button"></slot>
</div>
</div>
`;
@@ -339,14 +333,12 @@ export class HaChartBase extends LitElement {
let itemStyle: Record<string, any> = {};
let name = "";
let id = "";
let value = "";
if (typeof item === "string") {
name = item;
id = item;
} else {
name = item.name ?? "";
id = item.id ?? name;
value = item.value ?? "";
itemStyle = item.itemStyle ?? {};
}
const dataset =
@@ -373,7 +365,6 @@ export class HaChartBase extends LitElement {
})}
></div>
<div class="label">${name}</div>
${value ? html`<div class="value">${value}</div>` : nothing}
</li>`;
})}
${items.length > overflowLimit
@@ -587,13 +578,7 @@ export class HaChartBase extends LitElement {
id: "dataZoom",
type: "inside",
orient: "horizontal",
// "boundaryFilter" is a custom mode added via axis-proxy-patch.ts.
// It rescales the Y-axis to the visible data while keeping one point
// just outside each boundary to avoid line gaps at the zoom edges.
// Only use it for line charts — it causes issues with bar charts.
filterMode: (ensureArray(this.data).every((s) => s.type === "line")
? "boundaryFilter"
: "filter") as any,
filterMode: "none",
xAxisIndex: 0,
moveOnMouseMove: !this._isTouchDevice || this._isZoomed,
preventDefaultMouseMove: !this._isTouchDevice || this._isZoomed,
@@ -635,7 +620,7 @@ export class HaChartBase extends LitElement {
hideOverlap: true,
...axis.axisLabel,
},
minInterval: axis.minInterval ?? minInterval,
minInterval,
} as XAXisOption;
});
}
@@ -892,20 +877,10 @@ export class HaChartBase extends LitElement {
};
}
if (s.sampling === "minmax") {
const minX = xAxis?.min
? xAxis.min instanceof Date
? xAxis.min.getTime()
: typeof xAxis.min === "number"
? xAxis.min
: undefined
: undefined;
const maxX = xAxis?.max
? xAxis.max instanceof Date
? xAxis.max.getTime()
: typeof xAxis.max === "number"
? xAxis.max
: undefined
: undefined;
const minX =
xAxis?.min && typeof xAxis.min === "number" ? xAxis.min : undefined;
const maxX =
xAxis?.max && typeof xAxis.max === "number" ? xAxis.max : undefined;
return {
...s,
sampling: undefined,
@@ -1124,35 +1099,16 @@ export class HaChartBase extends LitElement {
height: 100%;
width: 100%;
}
.top-controls {
position: absolute;
top: var(--ha-space-4);
inset-inline-start: var(--ha-space-4);
inset-inline-end: var(--ha-space-1);
display: flex;
align-items: flex-start;
gap: var(--ha-space-2);
z-index: 1;
pointer-events: none;
}
::slotted([slot="search"]) {
flex: 1 1 250px;
min-width: 0;
max-width: 250px;
pointer-events: auto;
}
.chart-controls {
position: absolute;
top: 16px;
right: 4px;
display: flex;
flex-direction: column;
gap: var(--ha-space-1);
margin-inline-start: auto;
flex-shrink: 0;
pointer-events: auto;
}
.top-controls.small {
top: 0;
}
.chart-controls.small {
top: 0;
flex-direction: row;
}
.chart-controls ha-icon-button,
@@ -1200,9 +1156,6 @@ export class HaChartBase extends LitElement {
.chart-legend.multiple-items li {
max-width: 220px;
}
.chart-legend.multiple-items li:has(.value) {
max-width: 300px;
}
.chart-legend .hidden {
color: var(--secondary-text-color);
}
@@ -1211,12 +1164,6 @@ export class HaChartBase extends LitElement {
white-space: nowrap;
overflow: hidden;
}
.chart-legend .value {
color: var(--secondary-text-color);
margin-inline-start: var(--ha-space-1);
flex-shrink: 0;
white-space: nowrap;
}
.chart-legend .bullet {
border-width: 1px;
border-style: solid;

View File

@@ -1,9 +1,7 @@
import type { EChartsType } from "echarts/core";
import type { GraphSeriesOption } from "echarts/charts";
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state, query } from "lit/decorators";
import type {
CallbackDataParams,
TopLevelFormatterParams,
@@ -65,8 +63,6 @@ export interface NetworkData {
categories?: { name: string; symbol: string }[];
}
const PHYSICS_DISABLE_THRESHOLD = 512;
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/consistent-type-imports
let GraphChart: typeof import("echarts/lib/chart/graph/install");
@@ -80,23 +76,11 @@ export class HaNetworkGraph extends SubscribeMixin(LitElement) {
params: TopLevelFormatterParams
) => string;
/**
* Optional callback that returns additional searchable strings for a node.
* These are matched against the search filter in addition to the node's name and context.
*/
@property({ attribute: false }) public searchableAttributes?: (
nodeId: string
) => string[];
@property({ attribute: false }) public searchFilter = "";
public hass!: HomeAssistant;
@state() private _highlightedNodes?: Set<string>;
@state() private _reducedMotion = false;
@state() private _physicsEnabled?: boolean;
@state() private _physicsEnabled = true;
@state() private _showLabels = true;
@@ -124,14 +108,6 @@ export class HaNetworkGraph extends SubscribeMixin(LitElement) {
];
}
protected willUpdate(changedProperties: PropertyValues): void {
super.willUpdate(changedProperties);
if (this._physicsEnabled === undefined && this.data?.nodes?.length > 1) {
this._physicsEnabled =
this.data.nodes.length <= PHYSICS_DISABLE_THRESHOLD;
}
}
protected render() {
if (!GraphChart || !this.data.nodes?.length) {
return nothing;
@@ -141,24 +117,19 @@ export class HaNetworkGraph extends SubscribeMixin(LitElement) {
"all and (max-width: 450px), all and (max-height: 500px)"
).matches;
const hasHighlightedNodes =
this._highlightedNodes && this._highlightedNodes.size > 0;
return html`<ha-chart-base
.hass=${this.hass}
.data=${this._getSeries(
this.data,
this._physicsEnabled ?? false,
this._physicsEnabled,
this._reducedMotion,
this._showLabels,
isMobile,
hasHighlightedNodes
isMobile
)}
.options=${this._createOptions(this.data?.categories)}
height="100%"
.extraComponents=${[GraphChart]}
>
<slot name="search" slot="search"></slot>
<slot name="button" slot="button"></slot>
<ha-icon-button
slot="button"
@@ -194,7 +165,7 @@ export class HaNetworkGraph extends SubscribeMixin(LitElement) {
...category,
icon: category.symbol,
})),
bottom: 8,
top: 8,
},
dataZoom: {
type: "inside",
@@ -204,56 +175,13 @@ export class HaNetworkGraph extends SubscribeMixin(LitElement) {
deepEqual
);
protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
if (changedProperties.has("searchFilter")) {
const filter = this.searchFilter;
if (!filter) {
this._highlightedNodes = undefined;
} else {
const lowerFilter = filter.toLowerCase();
const matchingIds = new Set<string>();
for (const node of this.data.nodes) {
if (this._nodeMatchesFilter(node, lowerFilter)) {
matchingIds.add(node.id);
}
}
this._highlightedNodes = matchingIds;
}
this._applyHighlighting();
this._updateMouseoverHandler();
}
}
private _nodeMatchesFilter(node: NetworkNode, lowerFilter: string): boolean {
if (node.name?.toLowerCase().includes(lowerFilter)) {
return true;
}
if (node.context?.toLowerCase().includes(lowerFilter)) {
return true;
}
if (node.id?.toLowerCase().includes(lowerFilter)) {
return true;
}
if (this.searchableAttributes) {
const extraValues = this.searchableAttributes(node.id);
for (const value of extraValues) {
if (value?.toLowerCase().includes(lowerFilter)) {
return true;
}
}
}
return false;
}
private _getSeries = memoizeOne(
(
data: NetworkData,
physicsEnabled: boolean,
reducedMotion: boolean,
showLabels: boolean,
isMobile: boolean,
hasHighlightedNodes?: boolean
isMobile: boolean
) => ({
id: "network",
type: "graph",
@@ -286,7 +214,7 @@ export class HaNetworkGraph extends SubscribeMixin(LitElement) {
},
},
emphasis: {
focus: hasHighlightedNodes ? "self" : isMobile ? "none" : "adjacency",
focus: isMobile ? "none" : "adjacency",
},
force: {
repulsion: [400, 600],
@@ -434,68 +362,6 @@ export class HaNetworkGraph extends SubscribeMixin(LitElement) {
});
}
private _applyHighlighting() {
const chart = this._baseChart?.chart;
if (!chart) {
return;
}
// Reset all nodes to normal opacity first
chart.dispatchAction({ type: "downplay" });
const highlighted = this._highlightedNodes;
if (!highlighted || highlighted.size === 0) {
return;
}
const dataIndices: number[] = [];
this.data.nodes.forEach((node, index) => {
if (highlighted.has(node.id)) {
dataIndices.push(index);
}
});
if (dataIndices.length > 0) {
chart.dispatchAction({ type: "highlight", dataIndex: dataIndices });
}
}
private _emphasisGuardHandler?: () => void;
private _updateMouseoverHandler() {
const chart = this._baseChart?.chart;
if (!chart) {
return;
}
// When there are highlighted nodes, re-apply highlighting on hover
// and mouseout to prevent hover from overriding the search state
if (this._highlightedNodes && this._highlightedNodes.size > 0) {
if (this._emphasisGuardHandler) {
// Guard already set
return;
}
this._emphasisGuardHandler = () => {
this._applyHighlighting();
};
chart.on("mouseover", this._emphasisGuardHandler);
chart.on("mouseout", this._emphasisGuardHandler);
} else {
if (!this._emphasisGuardHandler) {
return;
}
chart.off("mouseover", this._emphasisGuardHandler);
chart.off("mouseout", this._emphasisGuardHandler);
this._emphasisGuardHandler = undefined;
}
}
public disconnectedCallback(): void {
super.disconnectedCallback();
if (this._emphasisGuardHandler) {
this._baseChart?.chart?.off("mouseover", this._emphasisGuardHandler);
this._baseChart?.chart?.off("mouseout", this._emphasisGuardHandler);
this._emphasisGuardHandler = undefined;
}
}
private _togglePhysics() {
this._saveNodePositions();
this._physicsEnabled = !this._physicsEnabled;

View File

@@ -239,9 +239,7 @@ export class StateHistoryChartLine extends LitElement {
changedProps.has("fitYData") ||
changedProps.has("paddingYAxis") ||
changedProps.has("_visualMap") ||
changedProps.has("_yWidth") ||
(changedProps.has("hass") &&
this._hasEntityStatesChanged(changedProps.get("hass")))
changedProps.has("_yWidth")
) {
const rtl = computeRTL(this.hass);
let minYAxis: number | ((values: { min: number }) => number) | undefined =
@@ -298,19 +296,6 @@ export class StateHistoryChartLine extends LitElement {
legend: {
type: "custom",
show: this.showNames,
data: this._chartData
.map((d, i) => ({ dataset: d, entityId: this._entityIds[i] }))
.filter((item) => !(item.dataset as LineSeriesOption).areaStyle)
.map((item) => {
const stateObj = this.hass.states[item.entityId];
return {
id: item.dataset.id as string,
name: item.dataset.name as string,
value: stateObj
? this.hass.formatEntityState(stateObj)
: undefined,
};
}),
},
grid: {
top: 15,
@@ -331,13 +316,6 @@ export class StateHistoryChartLine extends LitElement {
}
}
private _hasEntityStatesChanged(oldHass: HomeAssistant): boolean {
return this._entityIds.some(
(entityId) =>
this.hass.states[entityId]?.state !== oldHass.states[entityId]?.state
);
}
private _generateData() {
let colorIndex = 0;
const computedStyles = getComputedStyle(this);

View File

@@ -27,12 +27,10 @@ import {
getDisplayUnit,
getStatisticLabel,
getStatisticMetadata,
isExternalStatistic,
statisticsHaveType,
} from "../../data/recorder";
import type { ECOption } from "../../resources/echarts/echarts";
import type { HomeAssistant } from "../../types";
import { getPeriodicAxisLabelConfig } from "./axis-label";
import type { CustomLegendOption } from "./ha-chart-base";
import "./ha-chart-base";
@@ -294,22 +292,6 @@ export class StatisticsChart extends LitElement {
type: "time",
min: startTime,
max: this.endTime,
...(this.period === "month" && {
minInterval: 28 * 24 * 3600 * 1000,
axisLabel: getPeriodicAxisLabelConfig(
"month",
this.hass.locale,
this.hass.config
),
}),
...(this.period === "year" && {
minInterval: 365 * 24 * 3600 * 1000,
axisLabel: getPeriodicAxisLabelConfig(
"year",
this.hass.locale,
this.hass.config
),
}),
},
{
id: "hiddenAxis",
@@ -416,31 +398,7 @@ export class StatisticsChart extends LitElement {
endTime = new Date();
}
// Check if we need to display most recent data. Allow 10m of leeway for "now",
// because stats are 5 minute aggregated.
// Use same now point for all statistics even if processing time means the
// state value is actually from a slightly later time. Otherwise the points
// end up separated slightly and disappear from the tooltips.
const now = new Date();
const displayCurrentState = now.getTime() - endTime.getTime() <= 600000;
// Try to determine chart unit if it has not already been set explicitly
if (!this.unit) {
let unit: string | undefined | null;
statisticsData.forEach(([statistic_id, _stats]) => {
const meta = statisticsMetaData?.[statistic_id];
const statisticUnit = getDisplayUnit(this.hass, statistic_id, meta);
if (unit === undefined) {
unit = statisticUnit;
} else if (unit !== null && unit !== statisticUnit) {
// Clear unit if not all statistics have same unit
unit = null;
}
});
if (unit) {
this.unit = unit;
}
}
let unit: string | undefined | null;
const names = this.names || {};
statisticsData.forEach(([statistic_id, stats]) => {
@@ -450,6 +408,18 @@ export class StatisticsChart extends LitElement {
name = getStatisticLabel(this.hass, statistic_id, meta);
}
if (!this.unit) {
if (unit === undefined) {
unit = getDisplayUnit(this.hass, statistic_id, meta);
} else if (
unit !== null &&
unit !== getDisplayUnit(this.hass, statistic_id, meta)
) {
// Clear unit if not all statistics have same unit
unit = null;
}
}
// array containing [value1, value2, etc]
let prevValues: (number | null)[][] | null = null;
let prevEndTime: Date | undefined;
@@ -537,6 +507,7 @@ export class StatisticsChart extends LitElement {
id: `${statistic_id}-${type}`,
type: this.chartType,
smooth: this.chartType === "line" ? 0.4 : false,
smoothMonotone: "x",
cursor: "default",
data: [],
name: name
@@ -573,7 +544,7 @@ export class StatisticsChart extends LitElement {
(series as LineSeriesOption).areaStyle = undefined;
} else {
series.stackOrder = "seriesAsc";
if (type === bandTop) {
if (drawBands && type === bandTop) {
(series as LineSeriesOption).areaStyle = {
color: color + "3F",
};
@@ -642,10 +613,10 @@ export class StatisticsChart extends LitElement {
}
});
// For line charts, close out the last stat segment at prevEndTime
// Close out the last stat segment at prevEndTime
const lastEndTime = prevEndTime;
const lastValues = prevValues;
if (this.chartType === "line" && lastEndTime && lastValues) {
if (lastEndTime && lastValues) {
statDataSets.forEach((d, i) => {
d.data!.push(
this._transformDataValue([lastEndTime, ...lastValues[i]!])
@@ -653,14 +624,13 @@ export class StatisticsChart extends LitElement {
});
}
// Show current state if required, and units match (or are unknown)
const statisticUnit = getDisplayUnit(this.hass, statistic_id, meta);
if (
displayCurrentState &&
(!this.unit || !statisticUnit || this.unit === statisticUnit)
) {
// Skip external statistics
if (!isExternalStatistic(statistic_id)) {
// Append current state if viewing recent data
const now = new Date();
// allow 10m of leeway for "now", because stats are 5 minute aggregated
const isUpToNow = now.getTime() - endTime.getTime() <= 600000;
if (isUpToNow) {
// Skip external statistics (they have ":" in the ID)
if (!statistic_id.includes(":")) {
const stateObj = this.hass.states[statistic_id];
if (stateObj) {
const currentValue = parseFloat(stateObj.state);
@@ -670,12 +640,11 @@ export class StatisticsChart extends LitElement {
) {
// Then push the current state at now
statTypes.forEach((type, i) => {
if (type === "sum" || type === "change") {
// Skip cumulative types - need special calculation.
return;
}
const val: (number | null)[] = [];
if (
if (type === "sum" || type === "change") {
// Skip cumulative types - need special calculation
val.push(null);
} else if (
type === bandTop &&
this.chartType === "line" &&
drawBands &&
@@ -701,6 +670,10 @@ export class StatisticsChart extends LitElement {
Array.prototype.push.apply(legendData, statLegendData);
});
if (unit) {
this.unit = unit;
}
legendData.forEach(({ id, name, color, borderColor }) => {
// Add an empty series for the legend
totalDataSets.push({

View File

@@ -27,7 +27,7 @@ import type { HomeAssistant } from "../../types";
import "../ha-checkbox";
import type { HaCheckbox } from "../ha-checkbox";
import "../ha-svg-icon";
import "../input/ha-input-search";
import "../search-input";
import { filterData, sortData } from "./sort-filter";
export interface RowClickedEvent {
@@ -391,11 +391,11 @@ export class HaDataTable extends LitElement {
${this._filterable
? html`
<div class="table-header">
<ha-input-search
appearance="outlined"
@input=${this._handleSearchChange}
.placeholder=${this.searchLabel}
></ha-input-search>
<search-input
.hass=${this.hass}
@value-changed=${this._handleSearchChange}
.label=${this.searchLabel}
></search-input>
</div>
`
: ""}
@@ -970,12 +970,12 @@ export class HaDataTable extends LitElement {
});
}
private _handleSearchChange(ev: InputEvent): void {
private _handleSearchChange(ev: CustomEvent): void {
if (this.filter) {
return;
}
this._lastSelectedRowId = null;
this._debounceSearch((ev.target as HTMLInputElement).value);
this._debounceSearch(ev.detail.value);
}
private async _calcTableHeight() {
@@ -1388,9 +1388,11 @@ export class HaDataTable extends LitElement {
.table-header {
border-bottom: 1px solid var(--divider-color);
}
ha-input-search {
search-input {
display: block;
flex: 1;
padding: var(--ha-space-3);
--mdc-text-field-fill-color: var(--sidebar-background-color);
--mdc-text-field-idle-line-color: transparent;
}
slot[name="header"] {
display: block;

View File

@@ -1,417 +0,0 @@
import { TZDate } from "@date-fns/tz";
import { consume, type ContextType } from "@lit/context";
import type { ActionDetail } from "@material/mwc-list";
import { mdiCalendarToday } from "@mdi/js";
import "cally";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, queryAll, state } from "lit/decorators";
import { firstWeekdayIndex } from "../../common/datetime/first_weekday";
import {
formatCallyDateRange,
formatDateMonth,
formatDateYear,
formatISODateOnly,
} from "../../common/datetime/format_date";
import { fireEvent } from "../../common/dom/fire_event";
import {
configContext,
localeContext,
localizeContext,
} from "../../data/context";
import { TimeZone } from "../../data/translation";
import { MobileAwareMixin } from "../../mixins/mobile-aware-mixin";
import { haStyleScrollbar } from "../../resources/styles";
import type { ValueChangedEvent } from "../../types";
import "../chips/ha-chip-set";
import "../chips/ha-filter-chip";
import type { HaFilterChip } from "../chips/ha-filter-chip";
import type { HaBaseTimeInput } from "../ha-base-time-input";
import "../ha-icon-button";
import "../ha-icon-button-next";
import "../ha-icon-button-prev";
import "../ha-list";
import "../ha-list-item";
import "../ha-time-input";
import type { HaTimeInput } from "../ha-time-input";
import type { DateRangePickerRanges } from "./ha-date-range-picker";
import { datePickerStyles, dateRangePickerStyles } from "./styles";
@customElement("date-range-picker")
export class DateRangePicker extends MobileAwareMixin(LitElement) {
@property({ attribute: false }) public ranges?: DateRangePickerRanges | false;
@property({ attribute: false }) public startDate?: Date;
@property({ attribute: false }) public endDate?: Date;
@property({ attribute: "time-picker", type: Boolean })
public timePicker = false;
@state()
@consume({ context: localizeContext, subscribe: true })
private localize!: ContextType<typeof localizeContext>;
@state()
@consume({ context: localeContext, subscribe: true })
private locale!: ContextType<typeof localeContext>;
@state()
@consume({ context: configContext, subscribe: true })
private hassConfig!: ContextType<typeof configContext>;
/** used to show month in calendar-range header */
@state() private _pickerMonth?: string;
/** used to show year in calendar-date header */
@state() private _pickerYear?: string;
/** used for today to navigate focus in calendar-range */
@state() private _focusDate?: string;
@state() private _dateValue?: string;
@state() private _timeValue = {
from: { hours: 0, minutes: 0 },
to: { hours: 23, minutes: 59 },
};
@queryAll("ha-time-input") private _timeInputs?: NodeListOf<HaTimeInput>;
public connectedCallback() {
super.connectedCallback();
const date = this.startDate || new Date();
this._dateValue =
this.startDate && this.endDate
? formatCallyDateRange(
this.startDate,
this.endDate,
this.locale,
this.hassConfig
)
: undefined;
this._pickerMonth = formatDateMonth(date, this.locale, this.hassConfig);
this._pickerYear = formatDateYear(date, this.locale, this.hassConfig);
if (this.timePicker && this.startDate && this.endDate) {
this._timeValue = {
from: {
hours: this.startDate.getHours(),
minutes: this.startDate.getMinutes(),
},
to: {
hours: this.endDate.getHours(),
minutes: this.endDate.getMinutes(),
},
};
}
}
private _renderRanges() {
if (this._isMobileSize) {
return html`
<ha-chip-set class="ha-scrollbar">
${Object.entries(this.ranges!).map(
([name, range], index) => html`
<ha-filter-chip
.index=${index}
.range=${range}
@click=${this._clickDateRangeChip}
>
${name}
</ha-filter-chip>
`
)}
</ha-chip-set>
`;
}
return html`
<ha-list @action=${this._setDateRange} activatable>
${Object.keys(this.ranges!).map(
(name) => html`<ha-list-item>${name}</ha-list-item>`
)}
</ha-list>
`;
}
render() {
return html`<div class="picker">
${this.ranges !== false && this.ranges
? html`<div class="date-range-ranges">${this._renderRanges()}</div>`
: nothing}
<div class="range">
<calendar-range
.value=${this._dateValue}
.locale=${this.locale.language}
.focusedDate=${this._focusDate}
@focusday=${this._focusChanged}
@change=${this._handleChange}
show-outside-days
.firstDayOfWeek=${firstWeekdayIndex(this.locale)}
>
<ha-icon-button-prev
tabindex="-1"
slot="previous"
></ha-icon-button-prev>
<div class="heading" slot="heading">
<span class="month-year"
>${this._pickerMonth} ${this._pickerYear}</span
>
<ha-icon-button
@click=${this._focusToday}
.path=${mdiCalendarToday}
.label=${this.localize("ui.dialogs.date-picker.today")}
></ha-icon-button>
</div>
<ha-icon-button-next
tabindex="-1"
slot="next"
></ha-icon-button-next>
<calendar-month></calendar-month>
</calendar-range>
${this.timePicker
? html`
<div class="times">
<ha-time-input
.value=${`${this._timeValue.from.hours}:${this._timeValue.from.minutes}`}
.locale=${this.locale}
@value-changed=${this._handleChangeTime}
.label=${this.localize(
"ui.components.date-range-picker.time_from"
)}
id="from"
placeholder-labels
auto-validate
></ha-time-input>
<ha-time-input
.value=${`${this._timeValue.to.hours}:${this._timeValue.to.minutes}`}
.locale=${this.locale}
@value-changed=${this._handleChangeTime}
.label=${this.localize(
"ui.components.date-range-picker.time_to"
)}
id="to"
placeholder-labels
auto-validate
></ha-time-input>
</div>
`
: nothing}
</div>
</div>
<div class="footer">
<ha-button appearance="plain" @click=${this._cancel}
>${this.localize("ui.common.cancel")}</ha-button
>
<ha-button .disabled=${!this._dateValue} @click=${this._save}
>${this.localize("ui.components.date-range-picker.select")}</ha-button
>
</div>`;
}
private _focusToday() {
const date = new Date();
this._focusDate = formatISODateOnly(date, this.locale, this.hassConfig);
this._pickerMonth = formatDateMonth(date, this.locale, this.hassConfig);
this._pickerYear = formatDateYear(date, this.locale, this.hassConfig);
}
private _cancel() {
fireEvent(this, "cancel-date-picker");
}
private _save() {
if (!this._dateValue) {
return;
}
const dates = this._dateValue.split("/");
let startDate = new Date(`${dates[0]}T00:00:00`);
let endDate = new Date(`${dates[1]}T23:59:00`);
if (this.timePicker) {
const timeInputs = this._timeInputs;
if (
timeInputs &&
![...timeInputs].every((input) => input.reportValidity())
) {
// If we have time inputs, and they don't all report valid, don't save
return;
}
startDate.setHours(this._timeValue.from.hours);
startDate.setMinutes(this._timeValue.from.minutes);
endDate.setHours(this._timeValue.to.hours);
endDate.setMinutes(this._timeValue.to.minutes);
startDate.setSeconds(0);
startDate.setMilliseconds(0);
endDate.setSeconds(0);
endDate.setMilliseconds(0);
if (endDate <= startDate) {
endDate.setDate(startDate.getDate() + 1);
}
}
if (this.locale.time_zone === TimeZone.server) {
startDate = new Date(
new TZDate(startDate, this.hassConfig.time_zone).getTime()
);
endDate = new Date(
new TZDate(endDate, this.hassConfig.time_zone).getTime()
);
}
if (
startDate.getHours() !== this._timeValue.from.hours ||
startDate.getMinutes() !== this._timeValue.from.minutes ||
endDate.getHours() !== this._timeValue.to.hours ||
endDate.getMinutes() !== this._timeValue.to.minutes
) {
this._timeValue.from.hours = startDate.getHours();
this._timeValue.from.minutes = startDate.getMinutes();
this._timeValue.to.hours = endDate.getHours();
this._timeValue.to.minutes = endDate.getMinutes();
}
fireEvent(this, "value-changed", {
value: {
startDate,
endDate,
},
});
}
private _focusChanged(ev: CustomEvent<Date>) {
const date = ev.detail;
this._pickerMonth = formatDateMonth(date, this.locale, this.hassConfig);
this._pickerYear = formatDateYear(date, this.locale, this.hassConfig);
this._focusDate = undefined;
}
private _handleChange(ev: CustomEvent) {
const dateElement = ev.target as HTMLElementTagNameMap["calendar-range"];
this._dateValue = dateElement.value;
this._focusDate = undefined;
}
private _clickDateRangeChip(ev: Event) {
const chip = ev.target as HaFilterChip & {
index: number;
range: [Date, Date];
};
this._saveDateRangePreset(chip.range, chip.index);
}
private _setDateRange(ev: CustomEvent<ActionDetail>) {
const dateRange: [Date, Date] = Object.values(this.ranges!)[
ev.detail.index
];
this._saveDateRangePreset(dateRange, ev.detail.index);
}
private _saveDateRangePreset(range: [Date, Date], index: number) {
fireEvent(this, "value-changed", {
value: {
startDate: range[0],
endDate: range[1],
},
});
fireEvent(this, "preset-selected", {
index,
});
}
private _handleChangeTime(ev: ValueChangedEvent<string>) {
ev.stopPropagation();
const time = ev.detail.value;
const target = ev.target as HaBaseTimeInput;
const type = target.id;
if (time) {
if (!this._timeValue) {
this._timeValue = {
from: { hours: 0, minutes: 0 },
to: { hours: 23, minutes: 59 },
};
}
const [hours, minutes] = time.split(":").map(Number);
this._timeValue[type].hours = hours;
this._timeValue[type].minutes = minutes;
}
}
static styles = [
datePickerStyles,
dateRangePickerStyles,
haStyleScrollbar,
css`
.picker {
display: flex;
flex-direction: row;
}
.date-range-ranges {
border-right: var(--ha-border-width-sm) solid var(--divider-color);
min-width: 140px;
flex: 0 1 30%;
}
.range {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
padding: var(--ha-space-3);
overflow-x: hidden;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
.picker {
flex-direction: column;
}
.date-range-ranges {
border-bottom: 1px solid var(--divider-color);
margin-top: var(--ha-space-5);
overflow: visible;
}
ha-chip-set {
padding: var(--ha-space-3);
flex-wrap: nowrap;
overflow-x: auto;
}
.range {
flex-basis: fit-content;
}
}
.times {
display: flex;
flex-direction: column;
gap: var(--ha-space-2);
}
.footer {
display: flex;
justify-content: flex-end;
padding: var(--ha-space-2);
border-top: 1px solid var(--divider-color);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"date-range-picker": DateRangePicker;
}
interface HASSDomEvents {
"cancel-date-picker": undefined;
"preset-selected": { index: number };
}
}

View File

@@ -1,406 +0,0 @@
import "@home-assistant/webawesome/dist/components/popover/popover";
import { consume, type ContextType } from "@lit/context";
import { mdiCalendar } from "@mdi/js";
import "cally";
import { isThisYear } from "date-fns";
import type { TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { tinykeys } from "tinykeys";
import { shiftDateRange } from "../../common/datetime/calc_date";
import type { DateRange } from "../../common/datetime/calc_date_range";
import { calcDateRange } from "../../common/datetime/calc_date_range";
import {
formatShortDateTime,
formatShortDateTimeWithYear,
} from "../../common/datetime/format_date_time";
import { fireEvent } from "../../common/dom/fire_event";
import {
configContext,
localeContext,
localizeContext,
} from "../../data/context";
import "../ha-bottom-sheet";
import "../ha-icon-button";
import "../ha-icon-button-next";
import "../ha-icon-button-prev";
import "../ha-textarea";
import "./date-range-picker";
export type DateRangePickerRanges = Record<string, [Date, Date]>;
const RANGE_KEYS: DateRange[] = ["today", "yesterday", "this_week"];
const EXTENDED_RANGE_KEYS: DateRange[] = [
"this_month",
"this_year",
"now-1h",
"now-12h",
"now-24h",
"now-7d",
"now-30d",
];
@customElement("ha-date-range-picker")
export class HaDateRangePicker extends LitElement {
@state()
@consume({ context: localizeContext, subscribe: true })
private localize!: ContextType<typeof localizeContext>;
@state()
@consume({ context: localeContext, subscribe: true })
private locale!: ContextType<typeof localeContext>;
@state()
@consume({ context: configContext, subscribe: true })
private hassConfig!: ContextType<typeof configContext>;
@property({ attribute: false }) public startDate!: Date;
@property({ attribute: false }) public endDate!: Date;
@property({ attribute: false }) public ranges?: DateRangePickerRanges | false;
@state() private _ranges?: DateRangePickerRanges;
@property({ attribute: "time-picker", type: Boolean })
public timePicker = false;
@property({ type: Boolean, reflect: true })
public backdrop = false;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public minimal = false;
@property({ attribute: "extended-presets", type: Boolean })
public extendedPresets = false;
@property({ attribute: "popover-placement" })
public popoverPlacement:
| "bottom"
| "top"
| "left"
| "right"
| "top-start"
| "top-end"
| "right-start"
| "right-end"
| "bottom-start"
| "bottom-end"
| "left-start"
| "left-end" = "bottom-start";
@state() private _opened = false;
@state() private _pickerWrapperOpen = false;
@state() private _openedNarrow = false;
@state() private _popoverWidth = 0;
@query(".container") private _containerElement?: HTMLDivElement;
private _narrow = false;
private _unsubscribeTinyKeys?: () => void;
public connectedCallback() {
super.connectedCallback();
this._handleResize();
window.addEventListener("resize", this._handleResize);
const rangeKeys = this.extendedPresets
? [...RANGE_KEYS, ...EXTENDED_RANGE_KEYS]
: RANGE_KEYS;
this._ranges = {};
rangeKeys.forEach((key) => {
this._ranges![
this.localize(`ui.components.date-range-picker.ranges.${key}`)
] = calcDateRange(this.locale, this.hassConfig, key);
});
}
public open(): void {
this._openPicker();
}
protected render(): TemplateResult {
return html`
<div class="container">
<div class="date-range-inputs">
${!this.minimal
? html`<ha-textarea
id="field"
mobile-multiline
@click=${this._openPicker}
@keydown=${this._handleKeydown}
.value=${(isThisYear(this.startDate)
? formatShortDateTime(
this.startDate,
this.locale,
this.hassConfig
)
: formatShortDateTimeWithYear(
this.startDate,
this.locale,
this.hassConfig
)) +
(window.innerWidth >= 459 ? " - " : " - \n") +
(isThisYear(this.endDate)
? formatShortDateTime(
this.endDate,
this.locale,
this.hassConfig
)
: formatShortDateTimeWithYear(
this.endDate,
this.locale,
this.hassConfig
))}
.label=${this.localize(
"ui.components.date-range-picker.start_date"
) +
" - " +
this.localize("ui.components.date-range-picker.end_date")}
.disabled=${this.disabled}
readonly
></ha-textarea>
<ha-icon-button-prev
.label=${this.localize("ui.common.previous")}
@click=${this._handlePrev}
>
</ha-icon-button-prev>
<ha-icon-button-next
.label=${this.localize("ui.common.next")}
@click=${this._handleNext}
>
</ha-icon-button-next>`
: html`<ha-icon-button
@click=${this._openPicker}
.disabled=${this.disabled}
id="field"
.label=${this.localize(
"ui.components.date-range-picker.select_date_range"
)}
.path=${mdiCalendar}
></ha-icon-button>`}
</div>
${this._pickerWrapperOpen || this._opened
? this._openedNarrow
? html`
<ha-bottom-sheet
flexcontent
.open=${this._pickerWrapperOpen}
@wa-after-show=${this._dialogOpened}
@closed=${this._hidePicker}
>
${this._renderPicker()}
</ha-bottom-sheet>
`
: html`
<wa-popover
.open=${this._pickerWrapperOpen}
style="--body-width: ${this._popoverWidth}px;"
class=${this._opened ? "open" : ""}
without-arrow
distance="0"
.placement=${this.popoverPlacement}
for="field"
auto-size="vertical"
auto-size-padding="16"
@wa-after-show=${this._dialogOpened}
@wa-hide=${this._handlePopoverHide}
@wa-after-hide=${this._hidePicker}
trap-focus
>
${this._renderPicker()}
</wa-popover>
`
: nothing}
</div>
`;
}
private _renderPicker() {
if (!this._opened) {
return nothing;
}
return html`
<date-range-picker
.ranges=${this.ranges === false ? false : this.ranges || this._ranges}
.startDate=${this.startDate}
.endDate=${this.endDate}
.timePicker=${this.timePicker}
@cancel-date-picker=${this._closePicker}
@value-changed=${this._closePicker}
>
</date-range-picker>
`;
}
private _hidePicker(ev: Event) {
ev.stopPropagation();
this._opened = false;
this._pickerWrapperOpen = false;
this._unsubscribeTinyKeys?.();
fireEvent(this, "picker-closed");
}
public disconnectedCallback() {
super.disconnectedCallback();
window.removeEventListener("resize", this._handleResize);
this._unsubscribeTinyKeys?.();
}
private _handleResize = () => {
this._narrow =
window.matchMedia("(max-width: 870px)").matches ||
window.matchMedia("(max-height: 500px)").matches;
if (!this._openedNarrow && this._pickerWrapperOpen) {
this._popoverWidth = this._containerElement?.offsetWidth || 250;
}
};
private _dialogOpened = () => {
this._opened = true;
this._setTextareaFocusStyle(true);
};
private _handlePopoverHide = () => {
this._opened = false;
};
private _handleNext(ev: MouseEvent): void {
if (ev && ev.stopPropagation) ev.stopPropagation();
this._shift(true);
}
private _handlePrev(ev: MouseEvent): void {
if (ev && ev.stopPropagation) ev.stopPropagation();
this._shift(false);
}
private _shift(forward: boolean) {
if (!this.startDate) return;
const { start, end } = shiftDateRange(
this.startDate,
this.endDate,
forward,
this.locale,
this.hassConfig
);
this.startDate = start;
this.endDate = end;
fireEvent(this, "value-changed", {
value: {
startDate: this.startDate,
endDate: this.endDate,
},
});
}
private _closePicker() {
this._pickerWrapperOpen = false;
}
private _openPicker(ev?: Event) {
if (this.disabled) {
return;
}
if (this._pickerWrapperOpen) {
ev?.stopImmediatePropagation();
return;
}
this._openedNarrow = this._narrow;
this._popoverWidth = this._containerElement?.offsetWidth || 250;
this._pickerWrapperOpen = true;
this._unsubscribeTinyKeys = tinykeys(this, {
Escape: this._handleEscClose,
});
}
private _handleKeydown(ev: KeyboardEvent) {
if (ev.key === "Enter" || ev.key === " ") {
ev.stopPropagation();
this._openPicker(ev);
}
}
private _handleEscClose = (ev: KeyboardEvent) => {
ev.stopPropagation();
};
private _setTextareaFocusStyle(focused: boolean) {
const textarea = this.renderRoot.querySelector("ha-textarea");
if (textarea) {
const foundation = (textarea as any).mdcFoundation;
if (foundation) {
if (focused) {
foundation.activateFocus();
} else {
foundation.deactivateFocus();
}
}
}
}
static styles = [
css`
ha-icon-button {
direction: var(--direction);
}
.date-range-inputs {
display: flex;
align-items: center;
gap: var(--ha-space-2);
}
ha-textarea {
display: inline-block;
width: 340px;
}
@media only screen and (max-width: 460px) {
ha-textarea {
width: 100%;
}
}
wa-popover {
--wa-space-l: 0;
}
wa-popover::part(dialog)::backdrop {
opacity: 0;
transition: opacity var(--ha-animation-duration-normal) ease-out;
}
wa-popover.open::part(dialog)::backdrop {
opacity: 1;
}
:host(:not([backdrop])) wa-popover::part(dialog)::backdrop {
background: none;
}
wa-popover::part(body) {
min-width: max(var(--body-width), 250px);
max-width: calc(
100vw - var(--safe-area-inset-left) - var(
--safe-area-inset-right
) - var(--ha-space-8)
);
overflow: hidden;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-date-range-picker": HaDateRangePicker;
}
}

View File

@@ -1,220 +0,0 @@
import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume, type ContextType } from "@lit/context";
import { mdiBackspace, mdiCalendarToday } from "@mdi/js";
import "cally";
import { css, html, LitElement, nothing } from "lit";
import { customElement, state } from "lit/decorators";
import {
formatDateMonth,
formatDateShort,
formatDateYear,
formatISODateOnly,
} from "../../common/datetime/format_date";
import {
configContext,
localeContext,
localizeContext,
} from "../../data/context";
import { DialogMixin } from "../../dialogs/dialog-mixin";
import "../ha-button";
import type { DatePickerDialogParams } from "../ha-date-input";
import "../ha-dialog";
import "../ha-dialog-footer";
import "../ha-icon-button";
import "../ha-icon-button-next";
import "../ha-icon-button-prev";
import { datePickerStyles } from "./styles";
type CalendarDate = HTMLElementTagNameMap["calendar-date"];
/**
* A date picker dialog component that displays a calendar for selecting dates.
* Uses the `cally` library for calendar rendering and supports localization,
* min/max date constraints, and optional clearing of the selected date.
*
* @element ha-dialog-date-picker
* Uses {@link DialogMixin} with {@link DatePickerDialogParams} to manage dialog state and parameters.
*/
@customElement("ha-dialog-date-picker")
export class HaDialogDatePicker extends DialogMixin<DatePickerDialogParams>(
LitElement
) {
@state()
@consume({ context: localizeContext, subscribe: true })
private localize!: ContextType<typeof localizeContext>;
@state()
@consume({ context: localeContext, subscribe: true })
private locale!: ContextType<typeof localeContext>;
@state()
@consume({ context: configContext, subscribe: true })
private hassConfig!: ContextType<typeof configContext>;
@state() private _value?: {
year: string;
title: string;
dateString: string;
};
/** used to show month in calendar-date header */
@state() private _pickerMonth?: string;
/** used to show year in calendar-date header */
@state() private _pickerYear?: string;
/** used for today to navigate focus in cally-calendar-date */
@state() private _focusDate?: string;
public connectedCallback() {
super.connectedCallback();
if (this.params) {
const date = this.params.value
? new Date(`${this.params.value.split("T")[0]}T00:00:00`)
: new Date();
this._pickerYear = formatDateYear(date, this.locale, this.hassConfig);
this._pickerMonth = formatDateMonth(date, this.locale, this.hassConfig);
this._value = this.params.value
? {
year: this._pickerYear,
title: formatDateShort(date, this.locale, this.hassConfig),
dateString: formatISODateOnly(date, this.locale, this.hassConfig),
}
: undefined;
}
}
render() {
if (!this.params) {
return nothing;
}
return html`<ha-dialog
open
width="small"
.headerTitle=${this._value?.title ||
this.localize("ui.dialogs.date-picker.title")}
.headerSubtitle=${this._value?.year}
header-subtitle-position="above"
>
${this.params.canClear
? html`
<ha-icon-button
.path=${mdiBackspace}
.label=${this.localize("ui.dialogs.date-picker.clear")}
slot="headerActionItems"
@click=${this._clear}
></ha-icon-button>
`
: nothing}
<wa-divider></wa-divider>
<calendar-date
.value=${this._value?.dateString}
.min=${this.params.min}
.max=${this.params.max}
.locale=${this.params.locale}
.firstDayOfWeek=${this.params.firstWeekday}
.focusedDate=${this._focusDate}
@change=${this._valueChanged}
@focusday=${this._focusChanged}
>
<ha-icon-button-prev
tabindex="-1"
slot="previous"
></ha-icon-button-prev>
<div class="heading" slot="heading">
<span class="month-year"
>${this._pickerMonth} ${this._pickerYear}</span
>
<ha-icon-button
@click=${this._setToday}
.path=${mdiCalendarToday}
.label=${this.localize("ui.dialogs.date-picker.today")}
></ha-icon-button>
</div>
<ha-icon-button-next tabindex="-1" slot="next"></ha-icon-button-next>
<calendar-month></calendar-month>
</calendar-date>
<ha-dialog-footer slot="footer">
<ha-button
appearance="plain"
slot="secondaryAction"
@click=${this.closeDialog}
>
${this.localize("ui.common.cancel")}
</ha-button>
<ha-button slot="primaryAction" @click=${this._setValue}>
${this.localize("ui.common.ok")}
</ha-button>
</ha-dialog-footer>
</ha-dialog>`;
}
private _valueChanged(ev: Event) {
const dateElement = ev.target as CalendarDate;
if (dateElement.value) {
this._updateValue(dateElement.value);
}
}
private _updateValue(value?: string, setFocusDay = false) {
const date = value
? new Date(`${value.split("T")[0]}T00:00:00`)
: new Date();
this._value = {
year: formatDateYear(date, this.locale, this.hassConfig),
title: formatDateShort(date, this.locale, this.hassConfig),
dateString:
value || formatISODateOnly(date, this.locale, this.hassConfig),
};
if (setFocusDay) {
this._focusDate = this._value.dateString;
this._pickerMonth = formatDateMonth(date, this.locale, this.hassConfig);
this._pickerYear = formatDateYear(date, this.locale, this.hassConfig);
}
}
private _focusChanged(ev: CustomEvent<Date>) {
const date = ev.detail;
this._pickerMonth = formatDateMonth(date, this.locale, this.hassConfig);
this._pickerYear = formatDateYear(date, this.locale, this.hassConfig);
this._focusDate = undefined;
}
private _clear() {
this.params?.onChange(undefined);
this.closeDialog();
}
private _setToday() {
this._updateValue(undefined, true);
}
private _setValue() {
if (!this._value) {
// Date picker opens to today if value is undefined. If user click OK
// without changing the date, should return todays date, not undefined.
this._setToday();
}
this.params?.onChange(this._value?.dateString);
this.closeDialog();
}
static styles = [
datePickerStyles,
css`
ha-dialog {
--dialog-content-padding: 0;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-dialog-date-picker": HaDialogDatePicker;
}
}

View File

@@ -1,117 +0,0 @@
import { css } from "lit";
export const datePickerStyles = css`
calendar-range,
calendar-date {
width: 100%;
min-width: 300px;
}
calendar-date::part(button),
calendar-range::part(button) {
border: none;
background-color: unset;
border-radius: var(--ha-border-radius-circle);
outline-offset: -2px;
outline-color: var(--ha-color-neutral-60);
}
calendar-month {
width: calc(40px * 7);
margin: 0 auto;
min-height: calc(42px * 7);
}
calendar-month::part(heading) {
display: none;
}
calendar-month::part(day) {
color: var(--disabled-text-color);
font-size: var(--ha-font-size-m);
font-family: var(--ha-font-body);
}
calendar-month::part(button) {
color: var(--primary-text-color);
height: 32px;
width: 32px;
margin: var(--ha-space-1);
border-radius: var(--ha-border-radius-circle);
}
calendar-month::part(button):focus-visible {
background-color: inherit;
outline: 2px solid var(--accent-color);
outline-offset: 2px;
}
calendar-month::part(button):hover {
background-color: var(--ha-color-fill-primary-quiet-hover);
}
calendar-month::part(today) {
color: var(--primary-color);
}
calendar-month::part(range-inner),
calendar-month::part(range-start),
calendar-month::part(range-end),
calendar-month::part(selected),
calendar-month::part(selected):hover {
color: var(--text-primary-color);
background-color: var(--primary-color);
height: 40px;
width: 40px;
margin: 0;
}
calendar-month::part(selected):focus-visible {
background-color: var(--primary-color);
color: var(--text-primary-color);
}
calendar-month::part(outside) {
cursor: pointer;
}
.heading {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
font-size: var(--ha-font-size-m);
font-weight: var(--ha-font-weight-medium);
}
.month-year {
flex: 1;
text-align: center;
margin-left: 48px;
}
`;
export const dateRangePickerStyles = css`
calendar-month::part(selected):focus-visible {
background-color: var(--primary-color);
color: var(--text-primary-color);
}
calendar-month::part(range-inner),
calendar-month::part(range-start),
calendar-month::part(range-end),
calendar-month::part(range-inner):hover,
calendar-month::part(range-start):hover,
calendar-month::part(range-end):hover {
color: var(--text-primary-color);
background-color: var(--primary-color);
border-radius: var(--ha-border-radius-square);
display: block;
margin: 0;
}
calendar-month::part(range-start),
calendar-month::part(range-start):hover {
border-top-left-radius: var(--ha-border-radius-circle);
border-bottom-left-radius: var(--ha-border-radius-circle);
}
calendar-month::part(range-end),
calendar-month::part(range-end):hover {
border-top-right-radius: var(--ha-border-radius-circle);
border-bottom-right-radius: var(--ha-border-radius-circle);
}
calendar-month::part(range-start):hover,
calendar-month::part(range-end):hover,
calendar-month::part(range-inner):hover {
color: var(--primary-text-color);
}
`;

View File

@@ -0,0 +1,359 @@
import wrap from "@vue/web-component-wrapper";
import { customElement } from "lit/decorators";
import Vue from "vue";
import DateRangePicker from "vue2-daterange-picker";
// @ts-ignore
import dateRangePickerStyles from "vue2-daterange-picker/dist/vue2-daterange-picker.css";
import {
localizeMonths,
localizeWeekdays,
} from "../common/datetime/localize_date";
import { fireEvent } from "../common/dom/fire_event";
import { mainWindow } from "../common/dom/get_main_window";
// eslint-disable-next-line @typescript-eslint/naming-convention
const CustomDateRangePicker = Vue.extend({
mixins: [DateRangePicker],
methods: {
// Set the current date to the left picker instead of the right picker because the right is hidden
selectMonthDate() {
const dt: Date = this.end || new Date();
// @ts-ignore
this.changeLeftMonth({
year: dt.getFullYear(),
month: dt.getMonth() + 1,
});
},
// Fix the start/end date calculation when selecting a date range. The
// original code keeps track of the first clicked date (in_selection) but it
// never sets it to either the start or end date variables, so if the
// in_selection date is between the start and end date that were set by the
// hover the selection will enter a broken state that's counter-intuitive
// when hovering between weeks and leads to a random date when selecting a
// range across months. This bug doesn't seem to be present on v0.6.7 of the
// lib
hoverDate(value: Date) {
if (this.readonly) return;
if (this.in_selection) {
const pickA = this.in_selection as Date;
const pickB = value;
this.start = this.normalizeDatetime(
Math.min(pickA.valueOf(), pickB.valueOf()),
this.start
);
this.end = this.normalizeDatetime(
Math.max(pickA.valueOf(), pickB.valueOf()),
this.end
);
}
this.$emit("hover-date", value);
},
},
});
// eslint-disable-next-line @typescript-eslint/naming-convention
const Component = Vue.extend({
props: {
timePicker: {
type: Boolean,
default: true,
},
twentyfourHours: {
type: Boolean,
default: true,
},
openingDirection: {
type: String,
default: "right",
},
disabled: {
type: Boolean,
default: false,
},
ranges: {
type: Boolean,
default: true,
},
startDate: {
type: [String, Date],
default() {
return new Date();
},
},
endDate: {
type: [String, Date],
default() {
return new Date();
},
},
firstDay: {
type: Number,
default: 1,
},
autoApply: {
type: Boolean,
default: false,
},
language: {
type: String,
default: "en",
},
opensVertical: {
type: String,
default: undefined,
},
},
render(createElement) {
// @ts-expect-error
return createElement(CustomDateRangePicker, {
props: {
"time-picker": this.timePicker,
"auto-apply": this.autoApply,
opens: this.openingDirection,
"show-dropdowns": false,
"time-picker24-hour": this.twentyfourHours,
disabled: this.disabled,
ranges: this.ranges ? {} : false,
"locale-data": {
firstDay: this.firstDay,
daysOfWeek: localizeWeekdays(this.language, true),
monthNames: localizeMonths(this.language, false),
},
},
model: {
value: {
startDate: this.startDate,
endDate: this.endDate,
},
callback: (value) => {
fireEvent(this.$el as HTMLElement, "change", value);
},
expression: "dateRange",
},
on: {
toggle: (open: boolean) => {
fireEvent(this.$el as HTMLElement, "toggle", { open });
},
},
scopedSlots: {
input() {
return createElement("slot", {
domProps: { name: "input" },
});
},
header() {
return createElement("slot", {
domProps: { name: "header" },
});
},
ranges() {
return createElement("slot", {
domProps: { name: "ranges" },
});
},
footer() {
return createElement("slot", {
domProps: { name: "footer" },
});
},
},
});
},
});
// Assertion corrects HTMLElement type from package
// eslint-disable-next-line @typescript-eslint/naming-convention
const WrappedElement = wrap(
Vue,
Component
) as unknown as CustomElementConstructor;
@customElement("date-range-picker")
class DateRangePickerElement extends WrappedElement {
constructor() {
super();
const style = document.createElement("style");
style.innerHTML = `
${dateRangePickerStyles}
.calendars {
display: flex;
flex-wrap: nowrap !important;
}
.daterangepicker {
top: auto;
box-shadow: var(--ha-card-box-shadow, none);
background-color: var(--card-background-color);
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
border-width: var(--ha-card-border-width, 1px);
border-style: solid;
border-color: var(
--ha-card-border-color,
var(--divider-color, #e0e0e0)
);
color: var(--primary-text-color);
min-width: initial !important;
max-height: var(--date-range-picker-max-height);
overflow-y: auto;
}
.daterangepicker:before {
display: none;
}
.daterangepicker:after {
border-bottom: 6px solid var(--card-background-color);
}
.daterangepicker .calendar-table {
background-color: var(--card-background-color);
border: none;
}
.daterangepicker .calendar-table td,
.daterangepicker .calendar-table th {
background-color: transparent;
color: var(--secondary-text-color);
border-radius: var(--ha-border-radius-square);
outline: none;
min-width: 32px;
height: 32px;
}
.daterangepicker td.off,
.daterangepicker td.off.end-date,
.daterangepicker td.off.in-range,
.daterangepicker td.off.start-date {
background-color: var(--secondary-background-color);
color: var(--disabled-text-color);
}
.daterangepicker td.in-range {
background-color: var(--light-primary-color);
color: var(--text-light-primary-color, var(--primary-text-color));
}
.daterangepicker td.active,
.daterangepicker td.active:hover {
background-color: var(--primary-color);
color: var(--text-primary-color);
}
.daterangepicker td.start-date.end-date {
border-radius: var(--ha-border-radius-circle);
}
.daterangepicker td.start-date {
border-radius: var(--ha-border-radius-circle) var(--ha-border-radius-square) var(--ha-border-radius-square) var(--ha-border-radius-circle);
}
.daterangepicker td.end-date {
border-radius: var(--ha-border-radius-square) var(--ha-border-radius-circle) var(--ha-border-radius-circle) var(--ha-border-radius-square);
}
.reportrange-text {
background: none !important;
padding: 0 !important;
border: none !important;
}
.daterangepicker .calendar-table .next span,
.daterangepicker .calendar-table .prev span {
border: solid var(--primary-text-color);
border-width: 0 2px 2px 0;
}
.daterangepicker .ranges li {
outline: none;
}
.daterangepicker .ranges li:hover {
background-color: var(--secondary-background-color);
}
.daterangepicker .ranges li.active {
background-color: var(--primary-color);
color: var(--text-primary-color);
}
.daterangepicker select.ampmselect,
.daterangepicker select.hourselect,
.daterangepicker select.minuteselect,
.daterangepicker select.secondselect {
background: var(--card-background-color);
border: 1px solid var(--divider-color);
color: var(--primary-color);
}
.daterangepicker .drp-buttons .btn {
border: 1px solid var(--primary-color);
background-color: transparent;
color: var(--primary-color);
border-radius: var(--ha-border-radius-sm);
padding: 8px;
cursor: pointer;
}
.calendars-container {
flex-direction: column;
align-items: center;
}
.drp-calendar.col.right .calendar-table {
display: none;
}
.daterangepicker.show-ranges .drp-calendar.left {
border-left: 0px;
}
.daterangepicker .drp-calendar.left {
padding: 8px;
width: unset;
max-width: unset;
min-width: 270px;
}
.daterangepicker.show-calendar .ranges {
margin-top: 0;
padding-top: 8px;
border-right: 1px solid var(--divider-color);
}
@media only screen and (max-width: 800px) {
.calendars {
flex-direction: column;
}
}
.calendar-table {
padding: 0 !important;
}
.calendar-time {
direction: ltr;
}
.daterangepicker.ltr {
direction: var(--direction);
text-align: var(--float-start);
}
.vue-daterange-picker{
min-width: unset !important;
display: block !important;
}
:host([opens-vertical="up"]) .daterangepicker {
bottom: 100%;
top: auto !important;
}
`;
if (mainWindow.document.dir === "rtl") {
style.innerHTML += `
.daterangepicker .calendar-table .next span {
transform: rotate(135deg);
-webkit-transform: rotate(135deg);
}
.daterangepicker .calendar-table .prev span {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
}
.daterangepicker td.start-date {
border-radius: var(--ha-border-radius-square) var(--ha-border-radius-circle) var(--ha-border-radius-circle) var(--ha-border-radius-square);
}
.daterangepicker td.end-date {
border-radius: var(--ha-border-radius-circle) var(--ha-border-radius-square) var(--ha-border-radius-square) var(--ha-border-radius-circle);
}
`;
}
const shadowRoot = this.shadowRoot!;
shadowRoot.appendChild(style);
// Stop click events from reaching the document, otherwise it will close the picker immediately.
shadowRoot.addEventListener("click", (ev) => ev.stopPropagation());
}
}
declare global {
interface HTMLElementTagNameMap {
"date-range-picker": DateRangePickerElement;
}
interface HASSDomEvents {
toggle: { open: boolean };
}
}

View File

@@ -36,7 +36,7 @@ export abstract class HaDeviceAutomationPicker<
@state()
@consume({ context: fullEntitiesContext, subscribe: true })
_entityReg: EntityRegistryEntry[] = [];
_entityReg!: EntityRegistryEntry[];
protected get NO_AUTOMATION_TEXT() {
return this.hass.localize(

View File

@@ -173,14 +173,11 @@ export class HaDevicePicker extends LitElement {
alt=""
crossorigin="anonymous"
referrerpolicy="no-referrer"
src=${brandsUrl(
{
domain: configEntry.domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
},
this.hass.auth.data.hassUrl
)}
src=${brandsUrl({
domain: configEntry.domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
})}
/>`
: nothing}
<span slot="headline">${primary}</span>
@@ -198,14 +195,11 @@ export class HaDevicePicker extends LitElement {
alt=""
crossorigin="anonymous"
referrerpolicy="no-referrer"
src=${brandsUrl(
{
domain: item.domain,
type: "icon",
darkOptimized: this.hass.themes.darkMode,
},
this.hass.auth.data.hassUrl
)}
src=${brandsUrl({
domain: item.domain,
type: "icon",
darkOptimized: this.hass.themes.darkMode,
})}
/>
`
: nothing}

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