mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-20 03:17:20 +00:00
Compare commits
No commits in common. "dev" and "20250312.0" have entirely different histories.
dev
...
20250312.0
4
.github/workflows/cast_deployment.yaml
vendored
4
.github/workflows/cast_deployment.yaml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
ref: dev
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v4.2.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@ -62,7 +62,7 @@ jobs:
|
||||
ref: master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v4.2.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
14
.github/workflows/ci.yaml
vendored
14
.github/workflows/ci.yaml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v4.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@v4.2.3
|
||||
uses: actions/cache@v4.2.1
|
||||
with:
|
||||
path: |
|
||||
node_modules/.cache/prettier
|
||||
@ -60,7 +60,7 @@ jobs:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v4.2.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@ -78,7 +78,7 @@ jobs:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v4.2.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@ -89,7 +89,7 @@ jobs:
|
||||
env:
|
||||
IS_TEST: "true"
|
||||
- name: Upload bundle stats
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
with:
|
||||
name: frontend-bundle-stats
|
||||
path: build/stats/*.json
|
||||
@ -102,7 +102,7 @@ jobs:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v4.2.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@ -113,7 +113,7 @@ jobs:
|
||||
env:
|
||||
IS_TEST: "true"
|
||||
- name: Upload bundle stats
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
with:
|
||||
name: supervisor-bundle-stats
|
||||
path: build/stats/*.json
|
||||
|
4
.github/workflows/demo_deployment.yaml
vendored
4
.github/workflows/demo_deployment.yaml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
ref: dev
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v4.2.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@ -63,7 +63,7 @@ jobs:
|
||||
ref: master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v4.2.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
2
.github/workflows/design_deployment.yaml
vendored
2
.github/workflows/design_deployment.yaml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v4.2.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
2
.github/workflows/design_preview.yaml
vendored
2
.github/workflows/design_preview.yaml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v4.2.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
6
.github/workflows/nightly.yaml
vendored
6
.github/workflows/nightly.yaml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v4.2.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@ -57,14 +57,14 @@ jobs:
|
||||
run: tar -czvf translations.tar.gz translations
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
with:
|
||||
name: wheels
|
||||
path: dist/home_assistant_frontend*.whl
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload translations
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
with:
|
||||
name: translations
|
||||
path: translations.tar.gz
|
||||
|
2
.github/workflows/relative-ci.yaml
vendored
2
.github/workflows/relative-ci.yaml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send bundle stats and build information to RelativeCI
|
||||
uses: relative-ci/agent-action@v2.2.0
|
||||
uses: relative-ci/agent-action@v2.1.14
|
||||
with:
|
||||
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
||||
token: ${{ github.token }}
|
||||
|
8
.github/workflows/release.yaml
vendored
8
.github/workflows/release.yaml
vendored
@ -34,7 +34,7 @@ jobs:
|
||||
uses: home-assistant/actions/helpers/verify-version@master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v4.2.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@ -74,7 +74,7 @@ jobs:
|
||||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2025.03.0
|
||||
uses: home-assistant/wheels@2024.11.0
|
||||
with:
|
||||
abi: cp313
|
||||
tag: musllinux_1_2
|
||||
@ -92,7 +92,7 @@ jobs:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v4.2.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@ -121,7 +121,7 @@ jobs:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.4.0
|
||||
uses: actions/setup-node@v4.2.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
34
.yarn/patches/@polymer/polymer/pr-5569.patch
Normal file
34
.yarn/patches/@polymer/polymer/pr-5569.patch
Normal file
@ -0,0 +1,34 @@
|
||||
diff --git a/lib/legacy/class.js b/lib/legacy/class.js
|
||||
index aee2511be1cd9bf900ee552bc98190c1631c57c0..f2f499d68bf52034cac9c28307c99e8ce6b8417d 100644
|
||||
--- a/lib/legacy/class.js
|
||||
+++ b/lib/legacy/class.js
|
||||
@@ -304,17 +304,23 @@ function GenerateClassFromInfo(info, Base, behaviors) {
|
||||
// only proceed if the generated class' prototype has not been registered.
|
||||
const generatedProto = PolymerGenerated.prototype;
|
||||
if (!generatedProto.hasOwnProperty(JSCompiler_renameProperty('__hasRegisterFinished', generatedProto))) {
|
||||
- generatedProto.__hasRegisterFinished = true;
|
||||
+ // make sure legacy lifecycle is called on the *element*'s prototype
|
||||
+ // and not the generated class prototype; if the element has been
|
||||
+ // extended, these are *not* the same.
|
||||
+ const proto = Object.getPrototypeOf(this);
|
||||
+ // Only set flag when generated prototype itself is registered,
|
||||
+ // as this element may be extended from, and needs to run `registered`
|
||||
+ // on all behaviors on the subclass as well.
|
||||
+ if (proto === generatedProto) {
|
||||
+ generatedProto.__hasRegisterFinished = true;
|
||||
+ }
|
||||
// ensure superclass is registered first.
|
||||
super._registered();
|
||||
// copy properties onto the generated class lazily if we're optimizing,
|
||||
- if (legacyOptimizations) {
|
||||
+ if (legacyOptimizations && !Object.hasOwnProperty(generatedProto, '__hasCopiedProperties')) {
|
||||
+ generatedProto.__hasCopiedProperties = true;
|
||||
copyPropertiesToProto(generatedProto);
|
||||
}
|
||||
- // make sure legacy lifecycle is called on the *element*'s prototype
|
||||
- // and not the generated class prototype; if the element has been
|
||||
- // extended, these are *not* the same.
|
||||
- const proto = Object.getPrototypeOf(this);
|
||||
let list = lifecycle.beforeRegister;
|
||||
if (list) {
|
||||
for (let i=0; i < list.length; i++) {
|
934
.yarn/releases/yarn-4.6.0.cjs
vendored
Executable file
934
.yarn/releases/yarn-4.6.0.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
948
.yarn/releases/yarn-4.9.1.cjs
vendored
948
.yarn/releases/yarn-4.9.1.cjs
vendored
File diff suppressed because one or more lines are too long
@ -6,4 +6,4 @@ enableGlobalCache: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.9.1.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.6.0.cjs
|
||||
|
@ -2,7 +2,7 @@ import defineProvider from "@babel/helper-define-polyfill-provider";
|
||||
import { join } from "node:path";
|
||||
import paths from "../paths.cjs";
|
||||
|
||||
const POLYFILL_DIR = join(paths.root_dir, "src/resources/polyfills");
|
||||
const POLYFILL_DIR = join(paths.polymer_dir, "src/resources/polyfills");
|
||||
|
||||
// List of polyfill keys with supported browser targets for the functionality
|
||||
const polyfillSupport = {
|
||||
|
@ -18,18 +18,30 @@ module.exports.sourceMapURL = () => {
|
||||
module.exports.ignorePackages = () => [];
|
||||
|
||||
// Files from NPM packages that we should replace with empty file
|
||||
module.exports.emptyPackages = ({ isHassioBuild }) =>
|
||||
module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
|
||||
[
|
||||
// Contains all color definitions for all material color sets.
|
||||
// We don't use it
|
||||
require.resolve("@polymer/paper-styles/color.js"),
|
||||
require.resolve("@polymer/paper-styles/default-theme.js"),
|
||||
// Loads stuff from a CDN
|
||||
require.resolve("@polymer/font-roboto/roboto.js"),
|
||||
require.resolve("@vaadin/vaadin-material-styles/typography.js"),
|
||||
require.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
|
||||
// Compatibility not needed for latest builds
|
||||
latestBuild &&
|
||||
// wrapped in require.resolve so it blows up if file no longer exists
|
||||
require.resolve(
|
||||
path.resolve(paths.polymer_dir, "src/resources/compatibility.ts")
|
||||
),
|
||||
// Icons in supervisor conflict with icons in HA so we don't load.
|
||||
isHassioBuild &&
|
||||
require.resolve(
|
||||
path.resolve(paths.root_dir, "src/components/ha-icon.ts")
|
||||
path.resolve(paths.polymer_dir, "src/components/ha-icon.ts")
|
||||
),
|
||||
isHassioBuild &&
|
||||
require.resolve(
|
||||
path.resolve(paths.root_dir, "src/components/ha-icon-picker.ts")
|
||||
path.resolve(paths.polymer_dir, "src/components/ha-icon-picker.ts")
|
||||
),
|
||||
].filter(Boolean);
|
||||
|
||||
@ -43,9 +55,8 @@ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
||||
__STATIC_PATH__: "/static/",
|
||||
__HASS_URL__: `\`${
|
||||
"HASS_URL" in process.env
|
||||
? process.env.HASS_URL
|
||||
: // eslint-disable-next-line no-template-curly-in-string
|
||||
"${location.protocol}//${location.host}"
|
||||
? process.env["HASS_URL"]
|
||||
: "${location.protocol}//${location.host}"
|
||||
}\``,
|
||||
"process.env.NODE_ENV": JSON.stringify(
|
||||
isProdBuild ? "production" : "development"
|
||||
@ -159,7 +170,7 @@ module.exports.babelOptions = ({
|
||||
],
|
||||
],
|
||||
exclude: [
|
||||
path.join(paths.root_dir, "src/resources/polyfills"),
|
||||
path.join(paths.polymer_dir, "src/resources/polyfills"),
|
||||
...[
|
||||
"@formatjs/(?:ecma402-abstract|intl-\\w+)",
|
||||
"@lit-labs/virtualizer/polyfills",
|
||||
@ -177,7 +188,6 @@ module.exports.babelOptions = ({
|
||||
include: /\/node_modules\//,
|
||||
exclude: [
|
||||
"element-internals-polyfill",
|
||||
"@shoelace-style",
|
||||
"@?lit(?:-labs|-element|-html)?",
|
||||
].map((p) => new RegExp(`/node_modules/${p}/`)),
|
||||
},
|
||||
|
@ -21,7 +21,7 @@ module.exports = {
|
||||
},
|
||||
version() {
|
||||
const version = fs
|
||||
.readFileSync(path.resolve(paths.root_dir, "pyproject.toml"), "utf8")
|
||||
.readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8")
|
||||
.match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/);
|
||||
if (!version) {
|
||||
throw Error("Version not found");
|
||||
|
@ -56,7 +56,6 @@ const getCommonTemplateVars = () => {
|
||||
);
|
||||
return {
|
||||
modernRegex: compileRegex(browserRegexes.concat(haMacOSRegex)).toString(),
|
||||
hassUrl: process.env.HASS_URL || "",
|
||||
};
|
||||
};
|
||||
|
||||
@ -169,14 +168,14 @@ const APP_PAGE_ENTRIES = {
|
||||
|
||||
gulp.task(
|
||||
"gen-pages-app-dev",
|
||||
genPagesDevTask(APP_PAGE_ENTRIES, paths.root_dir, paths.app_output_root)
|
||||
genPagesDevTask(APP_PAGE_ENTRIES, paths.polymer_dir, paths.app_output_root)
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"gen-pages-app-prod",
|
||||
genPagesProdTask(
|
||||
APP_PAGE_ENTRIES,
|
||||
paths.root_dir,
|
||||
paths.polymer_dir,
|
||||
paths.app_output_root,
|
||||
paths.app_output_latest,
|
||||
paths.app_output_es5
|
||||
|
@ -6,8 +6,8 @@ import path from "path";
|
||||
import paths from "../paths.cjs";
|
||||
|
||||
const npmPath = (...parts) =>
|
||||
path.resolve(paths.root_dir, "node_modules", ...parts);
|
||||
const polyPath = (...parts) => path.resolve(paths.root_dir, ...parts);
|
||||
path.resolve(paths.polymer_dir, "node_modules", ...parts);
|
||||
const polyPath = (...parts) => path.resolve(paths.polymer_dir, ...parts);
|
||||
|
||||
const copyFileDir = (fromFile, toDir) =>
|
||||
fs.copySync(fromFile, path.join(toDir, path.basename(fromFile)));
|
||||
@ -59,11 +59,6 @@ function copyPolyfills(staticDir) {
|
||||
npmPath("@webcomponents/webcomponentsjs/webcomponents-bundle.js.map"),
|
||||
staticPath("polyfills/")
|
||||
);
|
||||
// Lit polyfill support
|
||||
fs.copySync(
|
||||
npmPath("lit/polyfill-support.js"),
|
||||
path.join(staticPath("polyfills/"), "lit-polyfill-support.js")
|
||||
);
|
||||
|
||||
// dialog-polyfill css
|
||||
copyFileDir(
|
||||
|
@ -4,7 +4,7 @@ import gulp from "gulp";
|
||||
import { join, resolve } from "node:path";
|
||||
import paths from "../paths.cjs";
|
||||
|
||||
const formatjsDir = join(paths.root_dir, "node_modules", "@formatjs");
|
||||
const formatjsDir = join(paths.polymer_dir, "node_modules", "@formatjs");
|
||||
const outDir = join(paths.build_dir, "locale-data");
|
||||
|
||||
const INTL_POLYFILLS = {
|
||||
|
@ -40,17 +40,20 @@ class CustomJSON extends Transform {
|
||||
this._reviver = reviver;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
async _transform(file, _, callback) {
|
||||
let obj = JSON.parse(file.contents.toString(), this._reviver);
|
||||
if (this._func) obj = this._func(obj, file.path);
|
||||
for (const [outObj, dir] of Array.isArray(obj) ? obj : [[obj, ""]]) {
|
||||
const outFile = file.clone({ contents: false });
|
||||
outFile.contents = Buffer.from(JSON.stringify(outObj));
|
||||
outFile.dirname += `/${dir}`;
|
||||
this.push(outFile);
|
||||
try {
|
||||
let obj = JSON.parse(file.contents.toString(), this._reviver);
|
||||
if (this._func) obj = this._func(obj, file.path);
|
||||
for (const [outObj, dir] of Array.isArray(obj) ? obj : [[obj, ""]]) {
|
||||
const outFile = file.clone({ contents: false });
|
||||
outFile.contents = Buffer.from(JSON.stringify(outObj));
|
||||
outFile.dirname += `/${dir}`;
|
||||
this.push(outFile);
|
||||
}
|
||||
callback(null);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,19 +68,25 @@ class MergeJSON extends Transform {
|
||||
this._reviver = reviver;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
async _transform(file, _, callback) {
|
||||
this._objects.push(JSON.parse(file.contents.toString(), this._reviver));
|
||||
if (!this._outFile) this._outFile = file.clone({ contents: false });
|
||||
callback(null);
|
||||
try {
|
||||
this._objects.push(JSON.parse(file.contents.toString(), this._reviver));
|
||||
if (!this._outFile) this._outFile = file.clone({ contents: false });
|
||||
callback(null);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
async _flush(callback) {
|
||||
const mergedObj = merge(this._startObj, ...this._objects);
|
||||
this._outFile.contents = Buffer.from(JSON.stringify(mergedObj));
|
||||
this._outFile.stem = this._stem;
|
||||
callback(null, this._outFile);
|
||||
try {
|
||||
const mergedObj = merge(this._startObj, ...this._objects);
|
||||
this._outFile.contents = Buffer.from(JSON.stringify(mergedObj));
|
||||
this._outFile.stem = this._stem;
|
||||
callback(null, this._outFile);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
root_dir: path.resolve(__dirname, ".."),
|
||||
polymer_dir: path.resolve(__dirname, ".."),
|
||||
|
||||
build_dir: path.resolve(__dirname, "../build"),
|
||||
app_output_root: path.resolve(__dirname, "../hass_frontend"),
|
||||
|
@ -1,17 +1,12 @@
|
||||
const { existsSync } = require("fs");
|
||||
const path = require("path");
|
||||
const rspack = require("@rspack/core");
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { RsdoctorRspackPlugin } = require("@rsdoctor/rspack-plugin");
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { StatsWriterPlugin } = require("webpack-stats-plugin");
|
||||
const filterStats = require("@bundle-stats/plugin-webpack-filter");
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const filterStats = require("@bundle-stats/plugin-webpack-filter").default;
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { WebpackManifestPlugin } = require("rspack-manifest-plugin");
|
||||
const log = require("fancy-log");
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const WebpackBar = require("webpackbar/rspack");
|
||||
const paths = require("./paths.cjs");
|
||||
const bundle = require("./bundle.cjs");
|
||||
@ -160,8 +155,10 @@ const createRspackConfig = ({
|
||||
},
|
||||
}),
|
||||
new rspack.NormalModuleReplacementPlugin(
|
||||
new RegExp(bundle.emptyPackages({ isHassioBuild }).join("|")),
|
||||
path.resolve(paths.root_dir, "src/util/empty.js")
|
||||
new RegExp(
|
||||
bundle.emptyPackages({ latestBuild, isHassioBuild }).join("|")
|
||||
),
|
||||
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||
),
|
||||
!isProdBuild && new LogStartCompilePlugin(),
|
||||
isProdBuild &&
|
||||
@ -195,7 +192,6 @@ const createRspackConfig = ({
|
||||
"lit/directives/if-defined$": "lit/directives/if-defined.js",
|
||||
"lit/directives/guard$": "lit/directives/guard.js",
|
||||
"lit/directives/cache$": "lit/directives/cache.js",
|
||||
"lit/directives/join$": "lit/directives/join.js",
|
||||
"lit/directives/repeat$": "lit/directives/repeat.js",
|
||||
"lit/directives/live$": "lit/directives/live.js",
|
||||
"lit/directives/keyed$": "lit/directives/keyed.js",
|
||||
|
@ -309,7 +309,7 @@ export class HcMain extends HassElement {
|
||||
"../../../../src/panels/lovelace/strategies/get-strategy"
|
||||
);
|
||||
const config = await generateLovelaceDashboardStrategy(
|
||||
rawConfig,
|
||||
rawConfig.strategy,
|
||||
this.hass!
|
||||
);
|
||||
this._handleNewLovelaceConfig(config);
|
||||
@ -351,7 +351,10 @@ export class HcMain extends HassElement {
|
||||
"../../../../src/panels/lovelace/strategies/get-strategy"
|
||||
);
|
||||
this._handleNewLovelaceConfig(
|
||||
await generateLovelaceDashboardStrategy(DEFAULT_CONFIG, this.hass!)
|
||||
await generateLovelaceDashboardStrategy(
|
||||
DEFAULT_CONFIG.strategy,
|
||||
this.hass!
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ export const demoThemeJimpower = () => ({
|
||||
"paper-item-icon-color": "var(--primary-text-color)",
|
||||
"primary-color": "#5294E2",
|
||||
"label-badge-red": "var(--accent-color)",
|
||||
"paper-tabs-selection-bar-color": "green",
|
||||
"light-primary-color": "var(--accent-color)",
|
||||
"primary-background-color": "#383C45",
|
||||
"primary-text-color": "#FFFFFF",
|
||||
|
@ -4,6 +4,7 @@ export const demoThemeKernehed = () => ({
|
||||
"paper-item-icon-color": "var(--primary-text-color)",
|
||||
"primary-color": "#2980b9",
|
||||
"label-badge-red": "var(--accent-color)",
|
||||
"paper-tabs-selection-bar-color": "green",
|
||||
"primary-text-color": "#FFFFFF",
|
||||
"light-primary-color": "var(--accent-color)",
|
||||
"primary-background-color": "#222222",
|
||||
|
@ -5,7 +5,7 @@ import { until } from "lit/directives/until";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-button";
|
||||
import "../../../src/components/ha-spinner";
|
||||
import "../../../src/components/ha-circular-progress";
|
||||
import type { LovelaceCardConfig } from "../../../src/data/lovelace/config/card";
|
||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
import type {
|
||||
@ -44,7 +44,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
||||
<div class="picker">
|
||||
<div class="label">
|
||||
${this._switching
|
||||
? html`<ha-spinner></ha-spinner>`
|
||||
? html`
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
`
|
||||
: until(
|
||||
selectedDemoConfig.then(
|
||||
(conf) => html`
|
||||
|
@ -1,3 +1,5 @@
|
||||
// Compat needs to be first import
|
||||
import "../../src/resources/compatibility";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
|
||||
import { navigate } from "../../src/common/navigate";
|
||||
|
@ -1,10 +1,9 @@
|
||||
import type { validateConfig } from "../../../src/data/config";
|
||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockConfig = (hass: MockHomeAssistant) => {
|
||||
hass.mockWS<typeof validateConfig>("validate_config", () => ({
|
||||
actions: { valid: true, error: null },
|
||||
conditions: { valid: true, error: null },
|
||||
triggers: { valid: true, error: null },
|
||||
hass.mockWS("validate_config", () => ({
|
||||
actions: { valid: true },
|
||||
conditions: { valid: true },
|
||||
triggers: { valid: true },
|
||||
}));
|
||||
};
|
||||
|
@ -1,26 +1,20 @@
|
||||
import type { getConfigEntries } from "../../../src/data/config_entries";
|
||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockConfigEntries = (hass: MockHomeAssistant) => {
|
||||
hass.mockWS<typeof getConfigEntries>("config_entries/get", () => [
|
||||
{
|
||||
entry_id: "mock-entry-co2signal",
|
||||
domain: "co2signal",
|
||||
title: "Electricity Maps",
|
||||
source: "user",
|
||||
state: "loaded",
|
||||
supports_options: false,
|
||||
supports_remove_device: false,
|
||||
supports_unload: true,
|
||||
supports_reconfigure: true,
|
||||
supported_subentry_types: {},
|
||||
pref_disable_new_entities: false,
|
||||
pref_disable_polling: false,
|
||||
disabled_by: null,
|
||||
reason: null,
|
||||
num_subentries: 0,
|
||||
error_reason_translation_key: null,
|
||||
error_reason_translation_placeholders: null,
|
||||
},
|
||||
]);
|
||||
hass.mockWS("config_entries/get", () => ({
|
||||
entry_id: "co2signal",
|
||||
domain: "co2signal",
|
||||
title: "Electricity Maps",
|
||||
source: "user",
|
||||
state: "loaded",
|
||||
supports_options: false,
|
||||
supports_remove_device: false,
|
||||
supports_unload: true,
|
||||
supports_reconfigure: true,
|
||||
supported_subentry_types: {},
|
||||
pref_disable_new_entities: false,
|
||||
pref_disable_polling: false,
|
||||
disabled_by: null,
|
||||
reason: null,
|
||||
}));
|
||||
};
|
||||
|
@ -42,6 +42,7 @@ export default tseslint.config(
|
||||
__VERSION__: false,
|
||||
__STATIC_PATH__: false,
|
||||
__SUPERVISOR__: false,
|
||||
Polymer: true,
|
||||
},
|
||||
|
||||
parser: tseslint.parser,
|
||||
|
@ -1,4 +1,4 @@
|
||||
---
|
||||
title: Spinner
|
||||
title: Circular Progress
|
||||
subtitle: Can be used to indicate an ongoing task.
|
||||
---
|
63
gallery/src/pages/components/ha-circular-progress.ts
Normal file
63
gallery/src/pages/components/ha-circular-progress.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { html, css, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../src/components/ha-bar";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "@material/web/progress/circular-progress";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
|
||||
@customElement("demo-components-ha-circular-progress")
|
||||
export class DemoHaCircularProgress extends LitElement {
|
||||
@property({ attribute: false }) hass!: HomeAssistant;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<ha-card header="Basic circular progress">
|
||||
<div class="card-content">
|
||||
<ha-circular-progress indeterminate></ha-circular-progress></div
|
||||
></ha-card>
|
||||
<ha-card header="Different circular progress sizes">
|
||||
<div class="card-content">
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="tiny"
|
||||
></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="small"
|
||||
></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="medium"
|
||||
></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="large"
|
||||
></ha-circular-progress></div
|
||||
></ha-card>
|
||||
<ha-card header="Circular progress with an aria-label">
|
||||
<div class="card-content">
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
aria-label="Doing something..."
|
||||
></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
.ariaLabel=${"Doing something..."}
|
||||
></ha-circular-progress></div
|
||||
></ha-card>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-circular-progress": DemoHaCircularProgress;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { mdiLightbulbOn, mdiPacMan } from "@mdi/js";
|
||||
import { mdiPacMan } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
@ -125,23 +125,6 @@ const SAMPLES: {
|
||||
`;
|
||||
},
|
||||
},
|
||||
{
|
||||
template(slot, leftChevron) {
|
||||
return html`
|
||||
<ha-expansion-panel
|
||||
slot=${slot}
|
||||
.leftChevron=${leftChevron}
|
||||
header="Attr Header with actions"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="leading-icon"
|
||||
.path=${mdiLightbulbOn}
|
||||
></ha-svg-icon>
|
||||
${SHORT_TEXT}
|
||||
</ha-expansion-panel>
|
||||
`;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-components-ha-expansion-panel")
|
||||
|
@ -1,44 +0,0 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { html, css, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../src/components/ha-bar";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
|
||||
@customElement("demo-components-ha-spinner")
|
||||
export class DemoHaSpinner extends LitElement {
|
||||
@property({ attribute: false }) hass!: HomeAssistant;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<ha-card header="Basic spinner">
|
||||
<div class="card-content">
|
||||
<ha-spinner></ha-spinner></div
|
||||
></ha-card>
|
||||
<ha-card header="Different spinner sizes">
|
||||
<div class="card-content">
|
||||
<ha-spinner size="tiny"></ha-spinner>
|
||||
<ha-spinner size="small"></ha-spinner>
|
||||
<ha-spinner size="medium"></ha-spinner>
|
||||
<ha-spinner size="large"></ha-spinner></div
|
||||
></ha-card>
|
||||
<ha-card header="Spinner with an aria-label">
|
||||
<div class="card-content">
|
||||
<ha-spinner aria-label="Doing something..."></ha-spinner>
|
||||
<ha-spinner .ariaLabel=${"Doing something..."}></ha-spinner></div
|
||||
></ha-card>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-spinner": DemoHaSpinner;
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import type { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import type { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
@ -21,7 +21,7 @@ class HassioAddonConfigDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`<ha-spinner></ha-spinner>`;
|
||||
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
|
||||
}
|
||||
const hasConfiguration =
|
||||
(this.addon.options && Object.keys(this.addon.options).length) ||
|
||||
|
@ -113,9 +113,8 @@ class HassioAddonConfig extends LitElement {
|
||||
required: entry.required,
|
||||
selector: {
|
||||
text: {
|
||||
type: entry.format
|
||||
? entry.format
|
||||
: MASKED_FIELDS.includes(entry.name)
|
||||
type:
|
||||
entry.format || MASKED_FIELDS.includes(entry.name)
|
||||
? "password"
|
||||
: "text",
|
||||
},
|
||||
|
@ -2,7 +2,7 @@ import "../../../../src/components/ha-card";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
@ -33,7 +33,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`<ha-spinner></ha-spinner>`;
|
||||
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
|
@ -11,6 +11,7 @@ import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import { navigate } from "../../../src/common/navigate";
|
||||
import { extractSearchParam } from "../../../src/common/url/search-params";
|
||||
import "../../../src/components/ha-circular-progress";
|
||||
import type { HassioAddonDetails } from "../../../src/data/hassio/addon";
|
||||
import {
|
||||
fetchAddonInfo,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import type { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import type { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
@ -23,7 +23,7 @@ class HassioAddonInfoDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`<ha-spinner></ha-spinner>`;
|
||||
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
|
@ -1331,12 +1331,6 @@ class HassioAddonInfo extends LitElement {
|
||||
ha-alert mwc-button {
|
||||
--mdc-theme-primary: var(--primary-text-color);
|
||||
}
|
||||
|
||||
:host > ha-alert {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
type TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import type { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import type { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
@ -28,7 +28,9 @@ class HassioAddonLogDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html` <ha-spinner></ha-spinner> `;
|
||||
return html`
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<div class="search">
|
||||
|
@ -3,6 +3,7 @@ import type { TemplateResult } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/ha-circular-progress";
|
||||
import "../../../src/components/ha-file-upload";
|
||||
import type { HassioBackup } from "../../../src/data/hassio/backup";
|
||||
import { uploadBackup } from "../../../src/data/hassio/backup";
|
||||
|
@ -12,7 +12,6 @@ import "../../../../src/components/ha-md-dialog";
|
||||
import "../../../../src/components/ha-dialog-header";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import "../../../../src/components/ha-button";
|
||||
import "../../../../src/components/ha-button-menu";
|
||||
import "../../../../src/components/ha-header-bar";
|
||||
@ -139,7 +138,7 @@ class HassioBackupDialog
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: this._restoringBackup
|
||||
? html`<div class="loading">
|
||||
<ha-spinner></ha-spinner>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
</div>`
|
||||
: html`
|
||||
<supervisor-backup-content
|
||||
@ -311,6 +310,10 @@ class HassioBackupDialog
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-circular-progress {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
ha-header-bar {
|
||||
--mdc-theme-on-primary: var(--primary-text-color);
|
||||
--mdc-theme-primary: var(--mdc-theme-surface);
|
||||
|
@ -5,7 +5,6 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
import {
|
||||
createHassioFullBackup,
|
||||
@ -59,7 +58,7 @@ class HassioCreateBackupDialog extends LitElement {
|
||||
)}
|
||||
>
|
||||
${this._creatingBackup
|
||||
? html`<ha-spinner></ha-spinner>`
|
||||
? html`<ha-circular-progress indeterminate></ha-circular-progress>`
|
||||
: html`<supervisor-backup-content
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this._dialogParams.supervisor}
|
||||
@ -143,6 +142,10 @@ class HassioCreateBackupDialog extends LitElement {
|
||||
:host {
|
||||
direction: var(--direction);
|
||||
}
|
||||
ha-circular-progress {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-select";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import {
|
||||
@ -69,7 +69,12 @@ class HassioDatadiskDialog extends LitElement {
|
||||
?hideActions=${this.moving}
|
||||
>
|
||||
${this.moving
|
||||
? html`<ha-spinner aria-label="Moving" size="large"></ha-spinner>
|
||||
? html` <ha-circular-progress
|
||||
aria-label="Moving"
|
||||
size="large"
|
||||
indeterminate
|
||||
>
|
||||
</ha-circular-progress>
|
||||
<p class="progress-text">
|
||||
${this.dialogParams.supervisor.localize(
|
||||
"dialog.datadisk_move.moving_desc"
|
||||
@ -161,7 +166,7 @@ class HassioDatadiskDialog extends LitElement {
|
||||
ha-select {
|
||||
width: 100%;
|
||||
}
|
||||
ha-spinner {
|
||||
ha-circular-progress {
|
||||
display: block;
|
||||
margin: 32px;
|
||||
text-align: center;
|
||||
|
@ -16,14 +16,23 @@ import type { HomeAssistant } from "../../../../src/types";
|
||||
import type { HassioHardwareDialogParams } from "./show-dialog-hassio-hardware";
|
||||
|
||||
const _filterDevices = memoizeOne(
|
||||
(hardware: HassioHardwareInfo, filter: string, language: string) =>
|
||||
(
|
||||
showAdvanced: boolean,
|
||||
hardware: HassioHardwareInfo,
|
||||
filter: string,
|
||||
language: string
|
||||
) =>
|
||||
hardware.devices
|
||||
.filter(
|
||||
(device) =>
|
||||
device.by_id?.toLowerCase().includes(filter) ||
|
||||
device.name.toLowerCase().includes(filter) ||
|
||||
device.dev_path.toLocaleLowerCase().includes(filter) ||
|
||||
JSON.stringify(device.attributes).toLocaleLowerCase().includes(filter)
|
||||
(showAdvanced ||
|
||||
["tty", "gpio", "input"].includes(device.subsystem)) &&
|
||||
(device.by_id?.toLowerCase().includes(filter) ||
|
||||
device.name.toLowerCase().includes(filter) ||
|
||||
device.dev_path.toLocaleLowerCase().includes(filter) ||
|
||||
JSON.stringify(device.attributes)
|
||||
.toLocaleLowerCase()
|
||||
.includes(filter))
|
||||
)
|
||||
.sort((a, b) => stringCompare(a.name, b.name, language))
|
||||
);
|
||||
@ -51,6 +60,7 @@ class HassioHardwareDialog extends LitElement {
|
||||
}
|
||||
|
||||
const devices = _filterDevices(
|
||||
this.hass.userData?.showAdvanced || false,
|
||||
this._dialogParams.hardware,
|
||||
(this._filter || "").toLowerCase(),
|
||||
this.hass.locale.language
|
||||
|
@ -10,7 +10,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { cache } from "lit/directives/cache";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-expansion-panel";
|
||||
import "../../../../src/components/ha-formfield";
|
||||
@ -161,8 +161,12 @@ export class DialogHassioNetwork
|
||||
.disabled=${this._scanning}
|
||||
>
|
||||
${this._scanning
|
||||
? html`<ha-spinner aria-label="Scanning" size="small">
|
||||
</ha-spinner>`
|
||||
? html`<ha-circular-progress
|
||||
aria-label="Scanning"
|
||||
indeterminate
|
||||
size="small"
|
||||
>
|
||||
</ha-circular-progress>`
|
||||
: this.supervisor.localize("dialog.network.scan_ap")}
|
||||
</mwc-button>
|
||||
${this._accessPoints &&
|
||||
@ -278,7 +282,8 @@ export class DialogHassioNetwork
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._updateNetwork} .disabled=${!this._dirty}>
|
||||
${this._processing
|
||||
? html`<ha-spinner size="small"> </ha-spinner>`
|
||||
? html`<ha-circular-progress indeterminate size="small">
|
||||
</ha-circular-progress>`
|
||||
: this.supervisor.localize("common.save")}
|
||||
</mwc-button>
|
||||
</div>`;
|
||||
|
@ -8,7 +8,7 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { caseInsensitiveStringCompare } from "../../../../src/common/string/compare";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-tooltip";
|
||||
import "../../../../src/components/ha-spinner";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-icon-button";
|
||||
import type {
|
||||
@ -161,7 +161,10 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
></ha-textfield>
|
||||
<mwc-button @click=${this._addRepository}>
|
||||
${this._processing
|
||||
? html`<ha-spinner size="small"></ha-spinner>`
|
||||
? html`<ha-circular-progress
|
||||
indeterminate
|
||||
size="small"
|
||||
></ha-circular-progress>`
|
||||
: this._dialogParams!.supervisor.localize(
|
||||
"dialog.repositories.add"
|
||||
)}
|
||||
@ -199,7 +202,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
margin-inline-start: 8px;
|
||||
margin-inline-end: initial;
|
||||
}
|
||||
ha-spinner {
|
||||
ha-circular-progress {
|
||||
display: block;
|
||||
margin: 32px;
|
||||
text-align: center;
|
||||
|
@ -1,6 +1,11 @@
|
||||
// Compat needs to be first import
|
||||
import "../../src/resources/compatibility";
|
||||
import "./hassio-main";
|
||||
|
||||
import("../../src/resources/ha-style");
|
||||
import("@polymer/polymer/lib/utils/settings").then(
|
||||
({ setCancelSyntheticClickEvents }) => setCancelSyntheticClickEvents(false)
|
||||
);
|
||||
|
||||
const styleEl = document.createElement("style");
|
||||
styleEl.textContent = `
|
||||
|
@ -15,7 +15,6 @@ import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-alert";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-spinner";
|
||||
import "../../../src/components/ha-checkbox";
|
||||
import "../../../src/components/ha-faded";
|
||||
import "../../../src/components/ha-icon-button";
|
||||
@ -193,10 +192,12 @@ class UpdateAvailableCard extends LitElement {
|
||||
`
|
||||
: nothing}
|
||||
`
|
||||
: html`<ha-spinner
|
||||
: html`<ha-circular-progress
|
||||
aria-label="Updating"
|
||||
size="large"
|
||||
></ha-spinner>
|
||||
indeterminate
|
||||
>
|
||||
</ha-circular-progress>
|
||||
<p class="progress-text">
|
||||
${this.supervisor.localize("update_available.updating", {
|
||||
name: this._name,
|
||||
@ -464,7 +465,7 @@ class UpdateAvailableCard extends LitElement {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
ha-spinner {
|
||||
ha-circular-progress {
|
||||
display: block;
|
||||
margin: 32px;
|
||||
text-align: center;
|
||||
|
136
package.json
136
package.json
@ -26,31 +26,31 @@
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.27.0",
|
||||
"@babel/runtime": "7.26.9",
|
||||
"@braintree/sanitize-url": "7.1.1",
|
||||
"@codemirror/autocomplete": "6.18.6",
|
||||
"@codemirror/commands": "6.8.1",
|
||||
"@codemirror/language": "6.11.0",
|
||||
"@codemirror/legacy-modes": "6.5.0",
|
||||
"@codemirror/search": "6.5.10",
|
||||
"@codemirror/commands": "6.8.0",
|
||||
"@codemirror/language": "6.10.8",
|
||||
"@codemirror/legacy-modes": "6.4.3",
|
||||
"@codemirror/search": "6.5.9",
|
||||
"@codemirror/state": "6.5.2",
|
||||
"@codemirror/view": "6.36.5",
|
||||
"@codemirror/view": "6.36.3",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.18.0",
|
||||
"@formatjs/intl-displaynames": "6.8.11",
|
||||
"@formatjs/intl-durationformat": "0.7.4",
|
||||
"@formatjs/intl-getcanonicallocales": "2.5.5",
|
||||
"@formatjs/intl-listformat": "7.7.11",
|
||||
"@formatjs/intl-locale": "4.2.11",
|
||||
"@formatjs/intl-numberformat": "8.15.4",
|
||||
"@formatjs/intl-pluralrules": "5.4.4",
|
||||
"@formatjs/intl-relativetimeformat": "11.4.11",
|
||||
"@fullcalendar/core": "6.1.17",
|
||||
"@fullcalendar/daygrid": "6.1.17",
|
||||
"@fullcalendar/interaction": "6.1.17",
|
||||
"@fullcalendar/list": "6.1.17",
|
||||
"@fullcalendar/luxon3": "6.1.17",
|
||||
"@fullcalendar/timegrid": "6.1.17",
|
||||
"@formatjs/intl-datetimeformat": "6.17.3",
|
||||
"@formatjs/intl-displaynames": "6.8.10",
|
||||
"@formatjs/intl-durationformat": "0.7.3",
|
||||
"@formatjs/intl-getcanonicallocales": "2.5.4",
|
||||
"@formatjs/intl-listformat": "7.7.10",
|
||||
"@formatjs/intl-locale": "4.2.10",
|
||||
"@formatjs/intl-numberformat": "8.15.3",
|
||||
"@formatjs/intl-pluralrules": "5.4.3",
|
||||
"@formatjs/intl-relativetimeformat": "11.4.10",
|
||||
"@fullcalendar/core": "6.1.15",
|
||||
"@fullcalendar/daygrid": "6.1.15",
|
||||
"@fullcalendar/interaction": "6.1.15",
|
||||
"@fullcalendar/list": "6.1.15",
|
||||
"@fullcalendar/luxon3": "6.1.15",
|
||||
"@fullcalendar/timegrid": "6.1.15",
|
||||
"@lezer/highlight": "1.2.1",
|
||||
"@lit-labs/context": "0.4.1",
|
||||
"@lit-labs/motion": "1.0.8",
|
||||
@ -81,25 +81,27 @@
|
||||
"@material/mwc-top-app-bar": "0.27.0",
|
||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/web": "2.3.0",
|
||||
"@material/web": "2.2.0",
|
||||
"@mdi/js": "7.4.47",
|
||||
"@mdi/svg": "7.4.47",
|
||||
"@polymer/paper-item": "3.0.1",
|
||||
"@polymer/paper-listbox": "3.0.1",
|
||||
"@polymer/paper-tabs": "3.1.0",
|
||||
"@polymer/polymer": "3.5.2",
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@shoelace-style/shoelace": "2.20.1",
|
||||
"@shoelace-style/shoelace": "2.20.0",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@tsparticles/engine": "3.8.1",
|
||||
"@tsparticles/preset-links": "3.2.0",
|
||||
"@vaadin/combo-box": "24.7.3",
|
||||
"@vaadin/vaadin-themable-mixin": "24.7.3",
|
||||
"@vaadin/combo-box": "24.6.5",
|
||||
"@vaadin/vaadin-themable-mixin": "24.6.5",
|
||||
"@vibrant/color": "4.0.0",
|
||||
"@vue/web-component-wrapper": "1.3.0",
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.9",
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
"app-datepicker": "5.1.1",
|
||||
"barcode-detector": "3.0.1",
|
||||
"barcode-detector": "3.0.0",
|
||||
"color-name": "2.0.0",
|
||||
"comlink": "4.4.2",
|
||||
"core-js": "3.41.0",
|
||||
"core-js": "3.40.0",
|
||||
"cropperjs": "1.6.2",
|
||||
"date-fns": "4.1.0",
|
||||
"date-fns-tz": "3.2.0",
|
||||
@ -107,22 +109,22 @@
|
||||
"deep-freeze": "0.0.1",
|
||||
"dialog-polyfill": "0.5.6",
|
||||
"echarts": "5.6.0",
|
||||
"element-internals-polyfill": "3.0.2",
|
||||
"element-internals-polyfill": "1.3.13",
|
||||
"fuse.js": "7.1.0",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"gulp-zopfli-green": "6.0.2",
|
||||
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
|
||||
"home-assistant-js-websocket": "9.5.0",
|
||||
"home-assistant-js-websocket": "9.4.0",
|
||||
"idb-keyval": "6.2.1",
|
||||
"intl-messageformat": "10.7.16",
|
||||
"intl-messageformat": "10.7.15",
|
||||
"js-yaml": "4.1.0",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
|
||||
"leaflet.markercluster": "1.5.3",
|
||||
"lit": "2.8.0",
|
||||
"lit-html": "2.8.0",
|
||||
"luxon": "3.6.1",
|
||||
"marked": "15.0.8",
|
||||
"luxon": "3.5.0",
|
||||
"marked": "15.0.7",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "4.0.3",
|
||||
"object-hash": "3.0.0",
|
||||
@ -135,7 +137,9 @@
|
||||
"stacktrace-js": "2.0.2",
|
||||
"superstruct": "2.0.2",
|
||||
"tinykeys": "3.0.0",
|
||||
"ua-parser-js": "2.0.3",
|
||||
"tsparticles-engine": "2.12.0",
|
||||
"tsparticles-preset-links": "2.12.0",
|
||||
"ua-parser-js": "2.0.2",
|
||||
"vis-data": "7.1.9",
|
||||
"vis-network": "9.1.9",
|
||||
"vue": "2.7.16",
|
||||
@ -150,20 +154,20 @@
|
||||
"xss": "1.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.26.10",
|
||||
"@babel/helper-define-polyfill-provider": "0.6.4",
|
||||
"@babel/core": "7.26.9",
|
||||
"@babel/helper-define-polyfill-provider": "0.6.3",
|
||||
"@babel/plugin-proposal-decorators": "7.25.9",
|
||||
"@babel/plugin-transform-runtime": "7.26.10",
|
||||
"@babel/plugin-transform-runtime": "7.26.9",
|
||||
"@babel/preset-env": "7.26.9",
|
||||
"@babel/preset-typescript": "7.27.0",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.19.1",
|
||||
"@lokalise/node-api": "14.3.0",
|
||||
"@octokit/auth-oauth-device": "7.1.5",
|
||||
"@octokit/plugin-retry": "7.2.1",
|
||||
"@babel/preset-typescript": "7.26.0",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.18.2",
|
||||
"@lokalise/node-api": "13.2.1",
|
||||
"@octokit/auth-oauth-device": "7.1.3",
|
||||
"@octokit/plugin-retry": "7.1.4",
|
||||
"@octokit/rest": "21.1.1",
|
||||
"@rsdoctor/rspack-plugin": "1.0.1",
|
||||
"@rspack/cli": "1.3.5",
|
||||
"@rspack/core": "1.3.5",
|
||||
"@rsdoctor/rspack-plugin": "0.4.13",
|
||||
"@rspack/cli": "1.2.5",
|
||||
"@rspack/core": "1.2.5",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.21",
|
||||
"@types/chromecast-caf-sender": "1.0.11",
|
||||
@ -171,31 +175,31 @@
|
||||
"@types/glob": "8.1.0",
|
||||
"@types/html-minifier-terser": "7.0.2",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/leaflet": "1.9.17",
|
||||
"@types/leaflet": "1.9.16",
|
||||
"@types/leaflet-draw": "1.0.11",
|
||||
"@types/leaflet.markercluster": "1.5.5",
|
||||
"@types/lodash.merge": "4.6.9",
|
||||
"@types/luxon": "3.6.2",
|
||||
"@types/luxon": "3.4.2",
|
||||
"@types/mocha": "10.0.10",
|
||||
"@types/qrcode": "1.5.5",
|
||||
"@types/sortablejs": "1.15.8",
|
||||
"@types/tar": "6.1.13",
|
||||
"@types/ua-parser-js": "0.7.39",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@vitest/coverage-v8": "3.1.1",
|
||||
"babel-loader": "10.0.0",
|
||||
"@vitest/coverage-v8": "3.0.6",
|
||||
"babel-loader": "9.2.1",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"browserslist-useragent-regexp": "4.1.3",
|
||||
"del": "8.0.0",
|
||||
"eslint": "9.24.0",
|
||||
"eslint": "9.21.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-prettier": "10.1.2",
|
||||
"eslint-config-prettier": "10.0.1",
|
||||
"eslint-import-resolver-webpack": "0.13.10",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-lit": "2.1.1",
|
||||
"eslint-plugin-lit": "1.15.0",
|
||||
"eslint-plugin-lit-a11y": "4.1.4",
|
||||
"eslint-plugin-unused-imports": "4.1.4",
|
||||
"eslint-plugin-wc": "3.0.0",
|
||||
"eslint-plugin-wc": "2.2.1",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.3.0",
|
||||
"glob": "11.0.1",
|
||||
@ -205,38 +209,40 @@
|
||||
"gulp-rename": "2.0.0",
|
||||
"html-minifier-terser": "7.2.0",
|
||||
"husky": "9.1.7",
|
||||
"jsdom": "26.1.0",
|
||||
"jsdom": "26.0.0",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "15.5.1",
|
||||
"lint-staged": "15.4.3",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.5.0",
|
||||
"map-stream": "0.0.7",
|
||||
"pinst": "3.0.0",
|
||||
"prettier": "3.5.3",
|
||||
"prettier": "3.5.2",
|
||||
"rspack-manifest-plugin": "5.0.3",
|
||||
"serve": "14.2.4",
|
||||
"sinon": "20.0.0",
|
||||
"sinon": "19.0.2",
|
||||
"tar": "7.4.3",
|
||||
"terser-webpack-plugin": "5.3.14",
|
||||
"terser-webpack-plugin": "5.3.11",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "8.30.1",
|
||||
"typescript": "5.7.3",
|
||||
"typescript-eslint": "8.24.1",
|
||||
"vite-tsconfig-paths": "5.1.4",
|
||||
"vitest": "3.1.1",
|
||||
"vitest": "3.0.6",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
"webpackbar": "7.0.0",
|
||||
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
|
||||
},
|
||||
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
||||
"resolutions": {
|
||||
"@polymer/polymer": "patch:@polymer/polymer@3.5.2#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
||||
"@material/mwc-button@^0.25.3": "^0.27.0",
|
||||
"lit": "2.8.0",
|
||||
"lit-html": "2.8.0",
|
||||
"clean-css": "5.3.3",
|
||||
"@lit/reactive-element": "1.6.3",
|
||||
"@fullcalendar/daygrid": "6.1.17",
|
||||
"@fullcalendar/daygrid": "6.1.15",
|
||||
"globals": "16.0.0",
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"packageManager": "yarn@4.9.1"
|
||||
"packageManager": "yarn@4.6.0"
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M76.9105 39.4999C77.739 39.4999 78.4105 38.8283 78.4105 37.9999C78.4105 37.1715 77.739 36.4999 76.9105 36.4999V39.4999ZM37.5 39.4999L76.9105 39.4999V36.4999L37.5 36.4999L37.5 39.4999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M30.8239 22.3365L38.8239 38.8365L30.3239 50.3365" stroke="black" stroke-opacity="0.12" stroke-width="3" stroke-linecap="round"/>
|
||||
<mask id="mask0_1110_23734" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="30" y="27" width="18" height="18">
|
||||
<path d="M45.75 42.075C45.75 42.4462 45.4462 42.75 45.075 42.75H32.925C32.5538 42.75 32.25 42.4462 32.25 42.075V36.675C32.25 36.3037 32.4649 35.7851 32.7276 35.5224L38.5224 29.7275C38.7851 29.4649 39.2143 29.4649 39.477 29.7275L45.2724 35.523C45.5351 35.7857 45.75 36.3043 45.75 36.6755V42.075Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1110_23734)">
|
||||
<rect x="30" y="27" width="18" height="18" fill="#212121"/>
|
||||
</g>
|
||||
<path d="M82 37.9999C82 36.343 83.3431 34.9999 85 34.9999C86.6569 34.9999 88 36.343 88 37.9999C88 39.6567 86.6569 40.9999 85 40.9999C83.3431 40.9999 82 39.6567 82 37.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
<rect x="23" y="11" width="8" height="8" rx="4" fill="black" fill-opacity="0.32"/>
|
||||
<rect x="22" y="52" width="8" height="8" rx="4" fill="black" fill-opacity="0.32"/>
|
||||
<path d="M21.5715 19.5C17.4983 23.801 15 29.6087 15 36C15 41.9085 17.1351 47.3183 20.6759 51.5" stroke="black" stroke-opacity="0.12" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle cx="39" cy="36" r="33.5" stroke="black" stroke-opacity="0.12"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.6 KiB |
@ -1,15 +0,0 @@
|
||||
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M30.824 22.3365L38.824 38.8365L30.324 50.3365" stroke="white" stroke-opacity="0.24" stroke-width="3" stroke-linecap="round"/>
|
||||
<mask id="mask0_1180_4955" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="30" y="27" width="18" height="18">
|
||||
<path d="M45.75 42.075C45.75 42.4462 45.4462 42.75 45.075 42.75H32.925C32.5538 42.75 32.25 42.4462 32.25 42.075V36.675C32.25 36.3037 32.4649 35.7851 32.7276 35.5224L38.5224 29.7275C38.7851 29.4649 39.2143 29.4649 39.477 29.7275L45.2724 35.523C45.5351 35.7857 45.75 36.3043 45.75 36.6755V42.075Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1180_4955)">
|
||||
<rect x="30" y="27" width="18" height="18" fill="#00AFFF"/>
|
||||
</g>
|
||||
<path d="M76.9105 39.4999C77.739 39.4999 78.4105 38.8283 78.4105 37.9999C78.4105 37.1715 77.739 36.4999 76.9105 36.4999V39.4999ZM37.5 39.4999L76.9105 39.4999V36.4999L37.5 36.4999L37.5 39.4999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M82 37.9999C82 36.343 83.3431 34.9999 85 34.9999C86.6569 34.9999 88 36.343 88 37.9999C88 39.6567 86.6569 40.9999 85 40.9999C83.3431 40.9999 82 39.6567 82 37.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
<rect x="23" y="11" width="8" height="8" rx="4" fill="white" fill-opacity="0.48"/>
|
||||
<rect x="22" y="52" width="8" height="8" rx="4" fill="white" fill-opacity="0.48"/>
|
||||
<path d="M21.5715 19.5C17.4983 23.801 15 29.6087 15 36C15 41.9085 17.1351 47.3183 20.6759 51.5" stroke="white" stroke-opacity="0.24" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle cx="39" cy="36" r="33.5" stroke="white" stroke-opacity="0.24"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.6 KiB |
@ -1,19 +0,0 @@
|
||||
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M63.1358 38.5084C63.9608 38.4334 64.5688 37.7037 64.4938 36.8787C64.4188 36.0537 63.6892 35.4457 62.8642 35.5207L63.1358 38.5084ZM46.6358 40.0084L63.1358 38.5084L62.8642 35.5207L46.3642 37.0207L46.6358 40.0084Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M38.5 22L45.9722 37.4115C46.2967 38.0807 46.223 38.8747 45.781 39.4728L38 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<circle cx="47" cy="36" r="34" fill="white"/>
|
||||
<circle cx="47" cy="36" r="33.5" stroke="black" stroke-opacity="0.12"/>
|
||||
<path d="M41.8777 12.5216C43.4905 12.1798 45.1631 12 46.8777 12C58.2401 12 67.7582 19.8959 70.2445 30.5M40 59C42.1788 59.6506 44.4874 60 46.8777 60C56.9498 60 65.5728 53.7955 69.1332 45" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M38.5 22L45.9722 37.4115C46.2967 38.0807 46.223 38.8747 45.781 39.4728L38 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<mask id="mask0_1110_23775" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="38" y="27" width="18" height="18">
|
||||
<path d="M53.75 42.075C53.75 42.4462 53.4462 42.75 53.075 42.75H40.925C40.5538 42.75 40.25 42.4462 40.25 42.075V36.675C40.25 36.3037 40.4649 35.7851 40.7276 35.5224L46.5224 29.7275C46.7851 29.4649 47.2143 29.4649 47.477 29.7275L53.2724 35.523C53.5351 35.7857 53.75 36.3043 53.75 36.6755V42.075Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1110_23775)">
|
||||
<rect x="38" y="27" width="18" height="18" fill="#212121"/>
|
||||
</g>
|
||||
<path d="M63.5 39.4999C64.3284 39.4999 65 38.8283 65 37.9999C65 37.1715 64.3284 36.4999 63.5 36.4999L63.5 39.4999ZM49.5 39.4999L63.5 39.4999L63.5 36.4999L49.5 36.4999L49.5 39.4999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<rect x="31" y="11" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<rect x="30" y="52" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<path d="M29.5715 19.5C25.4983 23.801 23 29.6087 23 36C23 41.9085 25.1351 47.3183 28.6759 51.5" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M68 37.9999C68 36.343 69.3431 34.9999 71 34.9999C72.6569 34.9999 74 36.343 74 37.9999C74 39.6567 72.6569 40.9999 71 40.9999C69.3431 40.9999 68 39.6567 68 37.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.4 KiB |
@ -1,19 +0,0 @@
|
||||
<svg width="94" height="72" viewBox="0 0 94 72" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M63.1358 38.5084C63.9608 38.4334 64.5688 37.7037 64.4938 36.8787C64.4188 36.0537 63.6892 35.4457 62.8642 35.5207L63.1358 38.5084ZM46.6358 40.0084L63.1358 38.5084L62.8642 35.5207L46.3642 37.0207L46.6358 40.0084Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<path d="M38.5 22L45.9722 37.4115C46.2967 38.0807 46.223 38.8747 45.781 39.4728L38 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<circle cx="47" cy="36" r="34" fill="#1C1C1C"/>
|
||||
<circle cx="47" cy="36" r="33.5" stroke="white" stroke-opacity="0.24"/>
|
||||
<path d="M41.8777 12.5216C43.4905 12.1798 45.1631 12 46.8777 12C58.2401 12 67.7582 19.8959 70.2445 30.5M40 59C42.1788 59.6506 44.4874 60 46.8777 60C56.9498 60 65.5728 53.7955 69.1332 45" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M38.5 22L45.9722 37.4115C46.2967 38.0807 46.223 38.8747 45.781 39.4728L38 50" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<mask id="mask0_1180_4965" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="38" y="27" width="18" height="18">
|
||||
<path d="M53.75 42.075C53.75 42.4462 53.4462 42.75 53.075 42.75H40.925C40.5538 42.75 40.25 42.4462 40.25 42.075V36.675C40.25 36.3037 40.4649 35.7851 40.7276 35.5224L46.5224 29.7275C46.7851 29.4649 47.2143 29.4649 47.477 29.7275L53.2724 35.523C53.5351 35.7857 53.75 36.3043 53.75 36.6755V42.075Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1180_4965)">
|
||||
<rect x="38" y="27" width="18" height="18" fill="#00AFFF"/>
|
||||
</g>
|
||||
<path d="M63.5 39.4999C64.3284 39.4999 65 38.8283 65 37.9999C65 37.1715 64.3284 36.4999 63.5 36.4999L63.5 39.4999ZM49.5 39.4999L63.5 39.4999L63.5 36.4999L49.5 36.4999L49.5 39.4999Z" fill="#00AFFF" fill-opacity="0.3"/>
|
||||
<rect x="31" y="11" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<rect x="30" y="52" width="8" height="8" rx="4" fill="#00AFFF" fill-opacity="0.6"/>
|
||||
<path d="M29.5715 19.5C25.4983 23.801 23 29.6087 23 36C23 41.9085 25.1351 47.3183 28.6759 51.5" stroke="#00AFFF" stroke-opacity="0.3" stroke-width="3" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||
<path d="M68 37.9999C68 36.343 69.3431 34.9999 71 34.9999C72.6569 34.9999 74 36.343 74 37.9999C74 39.6567 72.6569 40.9999 71 40.9999C69.3431 40.9999 68 39.6567 68 37.9999Z" stroke="#00AFFF" stroke-width="2"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.4 KiB |
@ -1,12 +1,11 @@
|
||||
[build-system]
|
||||
requires = ["setuptools~=77.0"]
|
||||
requires = ["setuptools~=75.1"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20250326.0"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*"]
|
||||
version = "20250312.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
@ -18,6 +17,8 @@ requires-python = ">=3.13.0"
|
||||
"Homepage" = "https://github.com/home-assistant/frontend"
|
||||
|
||||
[tool.setuptools]
|
||||
platforms = ["any"]
|
||||
zip-safe = false
|
||||
include-package-data = true
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
|
@ -37,7 +37,7 @@
|
||||
{
|
||||
"description": "Group tsparticles engine and presets",
|
||||
"groupName": "tsparticles",
|
||||
"matchPackageNames": ["@tsparticles/engine", "@tsparticles/preset-{/,}**"]
|
||||
"matchPackageNames": ["tsparticles-engine", "tsparticles-preset-{/,}**"]
|
||||
},
|
||||
{
|
||||
"description": "Group date-fns with dependent timezone package",
|
||||
|
@ -265,10 +265,7 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
window.innerWidth > 450 &&
|
||||
!matchMedia("(prefers-reduced-motion)").matches
|
||||
) {
|
||||
if (window.innerWidth > 450) {
|
||||
import("../resources/particles");
|
||||
}
|
||||
|
||||
|
@ -132,15 +132,6 @@ export const hs2rgb = (hs: [number, number]): [number, number, number] =>
|
||||
|
||||
export function theme2hex(themeColor: string): string {
|
||||
if (themeColor.startsWith("#")) {
|
||||
if (themeColor.length === 4 || themeColor.length === 5) {
|
||||
const c = themeColor;
|
||||
// Convert short-form hex (#abc) to 6 digit (#aabbcc). Ignore alpha channel.
|
||||
return `#${c[1]}${c[1]}${c[2]}${c[2]}${c[3]}${c[3]}`;
|
||||
}
|
||||
if (themeColor.length === 9) {
|
||||
// Ignore alpha channel.
|
||||
return themeColor.substring(0, 7);
|
||||
}
|
||||
return themeColor;
|
||||
}
|
||||
|
||||
|
@ -6,10 +6,6 @@ import {
|
||||
differenceInMilliseconds,
|
||||
differenceInMonths,
|
||||
endOfMonth,
|
||||
startOfDay,
|
||||
endOfDay,
|
||||
differenceInDays,
|
||||
addDays,
|
||||
} from "date-fns";
|
||||
import { toZonedTime, fromZonedTime } from "date-fns-tz";
|
||||
import type { HassConfig } from "home-assistant-js-websocket";
|
||||
@ -104,32 +100,6 @@ export const shiftDateRange = (
|
||||
locale,
|
||||
config
|
||||
);
|
||||
} else if (
|
||||
calcDateProperty(
|
||||
startDate,
|
||||
(date) => startOfDay(date).getMilliseconds() === date.getMilliseconds(),
|
||||
locale,
|
||||
config
|
||||
) &&
|
||||
calcDateProperty(
|
||||
endDate,
|
||||
(date) => endOfDay(date).getMilliseconds() === date.getMilliseconds(),
|
||||
locale,
|
||||
config
|
||||
)
|
||||
) {
|
||||
const difference =
|
||||
((calcDateDifferenceProperty(
|
||||
endDate,
|
||||
startDate,
|
||||
differenceInDays,
|
||||
locale,
|
||||
config
|
||||
) as number) +
|
||||
1) *
|
||||
(forward ? 1 : -1);
|
||||
start = calcDate(startDate, addDays, locale, config, difference);
|
||||
end = calcDate(endDate, addDays, locale, config, difference);
|
||||
} else {
|
||||
const difference =
|
||||
((calcDateDifferenceProperty(
|
||||
|
@ -1,116 +0,0 @@
|
||||
import {
|
||||
addDays,
|
||||
subHours,
|
||||
endOfDay,
|
||||
endOfMonth,
|
||||
endOfWeek,
|
||||
endOfYear,
|
||||
startOfDay,
|
||||
startOfMonth,
|
||||
startOfWeek,
|
||||
startOfYear,
|
||||
startOfQuarter,
|
||||
endOfQuarter,
|
||||
subDays,
|
||||
subMonths,
|
||||
} from "date-fns";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { calcDate } from "./calc_date";
|
||||
import { firstWeekdayIndex } from "./first_weekday";
|
||||
|
||||
export type DateRange =
|
||||
| "today"
|
||||
| "yesterday"
|
||||
| "this_week"
|
||||
| "this_month"
|
||||
| "this_quarter"
|
||||
| "this_year"
|
||||
| "now-7d"
|
||||
| "now-30d"
|
||||
| "now-12m"
|
||||
| "now-1h"
|
||||
| "now-12h"
|
||||
| "now-24h";
|
||||
|
||||
export const calcDateRange = (
|
||||
hass: HomeAssistant,
|
||||
range: DateRange
|
||||
): [Date, Date] => {
|
||||
const today = new Date();
|
||||
const weekStartsOn = firstWeekdayIndex(hass.locale);
|
||||
switch (range) {
|
||||
case "today":
|
||||
return [
|
||||
calcDate(today, startOfDay, hass.locale, hass.config, {
|
||||
weekStartsOn,
|
||||
}),
|
||||
calcDate(today, endOfDay, hass.locale, hass.config, {
|
||||
weekStartsOn,
|
||||
}),
|
||||
];
|
||||
case "yesterday":
|
||||
return [
|
||||
calcDate(addDays(today, -1), startOfDay, hass.locale, hass.config, {
|
||||
weekStartsOn,
|
||||
}),
|
||||
calcDate(addDays(today, -1), endOfDay, hass.locale, hass.config, {
|
||||
weekStartsOn,
|
||||
}),
|
||||
];
|
||||
case "this_week":
|
||||
return [
|
||||
calcDate(today, startOfWeek, hass.locale, hass.config, {
|
||||
weekStartsOn,
|
||||
}),
|
||||
calcDate(today, endOfWeek, hass.locale, hass.config, {
|
||||
weekStartsOn,
|
||||
}),
|
||||
];
|
||||
case "this_month":
|
||||
return [
|
||||
calcDate(today, startOfMonth, hass.locale, hass.config),
|
||||
calcDate(today, endOfMonth, hass.locale, hass.config),
|
||||
];
|
||||
case "this_quarter":
|
||||
return [
|
||||
calcDate(today, startOfQuarter, hass.locale, hass.config),
|
||||
calcDate(today, endOfQuarter, hass.locale, hass.config),
|
||||
];
|
||||
case "this_year":
|
||||
return [
|
||||
calcDate(today, startOfYear, hass.locale, hass.config),
|
||||
calcDate(today, endOfYear, hass.locale, hass.config),
|
||||
];
|
||||
case "now-7d":
|
||||
return [
|
||||
calcDate(today, subDays, hass.locale, hass.config, 7),
|
||||
calcDate(today, subDays, hass.locale, hass.config, 0),
|
||||
];
|
||||
case "now-30d":
|
||||
return [
|
||||
calcDate(today, subDays, hass.locale, hass.config, 30),
|
||||
calcDate(today, subDays, hass.locale, hass.config, 0),
|
||||
];
|
||||
case "now-12m":
|
||||
return [
|
||||
calcDate(subMonths(today, 12), startOfMonth, hass.locale, hass.config),
|
||||
calcDate(subMonths(today, 1), endOfMonth, hass.locale, hass.config),
|
||||
];
|
||||
case "now-1h":
|
||||
return [
|
||||
calcDate(today, subHours, hass.locale, hass.config, 1),
|
||||
calcDate(today, subHours, hass.locale, hass.config, 0),
|
||||
];
|
||||
case "now-12h":
|
||||
return [
|
||||
calcDate(today, subHours, hass.locale, hass.config, 12),
|
||||
calcDate(today, subHours, hass.locale, hass.config, 0),
|
||||
];
|
||||
case "now-24h":
|
||||
return [
|
||||
calcDate(today, subHours, hass.locale, hass.config, 24),
|
||||
calcDate(today, subHours, hass.locale, hass.config, 0),
|
||||
];
|
||||
}
|
||||
return [today, today];
|
||||
};
|
@ -134,7 +134,10 @@ export const applyThemesOnElement = (
|
||||
element.__themes = { cacheKey, keys: newTheme?.keys };
|
||||
|
||||
// Set and/or reset styles
|
||||
if (window.ShadyCSS) {
|
||||
if (element.updateStyles) {
|
||||
// Use updateStyles() method of Polymer elements
|
||||
element.updateStyles(styles);
|
||||
} else if (window.ShadyCSS) {
|
||||
// Use ShadyCSS if available
|
||||
window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ element, styles);
|
||||
} else {
|
||||
|
@ -1,4 +0,0 @@
|
||||
import type { AreaRegistryEntry } from "../../data/area_registry";
|
||||
|
||||
export const computeAreaName = (area: AreaRegistryEntry): string | undefined =>
|
||||
area.name?.trim();
|
@ -34,7 +34,7 @@ export const computeAttributeValueDisplay = (
|
||||
value !== undefined ? value : stateObj.attributes[attribute];
|
||||
|
||||
// Null value, the state is unknown
|
||||
if (attributeValue === null || attributeValue === undefined) {
|
||||
if (attributeValue === null) {
|
||||
return localize("state.default.unknown");
|
||||
}
|
||||
|
||||
|
@ -1,50 +0,0 @@
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
} from "../../data/entity_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { computeStateName } from "./compute_state_name";
|
||||
import { getDuplicates } from "../string/get_duplicates";
|
||||
|
||||
export const computeDeviceName = (
|
||||
device: DeviceRegistryEntry
|
||||
): string | undefined => (device.name_by_user || device.name)?.trim();
|
||||
|
||||
export const computeDeviceNameDisplay = (
|
||||
device: DeviceRegistryEntry,
|
||||
hass: HomeAssistant,
|
||||
entities?: EntityRegistryEntry[] | EntityRegistryDisplayEntry[] | string[]
|
||||
) =>
|
||||
computeDeviceName(device) ||
|
||||
(entities && fallbackDeviceName(hass, entities)) ||
|
||||
hass.localize("ui.panel.config.devices.unnamed_device", {
|
||||
type: hass.localize(
|
||||
`ui.panel.config.devices.type.${device.entry_type || "device"}`
|
||||
),
|
||||
});
|
||||
|
||||
export const fallbackDeviceName = (
|
||||
hass: HomeAssistant,
|
||||
entities: EntityRegistryEntry[] | EntityRegistryDisplayEntry[] | string[]
|
||||
) => {
|
||||
for (const entity of entities || []) {
|
||||
const entityId = typeof entity === "string" ? entity : entity.entity_id;
|
||||
const stateObj = hass.states[entityId];
|
||||
if (stateObj) {
|
||||
return computeStateName(stateObj);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getDuplicatedDeviceNames = memoizeOne(
|
||||
(devices: HomeAssistant["devices"]): Set<string> => {
|
||||
const names = Object.values(devices)
|
||||
.map((device) => computeDeviceName(device))
|
||||
.filter((name): name is string => name !== undefined);
|
||||
|
||||
return getDuplicates(names);
|
||||
}
|
||||
);
|
@ -1,59 +0,0 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
} from "../../data/entity_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { computeDeviceName } from "./compute_device_name";
|
||||
import { computeStateName } from "./compute_state_name";
|
||||
import { stripPrefixFromEntityName } from "./strip_prefix_from_entity_name";
|
||||
|
||||
export const computeEntityName = (
|
||||
stateObj: HassEntity,
|
||||
hass: HomeAssistant
|
||||
): string | undefined => {
|
||||
const entry = hass.entities[stateObj.entity_id] as
|
||||
| EntityRegistryDisplayEntry
|
||||
| undefined;
|
||||
|
||||
if (!entry) {
|
||||
// Fall back to state name if not in the entity registry (friendly name)
|
||||
return computeStateName(stateObj);
|
||||
}
|
||||
return computeEntityEntryName(entry, hass);
|
||||
};
|
||||
|
||||
export const computeEntityEntryName = (
|
||||
entry: EntityRegistryDisplayEntry | EntityRegistryEntry,
|
||||
hass: HomeAssistant
|
||||
): string | undefined => {
|
||||
const name =
|
||||
entry.name || ("original_name" in entry ? entry.original_name : undefined);
|
||||
|
||||
const device = entry.device_id ? hass.devices[entry.device_id] : undefined;
|
||||
|
||||
if (!device) {
|
||||
if (name) {
|
||||
return name;
|
||||
}
|
||||
const stateObj = hass.states[entry.entity_id] as HassEntity | undefined;
|
||||
if (stateObj) {
|
||||
return computeStateName(stateObj);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const deviceName = computeDeviceName(device);
|
||||
|
||||
// If the device name is the same as the entity name, consider empty entity name
|
||||
if (deviceName === name) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Remove the device name from the entity name if it starts with it
|
||||
if (deviceName && name) {
|
||||
return stripPrefixFromEntityName(name, deviceName) || name;
|
||||
}
|
||||
|
||||
return name;
|
||||
};
|
@ -1,4 +0,0 @@
|
||||
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
||||
|
||||
export const computeFloorName = (floor: FloorRegistryEntry): string =>
|
||||
floor.name?.trim();
|
@ -1,30 +0,0 @@
|
||||
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import type { FloorRegistryEntry } from "../../../data/floor_registry";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
interface AreaContext {
|
||||
area: AreaRegistryEntry | null;
|
||||
floor: FloorRegistryEntry | null;
|
||||
}
|
||||
|
||||
export const getAreaContext = (
|
||||
areaId: string,
|
||||
hass: HomeAssistant
|
||||
): AreaContext => {
|
||||
const area = (hass.areas[areaId] as AreaRegistryEntry | undefined) || null;
|
||||
|
||||
if (!area) {
|
||||
return {
|
||||
area: null,
|
||||
floor: null,
|
||||
};
|
||||
}
|
||||
|
||||
const floorId = area?.floor_id;
|
||||
const floor = floorId ? hass.floors[floorId] : null;
|
||||
|
||||
return {
|
||||
area: area,
|
||||
floor: floor,
|
||||
};
|
||||
};
|
@ -1,43 +0,0 @@
|
||||
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||
import type { EntityRegistryDisplayEntry } from "../../../data/entity_registry";
|
||||
import type { FloorRegistryEntry } from "../../../data/floor_registry";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
interface EntityContext {
|
||||
entity: EntityRegistryDisplayEntry | null;
|
||||
device: DeviceRegistryEntry | null;
|
||||
area: AreaRegistryEntry | null;
|
||||
floor: FloorRegistryEntry | null;
|
||||
}
|
||||
|
||||
export const getEntityContext = (
|
||||
entityId: string,
|
||||
hass: HomeAssistant
|
||||
): EntityContext => {
|
||||
const entity =
|
||||
(hass.entities[entityId] as EntityRegistryDisplayEntry | undefined) || null;
|
||||
|
||||
if (!entity) {
|
||||
return {
|
||||
entity: null,
|
||||
device: null,
|
||||
area: null,
|
||||
floor: null,
|
||||
};
|
||||
}
|
||||
|
||||
const deviceId = entity?.device_id;
|
||||
const device = deviceId ? hass.devices[deviceId] : null;
|
||||
const areaId = entity?.area_id || device?.area_id;
|
||||
const area = areaId ? hass.areas[areaId] : null;
|
||||
const floorId = area?.floor_id;
|
||||
const floor = floorId ? hass.floors[floorId] : null;
|
||||
|
||||
return {
|
||||
entity: entity,
|
||||
device: device,
|
||||
area: area,
|
||||
floor: floor,
|
||||
};
|
||||
};
|
@ -1,78 +0,0 @@
|
||||
import { computeDomain } from "./compute_domain";
|
||||
|
||||
export type EntityDomainFilterFunc = (entityId: string) => boolean;
|
||||
|
||||
export interface EntityDomainFilter {
|
||||
include_domains: string[];
|
||||
include_entities: string[];
|
||||
exclude_domains: string[];
|
||||
exclude_entities: string[];
|
||||
}
|
||||
|
||||
export const isEmptyEntityDomainFilter = (filter: EntityDomainFilter) =>
|
||||
filter.include_domains.length +
|
||||
filter.include_entities.length +
|
||||
filter.exclude_domains.length +
|
||||
filter.exclude_entities.length ===
|
||||
0;
|
||||
|
||||
export const generateEntityDomainFilter = (
|
||||
includeDomains?: string[],
|
||||
includeEntities?: string[],
|
||||
excludeDomains?: string[],
|
||||
excludeEntities?: string[]
|
||||
): EntityDomainFilterFunc => {
|
||||
const includeDomainsSet = new Set(includeDomains);
|
||||
const includeEntitiesSet = new Set(includeEntities);
|
||||
const excludeDomainsSet = new Set(excludeDomains);
|
||||
const excludeEntitiesSet = new Set(excludeEntities);
|
||||
|
||||
const haveInclude = includeDomainsSet.size > 0 || includeEntitiesSet.size > 0;
|
||||
const haveExclude = excludeDomainsSet.size > 0 || excludeEntitiesSet.size > 0;
|
||||
|
||||
// Case 1 - no includes or excludes - pass all entities
|
||||
if (!haveInclude && !haveExclude) {
|
||||
return () => true;
|
||||
}
|
||||
|
||||
// Case 2 - includes, no excludes - only include specified entities
|
||||
if (haveInclude && !haveExclude) {
|
||||
return (entityId) =>
|
||||
includeEntitiesSet.has(entityId) ||
|
||||
includeDomainsSet.has(computeDomain(entityId));
|
||||
}
|
||||
|
||||
// Case 3 - excludes, no includes - only exclude specified entities
|
||||
if (!haveInclude && haveExclude) {
|
||||
return (entityId) =>
|
||||
!excludeEntitiesSet.has(entityId) &&
|
||||
!excludeDomainsSet.has(computeDomain(entityId));
|
||||
}
|
||||
|
||||
// Case 4 - both includes and excludes specified
|
||||
// Case 4a - include domain specified
|
||||
// - if domain is included, pass if entity not excluded
|
||||
// - if domain is not included, pass if entity is included
|
||||
// note: if both include and exclude domains specified,
|
||||
// the exclude domains are ignored
|
||||
if (includeDomainsSet.size) {
|
||||
return (entityId) =>
|
||||
includeDomainsSet.has(computeDomain(entityId))
|
||||
? !excludeEntitiesSet.has(entityId)
|
||||
: includeEntitiesSet.has(entityId);
|
||||
}
|
||||
|
||||
// Case 4b - exclude domain specified
|
||||
// - if domain is excluded, pass if entity is included
|
||||
// - if domain is not excluded, pass if entity not excluded
|
||||
if (excludeDomainsSet.size) {
|
||||
return (entityId) =>
|
||||
excludeDomainsSet.has(computeDomain(entityId))
|
||||
? includeEntitiesSet.has(entityId)
|
||||
: !excludeEntitiesSet.has(entityId);
|
||||
}
|
||||
|
||||
// Case 4c - neither include or exclude domain specified
|
||||
// - Only pass if entity is included. Ignore entity excludes.
|
||||
return (entityId) => includeEntitiesSet.has(entityId);
|
||||
};
|
@ -1,121 +1,78 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { ensureArray } from "../array/ensure-array";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
import { getEntityContext } from "./context/get_entity_context";
|
||||
|
||||
type EntityCategory = "none" | "config" | "diagnostic";
|
||||
export type FilterFunc = (entityId: string) => boolean;
|
||||
|
||||
export interface EntityFilter {
|
||||
domain?: string | string[];
|
||||
device_class?: string | string[];
|
||||
device?: string | string[];
|
||||
area?: string | string[];
|
||||
floor?: string | string[];
|
||||
label?: string | string[];
|
||||
entity_category?: EntityCategory | EntityCategory[];
|
||||
hidden_platform?: string | string[];
|
||||
include_domains: string[];
|
||||
include_entities: string[];
|
||||
exclude_domains: string[];
|
||||
exclude_entities: string[];
|
||||
}
|
||||
|
||||
export type EntityFilterFunc = (entityId: string) => boolean;
|
||||
export const isEmptyFilter = (filter: EntityFilter) =>
|
||||
filter.include_domains.length +
|
||||
filter.include_entities.length +
|
||||
filter.exclude_domains.length +
|
||||
filter.exclude_entities.length ===
|
||||
0;
|
||||
|
||||
export const generateEntityFilter = (
|
||||
hass: HomeAssistant,
|
||||
filter: EntityFilter
|
||||
): EntityFilterFunc => {
|
||||
const domains = filter.domain
|
||||
? new Set(ensureArray(filter.domain))
|
||||
: undefined;
|
||||
const deviceClasses = filter.device_class
|
||||
? new Set(ensureArray(filter.device_class))
|
||||
: undefined;
|
||||
const floors = filter.floor ? new Set(ensureArray(filter.floor)) : undefined;
|
||||
const areas = filter.area ? new Set(ensureArray(filter.area)) : undefined;
|
||||
const devices = filter.device
|
||||
? new Set(ensureArray(filter.device))
|
||||
: undefined;
|
||||
const entityCategories = filter.entity_category
|
||||
? new Set(ensureArray(filter.entity_category))
|
||||
: undefined;
|
||||
const labels = filter.label ? new Set(ensureArray(filter.label)) : undefined;
|
||||
const hiddenPlatforms = filter.hidden_platform
|
||||
? new Set(ensureArray(filter.hidden_platform))
|
||||
: undefined;
|
||||
export const generateFilter = (
|
||||
includeDomains?: string[],
|
||||
includeEntities?: string[],
|
||||
excludeDomains?: string[],
|
||||
excludeEntities?: string[]
|
||||
): FilterFunc => {
|
||||
const includeDomainsSet = new Set(includeDomains);
|
||||
const includeEntitiesSet = new Set(includeEntities);
|
||||
const excludeDomainsSet = new Set(excludeDomains);
|
||||
const excludeEntitiesSet = new Set(excludeEntities);
|
||||
|
||||
return (entityId: string) => {
|
||||
const stateObj = hass.states[entityId] as HassEntity | undefined;
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
if (domains) {
|
||||
const domain = computeDomain(entityId);
|
||||
if (!domains.has(domain)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (deviceClasses) {
|
||||
const dc = stateObj.attributes.device_class || "none";
|
||||
if (!deviceClasses.has(dc)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const haveInclude = includeDomainsSet.size > 0 || includeEntitiesSet.size > 0;
|
||||
const haveExclude = excludeDomainsSet.size > 0 || excludeEntitiesSet.size > 0;
|
||||
|
||||
const { area, floor, device, entity } = getEntityContext(entityId, hass);
|
||||
// Case 1 - no includes or excludes - pass all entities
|
||||
if (!haveInclude && !haveExclude) {
|
||||
return () => true;
|
||||
}
|
||||
|
||||
if (entity && entity.hidden) {
|
||||
return false;
|
||||
}
|
||||
// Case 2 - includes, no excludes - only include specified entities
|
||||
if (haveInclude && !haveExclude) {
|
||||
return (entityId) =>
|
||||
includeEntitiesSet.has(entityId) ||
|
||||
includeDomainsSet.has(computeDomain(entityId));
|
||||
}
|
||||
|
||||
if (floors) {
|
||||
if (!floor) {
|
||||
return false;
|
||||
}
|
||||
if (!floors) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (areas) {
|
||||
if (!area) {
|
||||
return false;
|
||||
}
|
||||
if (!areas.has(area.area_id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (devices) {
|
||||
if (!device) {
|
||||
return false;
|
||||
}
|
||||
if (!devices.has(device.id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (labels) {
|
||||
if (!entity) {
|
||||
return false;
|
||||
}
|
||||
if (!entity.labels.some((label) => labels.has(label))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (entityCategories) {
|
||||
if (!entity) {
|
||||
return false;
|
||||
}
|
||||
const category = entity?.entity_category || "none";
|
||||
if (!entityCategories.has(category)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (hiddenPlatforms) {
|
||||
if (!entity) {
|
||||
return false;
|
||||
}
|
||||
if (entity.platform && hiddenPlatforms.has(entity.platform)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Case 3 - excludes, no includes - only exclude specified entities
|
||||
if (!haveInclude && haveExclude) {
|
||||
return (entityId) =>
|
||||
!excludeEntitiesSet.has(entityId) &&
|
||||
!excludeDomainsSet.has(computeDomain(entityId));
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
// Case 4 - both includes and excludes specified
|
||||
// Case 4a - include domain specified
|
||||
// - if domain is included, pass if entity not excluded
|
||||
// - if domain is not included, pass if entity is included
|
||||
// note: if both include and exclude domains specified,
|
||||
// the exclude domains are ignored
|
||||
if (includeDomainsSet.size) {
|
||||
return (entityId) =>
|
||||
includeDomainsSet.has(computeDomain(entityId))
|
||||
? !excludeEntitiesSet.has(entityId)
|
||||
: includeEntitiesSet.has(entityId);
|
||||
}
|
||||
|
||||
// Case 4b - exclude domain specified
|
||||
// - if domain is excluded, pass if entity is included
|
||||
// - if domain is not excluded, pass if entity not excluded
|
||||
if (excludeDomainsSet.size) {
|
||||
return (entityId) =>
|
||||
excludeDomainsSet.has(computeDomain(entityId))
|
||||
? includeEntitiesSet.has(entityId)
|
||||
: !excludeEntitiesSet.has(entityId);
|
||||
}
|
||||
|
||||
// Case 4c - neither include or exclude domain specified
|
||||
// - Only pass if entity is included. Ignore entity excludes.
|
||||
return (entityId) => includeEntitiesSet.has(entityId);
|
||||
};
|
||||
|
@ -1,18 +0,0 @@
|
||||
import type { AreaRegistryEntry } from "../../data/area_registry";
|
||||
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
interface AreaContext {
|
||||
floor: FloorRegistryEntry | null;
|
||||
}
|
||||
export const getAreaContext = (
|
||||
area: AreaRegistryEntry,
|
||||
hass: HomeAssistant
|
||||
): AreaContext => {
|
||||
const floorId = area.floor_id;
|
||||
const floor = floorId ? hass.floors[floorId] : null;
|
||||
|
||||
return {
|
||||
floor: floor,
|
||||
};
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
import type { AreaRegistryEntry } from "../../data/area_registry";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
interface DeviceContext {
|
||||
area: AreaRegistryEntry | null;
|
||||
floor: FloorRegistryEntry | null;
|
||||
}
|
||||
|
||||
export const getDeviceContext = (
|
||||
device: DeviceRegistryEntry,
|
||||
hass: HomeAssistant
|
||||
): DeviceContext => {
|
||||
const areaId = device.area_id;
|
||||
const area = areaId ? hass.areas[areaId] : null;
|
||||
const floorId = area?.floor_id;
|
||||
const floor = floorId ? hass.floors[floorId] : null;
|
||||
|
||||
return {
|
||||
area: area,
|
||||
floor: floor,
|
||||
};
|
||||
};
|
@ -1,55 +0,0 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { AreaRegistryEntry } from "../../data/area_registry";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import type {
|
||||
EntityRegistryDisplayEntry,
|
||||
EntityRegistryEntry,
|
||||
ExtEntityRegistryEntry,
|
||||
} from "../../data/entity_registry";
|
||||
import type { FloorRegistryEntry } from "../../data/floor_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
interface EntityContext {
|
||||
device: DeviceRegistryEntry | null;
|
||||
area: AreaRegistryEntry | null;
|
||||
floor: FloorRegistryEntry | null;
|
||||
}
|
||||
|
||||
export const getEntityContext = (
|
||||
stateObj: HassEntity,
|
||||
hass: HomeAssistant
|
||||
): EntityContext => {
|
||||
const entry = hass.entities[stateObj.entity_id] as
|
||||
| EntityRegistryDisplayEntry
|
||||
| undefined;
|
||||
|
||||
if (!entry) {
|
||||
return {
|
||||
device: null,
|
||||
area: null,
|
||||
floor: null,
|
||||
};
|
||||
}
|
||||
return getEntityEntryContext(entry, hass);
|
||||
};
|
||||
|
||||
export const getEntityEntryContext = (
|
||||
entry:
|
||||
| EntityRegistryDisplayEntry
|
||||
| EntityRegistryEntry
|
||||
| ExtEntityRegistryEntry,
|
||||
hass: HomeAssistant
|
||||
): EntityContext => {
|
||||
const deviceId = entry?.device_id;
|
||||
const device = deviceId ? hass.devices[deviceId] : null;
|
||||
const areaId = entry?.area_id || device?.area_id;
|
||||
const area = areaId ? hass.areas[areaId] : null;
|
||||
const floorId = area?.floor_id;
|
||||
const floor = floorId ? hass.floors[floorId] : null;
|
||||
|
||||
return {
|
||||
device: device,
|
||||
area: area,
|
||||
floor: floor,
|
||||
};
|
||||
};
|
@ -1,17 +1,17 @@
|
||||
const SUFFIXES = [" ", ": ", " - "];
|
||||
const SUFFIXES = [" ", ": "];
|
||||
|
||||
/**
|
||||
* Strips a device name from an entity name.
|
||||
* @param entityName the entity name
|
||||
* @param prefix the prefix to strip
|
||||
* @param lowerCasedPrefix the prefix to strip, lower cased
|
||||
* @returns
|
||||
*/
|
||||
export const stripPrefixFromEntityName = (
|
||||
entityName: string,
|
||||
prefix: string
|
||||
lowerCasedPrefix: string
|
||||
) => {
|
||||
const lowerCasedEntityName = entityName.toLowerCase();
|
||||
const lowerCasedPrefix = prefix.toLowerCase();
|
||||
|
||||
for (const suffix of SUFFIXES) {
|
||||
const lowerCasedPrefixWithSuffix = `${lowerCasedPrefix}${suffix}`;
|
||||
|
||||
|
@ -1 +1,2 @@
|
||||
export const webComponentsSupported = "attachShadow" in Element.prototype;
|
||||
export const webComponentsSupported =
|
||||
"customElements" in window && "content" in document.createElement("template");
|
||||
|
@ -5,7 +5,7 @@ import { getIntegrationDescriptions } from "../../data/integrations";
|
||||
import { showConfigFlowDialog } from "../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
|
||||
import { showMatterAddDeviceDialog } from "../../panels/config/integrations/integration-panels/matter/show-dialog-add-matter-device";
|
||||
import { showZWaveJSAddNodeDialog } from "../../panels/config/integrations/integration-panels/zwave_js/add-node/show-dialog-zwave_js-add-node";
|
||||
import { showZWaveJSAddNodeDialog } from "../../panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import { isComponentLoaded } from "../config/is_component_loaded";
|
||||
|
@ -45,22 +45,3 @@ export const caseInsensitiveStringCompare = (
|
||||
|
||||
return fallbackStringCompare(a.toLowerCase(), b.toLowerCase());
|
||||
};
|
||||
|
||||
export const orderCompare = (order: string[]) => (a: string, b: string) => {
|
||||
const idxA = order.indexOf(a);
|
||||
const idxB = order.indexOf(b);
|
||||
|
||||
if (idxA === idxB) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (idxA === -1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (idxB === -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return idxA - idxB;
|
||||
};
|
||||
|
@ -1,14 +0,0 @@
|
||||
export function getDuplicates(array: string[]): Set<string> {
|
||||
const duplicates = new Set<string>();
|
||||
const seen = new Set<string>();
|
||||
|
||||
for (const item of array) {
|
||||
if (seen.has(item)) {
|
||||
duplicates.add(item);
|
||||
} else {
|
||||
seen.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return duplicates;
|
||||
}
|
@ -1,38 +1,31 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiAlertOctagram, mdiCheckBold } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../ha-button";
|
||||
import "../ha-spinner";
|
||||
import "../ha-circular-progress";
|
||||
import "../ha-svg-icon";
|
||||
|
||||
@customElement("ha-progress-button")
|
||||
export class HaProgressButton extends LitElement {
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public progress = false;
|
||||
|
||||
@property({ type: Boolean }) public raised = false;
|
||||
|
||||
@property({ type: Boolean }) public unelevated = false;
|
||||
|
||||
@state() private _result?: "success" | "error";
|
||||
|
||||
public render(): TemplateResult {
|
||||
const overlay = this._result || this.progress;
|
||||
return html`
|
||||
<ha-button
|
||||
.raised=${this.raised}
|
||||
.label=${this.label}
|
||||
.unelevated=${this.unelevated}
|
||||
<mwc-button
|
||||
?raised=${this.raised}
|
||||
.disabled=${this.disabled || this.progress}
|
||||
class=${this._result || ""}
|
||||
>
|
||||
<slot name="icon" slot="icon"></slot>
|
||||
<slot></slot>
|
||||
</ha-button>
|
||||
</mwc-button>
|
||||
${!overlay
|
||||
? nothing
|
||||
: html`
|
||||
@ -42,7 +35,12 @@ export class HaProgressButton extends LitElement {
|
||||
: this._result === "error"
|
||||
? html`<ha-svg-icon .path=${mdiAlertOctagram}></ha-svg-icon>`
|
||||
: this.progress
|
||||
? html`<ha-spinner size="small"></ha-spinner>`
|
||||
? html`
|
||||
<ha-circular-progress
|
||||
size="small"
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
`}
|
||||
@ -72,12 +70,12 @@ export class HaProgressButton extends LitElement {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
ha-button {
|
||||
mwc-button {
|
||||
transition: all 1s;
|
||||
pointer-events: initial;
|
||||
}
|
||||
|
||||
ha-button.success {
|
||||
mwc-button.success {
|
||||
--mdc-theme-primary: white;
|
||||
background-color: var(--success-color);
|
||||
transition: none;
|
||||
@ -85,13 +83,12 @@ export class HaProgressButton extends LitElement {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
ha-button[unelevated].success,
|
||||
ha-button[raised].success {
|
||||
mwc-button[raised].success {
|
||||
--mdc-theme-primary: var(--success-color);
|
||||
--mdc-theme-on-primary: white;
|
||||
}
|
||||
|
||||
ha-button.error {
|
||||
mwc-button.error {
|
||||
--mdc-theme-primary: white;
|
||||
background-color: var(--error-color);
|
||||
transition: none;
|
||||
@ -99,8 +96,7 @@ export class HaProgressButton extends LitElement {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
ha-button[unelevated].error,
|
||||
ha-button[raised].error {
|
||||
mwc-button[raised].error {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
--mdc-theme-on-primary: white;
|
||||
}
|
||||
@ -117,8 +113,8 @@ export class HaProgressButton extends LitElement {
|
||||
color: white;
|
||||
}
|
||||
|
||||
ha-button.success slot,
|
||||
ha-button.error slot {
|
||||
mwc-button.success slot,
|
||||
mwc-button.error slot {
|
||||
visibility: hidden;
|
||||
}
|
||||
:host([destructive]) {
|
||||
|
@ -226,12 +226,7 @@ export class HaChartBase extends LitElement {
|
||||
const overflowLimit = isMobile
|
||||
? LEGEND_OVERFLOW_LIMIT_MOBILE
|
||||
: LEGEND_OVERFLOW_LIMIT;
|
||||
return html`<div
|
||||
class=${classMap({
|
||||
"chart-legend": true,
|
||||
"multiple-items": items.length > 1,
|
||||
})}
|
||||
>
|
||||
return html`<div class="chart-legend">
|
||||
<ul>
|
||||
${items.map((item: string, index: number) => {
|
||||
if (!this.expandLegend && index >= overflowLimit) {
|
||||
@ -267,9 +262,7 @@ export class HaChartBase extends LitElement {
|
||||
? this.hass.localize(
|
||||
"ui.components.history_charts.collapse_legend"
|
||||
)
|
||||
: `${this.hass.localize(
|
||||
"ui.components.history_charts.expand_legend"
|
||||
)} (${items.length - overflowLimit})`}
|
||||
: `${this.hass.localize("ui.components.history_charts.expand_legend")} (${items.length - overflowLimit})`}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="trailing-icon"
|
||||
@ -734,10 +727,10 @@ export class HaChartBase extends LitElement {
|
||||
align-items: center;
|
||||
padding: 0 2px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
.chart-legend.multiple-items li {
|
||||
max-width: 220px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.chart-legend .hidden {
|
||||
color: var(--secondary-text-color);
|
||||
|
@ -124,7 +124,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
const data = dataset.data || [];
|
||||
for (let i = data.length - 1; i >= 0; i--) {
|
||||
const point = data[i];
|
||||
if (point && point[0] <= time && typeof point[1] === "number") {
|
||||
if (point && point[0] <= time && point[1]) {
|
||||
lastData = point;
|
||||
break;
|
||||
}
|
||||
|
@ -296,11 +296,7 @@ export class StatisticsChart extends LitElement {
|
||||
align: "left",
|
||||
},
|
||||
position: computeRTL(this.hass) ? "right" : "left",
|
||||
scale:
|
||||
this.chartType !== "bar" ||
|
||||
this.logarithmicScale ||
|
||||
minYAxis !== undefined ||
|
||||
maxYAxis !== undefined,
|
||||
scale: true,
|
||||
min: this._clampYAxis(minYAxis),
|
||||
max: this._clampYAxis(maxYAxis),
|
||||
splitLine: {
|
||||
|
@ -645,16 +645,15 @@ export class HaDataTable extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const prom =
|
||||
this.sortColumn && this._sortColumns[this.sortColumn]
|
||||
? sortData(
|
||||
filteredData,
|
||||
this._sortColumns[this.sortColumn],
|
||||
this.sortDirection,
|
||||
this.sortColumn,
|
||||
this.hass.locale.language
|
||||
)
|
||||
: filteredData;
|
||||
const prom = this.sortColumn
|
||||
? sortData(
|
||||
filteredData,
|
||||
this._sortColumns[this.sortColumn],
|
||||
this.sortDirection,
|
||||
this.sortColumn,
|
||||
this.hass.locale.language
|
||||
)
|
||||
: filteredData;
|
||||
|
||||
const [data] = await Promise.all([prom, nextRender]);
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDeviceNameDisplay } from "../../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import type { ScorableTextItem } from "../../common/string/filter/sequence-matching";
|
||||
@ -14,12 +13,15 @@ import type {
|
||||
DeviceEntityDisplayLookup,
|
||||
DeviceRegistryEntry,
|
||||
} from "../../data/device_registry";
|
||||
import { getDeviceEntityDisplayLookup } from "../../data/device_registry";
|
||||
import {
|
||||
computeDeviceName,
|
||||
getDeviceEntityDisplayLookup,
|
||||
} from "../../data/device_registry";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-combo-box-item";
|
||||
import "../ha-list-item";
|
||||
|
||||
interface Device {
|
||||
name: string;
|
||||
@ -35,14 +37,11 @@ export type HaDevicePickerDeviceFilterFunc = (
|
||||
|
||||
export type HaDevicePickerEntityFilterFunc = (entity: HassEntity) => boolean;
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<Device> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
<span slot="headline">${item.name}</span>
|
||||
${item.area
|
||||
? html`<span slot="supporting-text">${item.area}</span>`
|
||||
: nothing}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
const rowRenderer: ComboBoxLitRenderer<Device> = (item) =>
|
||||
html`<ha-list-item .twoline=${!!item.area}>
|
||||
<span>${item.name}</span>
|
||||
<span slot="secondary">${item.area}</span>
|
||||
</ha-list-item>`;
|
||||
|
||||
@customElement("ha-device-picker")
|
||||
export class HaDevicePicker extends LitElement {
|
||||
@ -215,7 +214,7 @@ export class HaDevicePicker extends LitElement {
|
||||
}
|
||||
|
||||
const outputDevices = inputDevices.map((device) => {
|
||||
const name = computeDeviceNameDisplay(
|
||||
const name = computeDeviceName(
|
||||
device,
|
||||
this.hass,
|
||||
deviceEntityLookup[device.id]
|
||||
|
@ -1,78 +1,35 @@
|
||||
import { mdiMagnify, mdiPlus } from "@mdi/js";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { IFuseOptions } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import "../ha-list-item";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeAreaName } from "../../common/entity/compute_area_name";
|
||||
import { computeDeviceName } from "../../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeEntityName } from "../../common/entity/compute_entity_name";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { getEntityContext } from "../../common/entity/get_entity_context";
|
||||
import type { ScorableTextItem } from "../../common/string/filter/sequence-matching";
|
||||
import { fuzzyFilterSort } from "../../common/string/filter/sequence-matching";
|
||||
import type { ValueChangedEvent, HomeAssistant } from "../../types";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-svg-icon";
|
||||
import "./state-badge";
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import { showHelperDetailDialog } from "../../panels/config/helpers/show-dialog-helper-detail";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import type { HelperDomain } from "../../panels/config/helpers/const";
|
||||
import { isHelperDomain } from "../../panels/config/helpers/const";
|
||||
import { showHelperDetailDialog } from "../../panels/config/helpers/show-dialog-helper-detail";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-combo-box-item";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-list-item";
|
||||
import "../ha-svg-icon";
|
||||
import "./state-badge";
|
||||
|
||||
const FAKE_ENTITY: HassEntity = {
|
||||
entity_id: "",
|
||||
state: "",
|
||||
last_changed: "",
|
||||
last_updated: "",
|
||||
context: { id: "", user_id: null, parent_id: null },
|
||||
attributes: {},
|
||||
};
|
||||
|
||||
interface EntityPickerItem extends HassEntity {
|
||||
label: string;
|
||||
primary: string;
|
||||
secondary?: string;
|
||||
translated_domain?: string;
|
||||
show_entity_id?: boolean;
|
||||
entity_name?: string;
|
||||
area_name?: string;
|
||||
device_name?: string;
|
||||
friendly_name?: string;
|
||||
sorting_label?: string;
|
||||
icon_path?: string;
|
||||
interface HassEntityWithCachedName extends HassEntity, ScorableTextItem {
|
||||
friendly_name: string;
|
||||
}
|
||||
|
||||
export type HaEntityPickerEntityFilterFunc = (entity: HassEntity) => boolean;
|
||||
|
||||
const CREATE_ID = "___create-new-entity___";
|
||||
|
||||
const DOMAIN_STYLE = styleMap({
|
||||
fontSize: "12px",
|
||||
fontWeight: "400",
|
||||
lineHeight: "18px",
|
||||
alignSelf: "flex-end",
|
||||
maxWidth: "30%",
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
});
|
||||
|
||||
const ENTITY_ID_STYLE = styleMap({
|
||||
fontFamily: "var(--code-font-family, monospace)",
|
||||
fontSize: "11px",
|
||||
});
|
||||
|
||||
@customElement("ha-entity-picker")
|
||||
export class HaEntityPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@ -149,7 +106,8 @@ export class HaEntityPicker extends LitElement {
|
||||
@property({ attribute: "hide-clear-icon", type: Boolean })
|
||||
public hideClearIcon = false;
|
||||
|
||||
@property({ attribute: "item-label-path" }) public itemLabelPath = "label";
|
||||
@property({ attribute: "item-label-path" }) public itemLabelPath =
|
||||
"friendly_name";
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@ -165,48 +123,30 @@ export class HaEntityPicker extends LitElement {
|
||||
await this.comboBox?.focus();
|
||||
}
|
||||
|
||||
private _initialItems = false;
|
||||
private _initedStates = false;
|
||||
|
||||
private _items: EntityPickerItem[] = [];
|
||||
private _states: HassEntityWithCachedName[] = [];
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
this.hass.loadBackendTranslation("title");
|
||||
}
|
||||
private _rowRenderer: ComboBoxLitRenderer<HassEntityWithCachedName> = (
|
||||
item
|
||||
) =>
|
||||
html`<ha-list-item graphic="avatar" .twoline=${!!item.entity_id}>
|
||||
${item.state
|
||||
? html`<state-badge
|
||||
slot="graphic"
|
||||
.stateObj=${item}
|
||||
.hass=${this.hass}
|
||||
></state-badge>`
|
||||
: ""}
|
||||
<span>${item.friendly_name}</span>
|
||||
<span slot="secondary"
|
||||
>${item.entity_id.startsWith(CREATE_ID)
|
||||
? this.hass.localize("ui.components.entity.entity-picker.new_entity")
|
||||
: item.entity_id}</span
|
||||
>
|
||||
</ha-list-item>`;
|
||||
|
||||
private _rowRenderer: ComboBoxLitRenderer<EntityPickerItem> = (
|
||||
item,
|
||||
{ index }
|
||||
) => html`
|
||||
<ha-combo-box-item type="button" compact .borderTop=${index !== 0}>
|
||||
${item.icon_path
|
||||
? html`<ha-svg-icon slot="start" .path=${item.icon_path}></ha-svg-icon>`
|
||||
: html`
|
||||
<state-badge
|
||||
slot="start"
|
||||
.stateObj=${item}
|
||||
.hass=${this.hass}
|
||||
></state-badge>
|
||||
`}
|
||||
|
||||
<span slot="headline">${item.primary} </span>
|
||||
${item.secondary
|
||||
? html`<span slot="supporting-text">${item.secondary}</span>`
|
||||
: nothing}
|
||||
${item.entity_id && item.show_entity_id
|
||||
? html`<span slot="supporting-text" style=${ENTITY_ID_STYLE}
|
||||
>${item.entity_id}</span
|
||||
>`
|
||||
: nothing}
|
||||
${item.translated_domain && !item.show_entity_id
|
||||
? html`<div slot="trailing-supporting-text" style=${DOMAIN_STYLE}>
|
||||
${item.translated_domain}
|
||||
</div>`
|
||||
: nothing}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
|
||||
private _getItems = memoizeOne(
|
||||
private _getStates = memoizeOne(
|
||||
(
|
||||
_opened: boolean,
|
||||
hass: this["hass"],
|
||||
@ -218,8 +158,8 @@ export class HaEntityPicker extends LitElement {
|
||||
includeEntities: this["includeEntities"],
|
||||
excludeEntities: this["excludeEntities"],
|
||||
createDomains: this["createDomains"]
|
||||
): EntityPickerItem[] => {
|
||||
let states: EntityPickerItem[] = [];
|
||||
): HassEntityWithCachedName[] => {
|
||||
let states: HassEntityWithCachedName[] = [];
|
||||
|
||||
if (!hass) {
|
||||
return [];
|
||||
@ -228,7 +168,7 @@ export class HaEntityPicker extends LitElement {
|
||||
|
||||
const createItems = createDomains?.length
|
||||
? createDomains.map((domain) => {
|
||||
const primary = hass.localize(
|
||||
const newFriendlyName = hass.localize(
|
||||
"ui.components.entity.entity-picker.create_helper",
|
||||
{
|
||||
domain: isHelperDomain(domain)
|
||||
@ -240,14 +180,16 @@ export class HaEntityPicker extends LitElement {
|
||||
);
|
||||
|
||||
return {
|
||||
...FAKE_ENTITY,
|
||||
entity_id: CREATE_ID + domain,
|
||||
primary: primary,
|
||||
label: primary,
|
||||
secondary: this.hass.localize(
|
||||
"ui.components.entity.entity-picker.new_entity"
|
||||
),
|
||||
icon_path: mdiPlus,
|
||||
state: "on",
|
||||
last_changed: "",
|
||||
last_updated: "",
|
||||
context: { id: "", user_id: null, parent_id: null },
|
||||
friendly_name: newFriendlyName,
|
||||
attributes: {
|
||||
icon: "mdi:plus",
|
||||
},
|
||||
strings: [domain, newFriendlyName],
|
||||
};
|
||||
})
|
||||
: [];
|
||||
@ -255,14 +197,21 @@ export class HaEntityPicker extends LitElement {
|
||||
if (!entityIds.length) {
|
||||
return [
|
||||
{
|
||||
...FAKE_ENTITY,
|
||||
primary: this.hass!.localize(
|
||||
entity_id: "",
|
||||
state: "",
|
||||
last_changed: "",
|
||||
last_updated: "",
|
||||
context: { id: "", user_id: null, parent_id: null },
|
||||
friendly_name: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_entities"
|
||||
),
|
||||
label: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_entities"
|
||||
),
|
||||
icon_path: mdiMagnify,
|
||||
attributes: {
|
||||
friendly_name: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_entities"
|
||||
),
|
||||
icon: "mdi:magnify",
|
||||
},
|
||||
strings: [],
|
||||
},
|
||||
...createItems,
|
||||
];
|
||||
@ -292,49 +241,19 @@ export class HaEntityPicker extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
const isRTL = computeRTL(this.hass);
|
||||
|
||||
states = entityIds
|
||||
.map<EntityPickerItem>((entityId) => {
|
||||
const stateObj = hass!.states[entityId];
|
||||
|
||||
const { area, device } = getEntityContext(stateObj, hass);
|
||||
|
||||
const friendlyName = computeStateName(stateObj); // Keep this for search
|
||||
const entityName = computeEntityName(stateObj, hass);
|
||||
const deviceName = device ? computeDeviceName(device) : undefined;
|
||||
const areaName = area ? computeAreaName(area) : undefined;
|
||||
|
||||
const primary = entityName || deviceName || entityId;
|
||||
const secondary = [areaName, entityName ? deviceName : undefined]
|
||||
.filter(Boolean)
|
||||
.join(isRTL ? " ◂ " : " ▸ ");
|
||||
|
||||
const translatedDomain = domainToName(
|
||||
this.hass.localize,
|
||||
computeDomain(entityId)
|
||||
);
|
||||
|
||||
.map((key) => {
|
||||
const friendly_name = computeStateName(hass!.states[key]) || key;
|
||||
return {
|
||||
...hass!.states[entityId],
|
||||
primary: primary,
|
||||
secondary:
|
||||
secondary ||
|
||||
this.hass.localize("ui.components.device-picker.no_area"),
|
||||
label: friendlyName,
|
||||
translated_domain: translatedDomain,
|
||||
sorting_label: [deviceName, entityName].filter(Boolean).join("-"),
|
||||
entity_name: entityName || deviceName,
|
||||
area_name: areaName,
|
||||
device_name: deviceName,
|
||||
friendly_name: friendlyName,
|
||||
show_entity_id: hass.userData?.showEntityIdPicker,
|
||||
...hass!.states[key],
|
||||
friendly_name,
|
||||
strings: [key, friendly_name],
|
||||
};
|
||||
})
|
||||
.sort((entityA, entityB) =>
|
||||
caseInsensitiveStringCompare(
|
||||
entityA.sorting_label!,
|
||||
entityB.sorting_label!,
|
||||
entityA.friendly_name,
|
||||
entityB.friendly_name,
|
||||
this.hass.locale.language
|
||||
)
|
||||
);
|
||||
@ -372,14 +291,21 @@ export class HaEntityPicker extends LitElement {
|
||||
if (!states.length) {
|
||||
return [
|
||||
{
|
||||
...FAKE_ENTITY,
|
||||
primary: this.hass!.localize(
|
||||
entity_id: "",
|
||||
state: "",
|
||||
last_changed: "",
|
||||
last_updated: "",
|
||||
context: { id: "", user_id: null, parent_id: null },
|
||||
friendly_name: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_match"
|
||||
),
|
||||
label: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_match"
|
||||
),
|
||||
icon_path: mdiMagnify,
|
||||
attributes: {
|
||||
friendly_name: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_match"
|
||||
),
|
||||
icon: "mdi:magnify",
|
||||
},
|
||||
strings: [],
|
||||
},
|
||||
...createItems,
|
||||
];
|
||||
@ -405,8 +331,8 @@ export class HaEntityPicker extends LitElement {
|
||||
}
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
if (!this._initialItems || (changedProps.has("_opened") && this._opened)) {
|
||||
this._items = this._getItems(
|
||||
if (!this._initedStates || (changedProps.has("_opened") && this._opened)) {
|
||||
this._states = this._getStates(
|
||||
this._opened,
|
||||
this.hass,
|
||||
this.includeDomains,
|
||||
@ -418,10 +344,10 @@ export class HaEntityPicker extends LitElement {
|
||||
this.excludeEntities,
|
||||
this.createDomains
|
||||
);
|
||||
if (this._initialItems) {
|
||||
this.comboBox.filteredItems = this._items;
|
||||
if (this._initedStates) {
|
||||
this.comboBox.filteredItems = this._states;
|
||||
}
|
||||
this._initialItems = true;
|
||||
this._initedStates = true;
|
||||
}
|
||||
|
||||
if (changedProps.has("createDomains") && this.createDomains?.length) {
|
||||
@ -441,11 +367,10 @@ export class HaEntityPicker extends LitElement {
|
||||
: this.label}
|
||||
.helper=${this.helper}
|
||||
.allowCustomValue=${this.allowCustomEntity}
|
||||
.filteredItems=${this._items}
|
||||
.filteredItems=${this._states}
|
||||
.renderer=${this._rowRenderer}
|
||||
.required=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
.hideClearIcon=${this.hideClearIcon}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
@ -482,49 +407,12 @@ export class HaEntityPicker extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _fuseKeys = [
|
||||
"entity_name",
|
||||
"device_name",
|
||||
"area_name",
|
||||
"translated_domain",
|
||||
"friendly_name", // for backwards compatibility
|
||||
"entity_id", // for technical search
|
||||
];
|
||||
|
||||
private _fuseIndex = memoizeOne((states: EntityPickerItem[]) =>
|
||||
Fuse.createIndex(this._fuseKeys, states)
|
||||
);
|
||||
|
||||
private _filterChanged(ev: CustomEvent): void {
|
||||
const target = ev.target as HaComboBox;
|
||||
const filterString = ev.detail.value.trim().toLowerCase() as string;
|
||||
|
||||
const minLength = 2;
|
||||
|
||||
const searchTerms = (filterString.split(" ") ?? []).filter(
|
||||
(term) => term.length >= minLength
|
||||
);
|
||||
|
||||
if (searchTerms.length > 0) {
|
||||
const index = this._fuseIndex(this._items);
|
||||
|
||||
const options: IFuseOptions<EntityPickerItem> = {
|
||||
isCaseSensitive: false,
|
||||
threshold: 0.3,
|
||||
ignoreDiacritics: true,
|
||||
minMatchCharLength: minLength,
|
||||
};
|
||||
|
||||
const fuse = new Fuse(this._items, options, index);
|
||||
const results = fuse.search({
|
||||
$and: searchTerms.map((term) => ({
|
||||
$or: this._fuseKeys.map((key) => ({ [key]: term })),
|
||||
})),
|
||||
});
|
||||
target.filteredItems = results.map((result) => result.item);
|
||||
} else {
|
||||
target.filteredItems = this._items;
|
||||
}
|
||||
const filterString = ev.detail.value.trim().toLowerCase();
|
||||
target.filteredItems = filterString.length
|
||||
? fuzzyFilterSort<HassEntityWithCachedName>(filterString, this._states)
|
||||
: this._states;
|
||||
}
|
||||
|
||||
private _setValue(value: string | undefined) {
|
||||
|
@ -1,23 +1,23 @@
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import type { ScorableTextItem } from "../../common/string/filter/sequence-matching";
|
||||
import { fuzzyFilterSort } from "../../common/string/filter/sequence-matching";
|
||||
import type { StatisticsMetaData } from "../../data/recorder";
|
||||
import { getStatisticIds, getStatisticLabel } from "../../data/recorder";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import type { ValueChangedEvent, HomeAssistant } from "../../types";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-combo-box-item";
|
||||
import "../ha-svg-icon";
|
||||
import "./state-badge";
|
||||
import type { ScorableTextItem } from "../../common/string/filter/sequence-matching";
|
||||
import { fuzzyFilterSort } from "../../common/string/filter/sequence-matching";
|
||||
|
||||
interface StatisticItem extends ScorableTextItem {
|
||||
id: string;
|
||||
@ -99,18 +99,16 @@ export class HaStatisticPicker extends LitElement {
|
||||
@state() private _filteredItems?: StatisticItem[] = undefined;
|
||||
|
||||
private _rowRenderer: ComboBoxLitRenderer<StatisticItem> = (item) =>
|
||||
html`<ha-combo-box-item type="button">
|
||||
html`<mwc-list-item graphic="avatar" twoline>
|
||||
${item.state
|
||||
? html`
|
||||
<state-badge
|
||||
slot="start"
|
||||
.stateObj=${item.state}
|
||||
.hass=${this.hass}
|
||||
></state-badge>
|
||||
`
|
||||
: html`<span slot="start" style="width: 32px"></span>`}
|
||||
<span slot="headline">${item.name}</span>
|
||||
<span slot="supporting-text"
|
||||
? html`<state-badge
|
||||
slot="graphic"
|
||||
.stateObj=${item.state}
|
||||
.hass=${this.hass}
|
||||
></state-badge>`
|
||||
: ""}
|
||||
<span>${item.name}</span>
|
||||
<span slot="secondary"
|
||||
>${item.id === "" || item.id === "__missing"
|
||||
? html`<a
|
||||
target="_blank"
|
||||
@ -122,7 +120,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
>`
|
||||
: item.id}</span
|
||||
>
|
||||
</ha-combo-box-item>`;
|
||||
</mwc-list-item>`;
|
||||
|
||||
private _getStatistics = memoizeOne(
|
||||
(
|
||||
|
@ -79,17 +79,6 @@ export class StateBadge extends LitElement {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
const cls = this.getClass();
|
||||
if (cls) {
|
||||
cls.forEach((toSet, className) => {
|
||||
if (!toSet) {
|
||||
this.classList.remove(className);
|
||||
} else {
|
||||
this.classList.add(className);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.icon) {
|
||||
return nothing;
|
||||
}
|
||||
@ -186,57 +175,35 @@ export class StateBadge extends LitElement {
|
||||
backgroundImage = `url(${imageUrl})`;
|
||||
this.icon = false;
|
||||
}
|
||||
|
||||
if (domain === "update") {
|
||||
this.style.borderRadius = "0";
|
||||
} else if (domain === "media_player" || domain === "camera") {
|
||||
this.style.borderRadius = "8%";
|
||||
}
|
||||
}
|
||||
|
||||
this._iconStyle = iconStyle;
|
||||
this.style.backgroundImage = backgroundImage;
|
||||
}
|
||||
|
||||
protected getClass() {
|
||||
const cls = new Map(
|
||||
["has-no-radius", "has-media-image", "has-image"].map((_cls) => [
|
||||
_cls,
|
||||
false,
|
||||
])
|
||||
);
|
||||
if (this.stateObj) {
|
||||
const domain = computeDomain(this.stateObj.entity_id);
|
||||
if (domain === "update") {
|
||||
cls.set("has-no-radius", true);
|
||||
} else if (domain === "media_player" || domain === "camera") {
|
||||
cls.set("has-media-image", true);
|
||||
} else if (this.style.backgroundImage !== "") {
|
||||
cls.set("has-image", true);
|
||||
}
|
||||
}
|
||||
return cls;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
iconColorCSS,
|
||||
css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
color: var(--paper-item-icon-color, #44739e);
|
||||
border-radius: var(--state-badge-border-radius, 50%);
|
||||
border-radius: 50%;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
background-size: cover;
|
||||
line-height: 40px;
|
||||
vertical-align: middle;
|
||||
box-sizing: border-box;
|
||||
--state-inactive-color: initial;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
:host(.has-image) {
|
||||
border-radius: var(--state-badge-with-image-border-radius, 50%);
|
||||
}
|
||||
:host(.has-media-image) {
|
||||
border-radius: var(--state-badge-with-media-image-border-radius, 8%);
|
||||
}
|
||||
:host(.has-no-radius) {
|
||||
border-radius: 0;
|
||||
}
|
||||
:host(:focus) {
|
||||
outline: none;
|
||||
|
@ -10,23 +10,20 @@ import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import "./ha-alert";
|
||||
import "./ha-combo-box";
|
||||
import type { HaComboBox } from "./ha-combo-box";
|
||||
import "./ha-combo-box-item";
|
||||
import "./ha-list-item";
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
<span slot="headline">${item.name}</span>
|
||||
<span slot="supporting-text">${item.slug}</span>
|
||||
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (item) =>
|
||||
html`<ha-list-item twoline graphic="icon">
|
||||
<span>${item.name}</span>
|
||||
<span slot="secondary">${item.slug}</span>
|
||||
${item.icon
|
||||
? html`
|
||||
<img
|
||||
alt=""
|
||||
slot="start"
|
||||
.src="/api/hassio/addons/${item.slug}/icon"
|
||||
/>
|
||||
`
|
||||
: nothing}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
? html`<img
|
||||
alt=""
|
||||
slot="graphic"
|
||||
.src="/api/hassio/addons/${item.slug}/icon"
|
||||
/>`
|
||||
: ""}
|
||||
</ha-list-item>`;
|
||||
|
||||
@customElement("ha-addon-picker")
|
||||
class HaAddonPicker extends LitElement {
|
||||
|
95
src/components/ha-area-filter.ts
Normal file
95
src/components/ha-area-filter.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import { mdiTextureBox } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { showAreaFilterDialog } from "../dialogs/area-filter/show-area-filter-dialog";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-icon-next";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-textfield";
|
||||
|
||||
export interface AreaFilterValue {
|
||||
hidden?: string[];
|
||||
order?: string[];
|
||||
}
|
||||
|
||||
@customElement("ha-area-filter")
|
||||
export class HaAreaPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ attribute: false }) public value?: AreaFilterValue;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const allAreasCount = Object.keys(this.hass.areas).length;
|
||||
const hiddenAreasCount = this.value?.hidden?.length ?? 0;
|
||||
|
||||
const description =
|
||||
hiddenAreasCount === 0
|
||||
? this.hass.localize("ui.components.area-filter.all_areas")
|
||||
: allAreasCount === hiddenAreasCount
|
||||
? this.hass.localize("ui.components.area-filter.no_areas")
|
||||
: this.hass.localize("ui.components.area-filter.area_count", {
|
||||
count: allAreasCount - hiddenAreasCount,
|
||||
});
|
||||
|
||||
return html`
|
||||
<ha-list-item
|
||||
tabindex="0"
|
||||
role="button"
|
||||
hasMeta
|
||||
twoline
|
||||
graphic="icon"
|
||||
@click=${this._edit}
|
||||
@keydown=${this._edit}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiTextureBox}></ha-svg-icon>
|
||||
<span>${this.label}</span>
|
||||
<span slot="secondary">${description}</span>
|
||||
<ha-icon-next
|
||||
slot="meta"
|
||||
.label=${this.hass.localize("ui.common.edit")}
|
||||
></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _edit(ev) {
|
||||
if (ev.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
const value = await showAreaFilterDialog(this, {
|
||||
title: this.label,
|
||||
initialValue: this.value,
|
||||
});
|
||||
if (!value) return;
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-list-item {
|
||||
--mdc-list-side-padding-left: 8px;
|
||||
--mdc-list-side-padding-right: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-area-filter": HaAreaPicker;
|
||||
}
|
||||
}
|
@ -25,7 +25,6 @@ import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import "./ha-combo-box";
|
||||
import type { HaComboBox } from "./ha-combo-box";
|
||||
import "./ha-combo-box-item";
|
||||
import "./ha-floor-icon";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-list-item";
|
||||
@ -126,38 +125,38 @@ export class HaAreaFloorPicker extends LitElement {
|
||||
private _rowRenderer: ComboBoxLitRenderer<FloorAreaEntry> = (item) => {
|
||||
const rtl = computeRTL(this.hass);
|
||||
return html`
|
||||
<ha-combo-box-item
|
||||
type="button"
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
style=${item.type === "area" && item.hasFloor
|
||||
? "--md-list-item-leading-space: 48px;"
|
||||
? rtl
|
||||
? "--mdc-list-side-padding-right: 48px;"
|
||||
: "--mdc-list-side-padding-left: 48px;"
|
||||
: ""}
|
||||
>
|
||||
${item.type === "area" && item.hasFloor
|
||||
? html`
|
||||
<ha-tree-indicator
|
||||
style=${styleMap({
|
||||
width: "48px",
|
||||
position: "absolute",
|
||||
top: "0px",
|
||||
left: rtl ? undefined : "4px",
|
||||
right: rtl ? "4px" : undefined,
|
||||
transform: rtl ? "scaleX(-1)" : "",
|
||||
})}
|
||||
.end=${item.lastArea}
|
||||
slot="start"
|
||||
></ha-tree-indicator>
|
||||
`
|
||||
? html`<ha-tree-indicator
|
||||
style=${styleMap({
|
||||
width: "48px",
|
||||
position: "absolute",
|
||||
top: "0px",
|
||||
left: rtl ? undefined : "8px",
|
||||
right: rtl ? "8px" : undefined,
|
||||
transform: rtl ? "scaleX(-1)" : "",
|
||||
})}
|
||||
.end=${item.lastArea}
|
||||
slot="graphic"
|
||||
></ha-tree-indicator>`
|
||||
: nothing}
|
||||
${item.type === "floor"
|
||||
? html`<ha-floor-icon slot="start" .floor=${item}></ha-floor-icon>`
|
||||
? html`<ha-floor-icon slot="graphic" .floor=${item}></ha-floor-icon>`
|
||||
: item.icon
|
||||
? html`<ha-icon slot="start" .icon=${item.icon}></ha-icon>`
|
||||
? html`<ha-icon slot="graphic" .icon=${item.icon}></ha-icon>`
|
||||
: html`<ha-svg-icon
|
||||
slot="start"
|
||||
slot="graphic"
|
||||
.path=${mdiTextureBox}
|
||||
></ha-svg-icon>`}
|
||||
${item.name}
|
||||
</ha-combo-box-item>
|
||||
</ha-list-item>
|
||||
`;
|
||||
};
|
||||
|
||||
|
@ -4,6 +4,7 @@ import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
@ -23,21 +24,22 @@ import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import "./ha-combo-box";
|
||||
import type { HaComboBox } from "./ha-combo-box";
|
||||
import "./ha-combo-box-item";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-list-item";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
type ScorableAreaRegistryEntry = ScorableTextItem & AreaRegistryEntry;
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (item) =>
|
||||
html`<ha-list-item
|
||||
graphic="icon"
|
||||
class=${classMap({ "add-new": item.area_id === ADD_NEW_ID })}
|
||||
>
|
||||
${item.icon
|
||||
? html`<ha-icon slot="start" .icon=${item.icon}></ha-icon>`
|
||||
: html`<ha-svg-icon slot="start" .path=${mdiTextureBox}></ha-svg-icon>`}
|
||||
? html`<ha-icon slot="graphic" .icon=${item.icon}></ha-icon>`
|
||||
: html`<ha-svg-icon slot="graphic" .path=${mdiTextureBox}></ha-svg-icon>`}
|
||||
${item.name}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
</ha-list-item>`;
|
||||
|
||||
const ADD_NEW_ID = "___ADD_NEW___";
|
||||
const NO_ITEMS_ID = "___NO_ITEMS___";
|
||||
|
@ -1,102 +0,0 @@
|
||||
import { mdiTextureBox } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { getAreaContext } from "../common/entity/context/get_area_context";
|
||||
import { areaCompare } from "../data/area_registry";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-expansion-panel";
|
||||
import "./ha-items-display-editor";
|
||||
import type { DisplayItem, DisplayValue } from "./ha-items-display-editor";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-textfield";
|
||||
|
||||
export interface AreasDisplayValue {
|
||||
hidden?: string[];
|
||||
order?: string[];
|
||||
}
|
||||
|
||||
@customElement("ha-areas-display-editor")
|
||||
export class HaAreasDisplayEditor extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ attribute: false }) public value?: AreasDisplayValue;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public expanded = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "show-navigation-button" })
|
||||
public showNavigationButton = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const compare = areaCompare(this.hass.areas);
|
||||
|
||||
const areas = Object.values(this.hass.areas).sort((areaA, areaB) =>
|
||||
compare(areaA.area_id, areaB.area_id)
|
||||
);
|
||||
|
||||
const items: DisplayItem[] = areas.map((area) => {
|
||||
const { floor } = getAreaContext(area.area_id, this.hass!);
|
||||
return {
|
||||
value: area.area_id,
|
||||
label: area.name,
|
||||
icon: area.icon ?? undefined,
|
||||
iconPath: mdiTextureBox,
|
||||
description: floor?.name,
|
||||
};
|
||||
});
|
||||
|
||||
const value: DisplayValue = {
|
||||
order: this.value?.order ?? [],
|
||||
hidden: this.value?.hidden ?? [],
|
||||
};
|
||||
|
||||
return html`
|
||||
<ha-expansion-panel
|
||||
outlined
|
||||
.header=${this.label}
|
||||
.expanded=${this.expanded}
|
||||
>
|
||||
<ha-svg-icon slot="leading-icon" .path=${mdiTextureBox}></ha-svg-icon>
|
||||
<ha-items-display-editor
|
||||
.hass=${this.hass}
|
||||
.items=${items}
|
||||
.value=${value}
|
||||
@value-changed=${this._areaDisplayChanged}
|
||||
.showNavigationButton=${this.showNavigationButton}
|
||||
></ha-items-display-editor>
|
||||
</ha-expansion-panel>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _areaDisplayChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail.value as DisplayValue;
|
||||
const newValue: AreasDisplayValue = {
|
||||
...this.value,
|
||||
...value,
|
||||
};
|
||||
if (newValue.hidden?.length === 0) {
|
||||
delete newValue.hidden;
|
||||
}
|
||||
if (newValue.order?.length === 0) {
|
||||
delete newValue.order;
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-areas-display-editor": HaAreasDisplayEditor;
|
||||
}
|
||||
}
|
@ -295,7 +295,6 @@ export class HaAssistChat extends LitElement {
|
||||
this._addMessage(userMessage);
|
||||
this.requestUpdate("_audioRecorder");
|
||||
|
||||
let continueConversation = false;
|
||||
let hassMessage = {
|
||||
who: "hass",
|
||||
text: "…",
|
||||
@ -370,8 +369,6 @@ export class HaAssistChat extends LitElement {
|
||||
|
||||
if (event.type === "intent-end") {
|
||||
this._conversationId = event.data.intent_output.conversation_id;
|
||||
continueConversation =
|
||||
event.data.intent_output.continue_conversation;
|
||||
const plain = event.data.intent_output.response.speech?.plain;
|
||||
if (plain) {
|
||||
hassMessage.text = plain.speech;
|
||||
@ -383,12 +380,7 @@ export class HaAssistChat extends LitElement {
|
||||
const url = event.data.tts_output.url;
|
||||
this._audio = new Audio(url);
|
||||
this._audio.play();
|
||||
this._audio.addEventListener("ended", () => {
|
||||
this._unloadAudio();
|
||||
if (continueConversation) {
|
||||
this._startListening();
|
||||
}
|
||||
});
|
||||
this._audio.addEventListener("ended", this._unloadAudio);
|
||||
this._audio.addEventListener("pause", this._unloadAudio);
|
||||
this._audio.addEventListener("canplaythrough", this._playAudio);
|
||||
this._audio.addEventListener("error", this._audioError);
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { css, html, LitElement, nothing, type PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
@ -33,10 +32,6 @@ export class HaCameraStream extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public stateObj?: CameraEntity;
|
||||
|
||||
@property({ attribute: false }) public aspectRatio?: number;
|
||||
|
||||
@property({ attribute: false }) public fitMode?: "cover" | "contain" | "fill";
|
||||
|
||||
@property({ type: Boolean, attribute: "controls" })
|
||||
public controls = false;
|
||||
|
||||
@ -106,10 +101,6 @@ export class HaCameraStream extends LitElement {
|
||||
: this._connected
|
||||
? computeMJPEGStreamUrl(this.stateObj)
|
||||
: this._posterUrl || ""}
|
||||
style=${styleMap({
|
||||
aspectRatio: this.aspectRatio,
|
||||
objectFit: this.fitMode,
|
||||
})}
|
||||
alt=${`Preview of the ${computeStateName(this.stateObj)} camera.`}
|
||||
/>`;
|
||||
}
|
||||
@ -126,8 +117,6 @@ export class HaCameraStream extends LitElement {
|
||||
.posterUrl=${this._posterUrl}
|
||||
@streams=${this._handleHlsStreams}
|
||||
class=${stream.visible ? "" : "hidden"}
|
||||
.aspectRatio=${this.aspectRatio}
|
||||
.fitMode=${this.fitMode}
|
||||
></ha-hls-player>`;
|
||||
}
|
||||
|
||||
@ -142,8 +131,6 @@ export class HaCameraStream extends LitElement {
|
||||
.posterUrl=${this._posterUrl}
|
||||
@streams=${this._handleWebRtcStreams}
|
||||
class=${stream.visible ? "" : "hidden"}
|
||||
.aspectRatio=${this.aspectRatio}
|
||||
.fitMode=${this.fitMode}
|
||||
></ha-web-rtc-player>`;
|
||||
}
|
||||
|
||||
@ -272,16 +259,6 @@ export class HaCameraStream extends LitElement {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ha-web-rtc-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
ha-hls-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
49
src/components/ha-circular-progress.ts
Normal file
49
src/components/ha-circular-progress.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { MdCircularProgress } from "@material/web/progress/circular-progress";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
|
||||
@customElement("ha-circular-progress")
|
||||
export class HaCircularProgress extends MdCircularProgress {
|
||||
@property({ attribute: "aria-label", type: String }) public ariaLabel =
|
||||
"Loading";
|
||||
|
||||
@property() public size?: "tiny" | "small" | "medium" | "large";
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("size")) {
|
||||
switch (this.size) {
|
||||
case "tiny":
|
||||
this.style.setProperty("--md-circular-progress-size", "16px");
|
||||
break;
|
||||
case "small":
|
||||
this.style.setProperty("--md-circular-progress-size", "28px");
|
||||
break;
|
||||
case "medium":
|
||||
this.style.setProperty("--md-circular-progress-size", "48px");
|
||||
break;
|
||||
case "large":
|
||||
this.style.setProperty("--md-circular-progress-size", "68px");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
...super.styles,
|
||||
css`
|
||||
:host {
|
||||
--md-sys-color-primary: var(--primary-color);
|
||||
--md-circular-progress-size: 48px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-circular-progress": HaCircularProgress;
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
import { css } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { HaMdListItem } from "./ha-md-list-item";
|
||||
|
||||
@customElement("ha-combo-box-item")
|
||||
export class HaComboBoxItem extends HaMdListItem {
|
||||
@property({ type: Boolean, reflect: true, attribute: "border-top" })
|
||||
public borderTop = false;
|
||||
|
||||
static override styles = [
|
||||
...super.styles,
|
||||
css`
|
||||
:host {
|
||||
--md-list-item-one-line-container-height: 48px;
|
||||
--md-list-item-two-line-container-height: 64px;
|
||||
}
|
||||
:host([border-top]) md-item {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
[slot="start"] {
|
||||
--paper-item-icon-color: var(--secondary-text-color);
|
||||
}
|
||||
[slot="headline"] {
|
||||
line-height: 22px;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
[slot="supporting-text"] {
|
||||
line-height: 18px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
::slotted(state-badge),
|
||||
::slotted(img) {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-combo-box-item": HaComboBoxItem;
|
||||
}
|
||||
}
|
@ -16,8 +16,8 @@ import { customElement, property, query } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-combo-box-item";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-list-item";
|
||||
import "./ha-textfield";
|
||||
import type { HaTextField } from "./ha-textfield";
|
||||
|
||||
@ -105,9 +105,6 @@ export class HaComboBox extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public opened = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "hide-clear-icon" })
|
||||
public hideClearIcon = false;
|
||||
|
||||
@query("vaadin-combo-box-light", true) private _comboBox!: ComboBoxLight;
|
||||
|
||||
@query("ha-textfield", true) private _inputElement!: HaTextField;
|
||||
@ -190,7 +187,7 @@ export class HaComboBox extends LitElement {
|
||||
>
|
||||
<slot name="icon" slot="leadingIcon"></slot>
|
||||
</ha-textfield>
|
||||
${this.value && !this.hideClearIcon
|
||||
${this.value
|
||||
? html`<ha-svg-icon
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
@ -207,7 +204,6 @@ export class HaComboBox extends LitElement {
|
||||
aria-expanded=${this.opened ? "true" : "false"}
|
||||
class="toggle-button"
|
||||
.path=${this.opened ? mdiMenuUp : mdiMenuDown}
|
||||
?disabled=${this.disabled}
|
||||
@click=${this._toggleOpen}
|
||||
></ha-svg-icon>
|
||||
</vaadin-combo-box-light>
|
||||
@ -216,11 +212,10 @@ export class HaComboBox extends LitElement {
|
||||
|
||||
private _defaultRowRenderer: ComboBoxLitRenderer<
|
||||
string | Record<string, any>
|
||||
> = (item) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
> = (item) =>
|
||||
html`<ha-list-item>
|
||||
${this.itemLabelPath ? item[this.itemLabelPath] : item}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
</ha-list-item>`;
|
||||
|
||||
private _clearValue(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
@ -361,10 +356,6 @@ export class HaComboBox extends LitElement {
|
||||
:host([opened]) .toggle-button {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.toggle-button[disabled] {
|
||||
color: var(--disabled-text-color);
|
||||
pointer-events: none;
|
||||
}
|
||||
.clear-button {
|
||||
--mdc-icon-size: 20px;
|
||||
top: -7px;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
@ -10,7 +11,6 @@ import type { ValueChangedEvent, HomeAssistant } from "../types";
|
||||
import { brandsUrl } from "../util/brands-url";
|
||||
import "./ha-combo-box";
|
||||
import type { HaComboBox } from "./ha-combo-box";
|
||||
import "./ha-combo-box-item";
|
||||
|
||||
export interface ConfigEntryExtended extends ConfigEntry {
|
||||
localized_domain_name?: string;
|
||||
@ -48,20 +48,18 @@ class HaConfigEntryPicker extends LitElement {
|
||||
this._getConfigEntries();
|
||||
}
|
||||
|
||||
private _rowRenderer: ComboBoxLitRenderer<ConfigEntryExtended> = (
|
||||
item
|
||||
) => html`
|
||||
<ha-combo-box-item type="button">
|
||||
<span slot="headline">
|
||||
${item.title ||
|
||||
private _rowRenderer: ComboBoxLitRenderer<ConfigEntryExtended> = (item) =>
|
||||
html`<mwc-list-item twoline graphic="icon">
|
||||
<span
|
||||
>${item.title ||
|
||||
this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.unnamed_entry"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">${item.localized_domain_name}</span>
|
||||
)}</span
|
||||
>
|
||||
<span slot="secondary">${item.localized_domain_name}</span>
|
||||
<img
|
||||
alt=""
|
||||
slot="start"
|
||||
slot="graphic"
|
||||
src=${brandsUrl({
|
||||
domain: item.domain,
|
||||
type: "icon",
|
||||
@ -72,8 +70,7 @@ class HaConfigEntryPicker extends LitElement {
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
/>
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
</mwc-list-item>`;
|
||||
|
||||
protected render() {
|
||||
if (!this._configEntries) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { SelectBase } from "@material/mwc-select/mwc-select-base";
|
||||
import { mdiMenuDown } from "@mdi/js";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@ -25,16 +24,6 @@ export class HaControlSelectMenu extends SelectBase {
|
||||
@property({ type: Boolean, attribute: "hide-label" })
|
||||
public hideLabel = false;
|
||||
|
||||
@property() public options;
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.get("options")) {
|
||||
this.layoutOptions();
|
||||
this.selectByValue(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
public override render() {
|
||||
const classes = {
|
||||
"select-disabled": this.disabled,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DIRECTION_ALL, Manager, Pan, Press, Tap } from "@egjs/hammerjs";
|
||||
import { DIRECTION_ALL, Manager, Pan, Tap } from "@egjs/hammerjs";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
@ -159,7 +159,6 @@ export class HaControlSlider extends LitElement {
|
||||
);
|
||||
|
||||
this._mc.add(new Tap({ event: "singletap" }));
|
||||
this._mc.add(new Press());
|
||||
|
||||
let savedValue;
|
||||
this._mc.on("panstart", () => {
|
||||
@ -191,7 +190,7 @@ export class HaControlSlider extends LitElement {
|
||||
fireEvent(this, "value-changed", { value: this.value });
|
||||
});
|
||||
|
||||
this._mc.on("singletap pressup", (e) => {
|
||||
this._mc.on("singletap", (e) => {
|
||||
if (this.disabled) return;
|
||||
const percentage = this._getPercentageFromEvent(e);
|
||||
this.value = this.steppedValue(this.percentageToValue(percentage));
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user