mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 03:06:41 +00:00
20240529.0 (#20901)
This commit is contained in:
commit
1a2daf8a7a
@ -115,6 +115,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"unused-imports/no-unused-imports": "error",
|
"unused-imports/no-unused-imports": "error",
|
||||||
|
"lit/attribute-names": "warn",
|
||||||
"lit/attribute-value-entities": "off",
|
"lit/attribute-value-entities": "off",
|
||||||
"lit/no-template-map": "off",
|
"lit/no-template-map": "off",
|
||||||
"lit/no-native-attributes": "warn",
|
"lit/no-native-attributes": "warn",
|
||||||
|
4
.github/workflows/cast_deployment.yaml
vendored
4
.github/workflows/cast_deployment.yaml
vendored
@ -21,7 +21,7 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.4
|
uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: dev
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.4
|
uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
|
8
.github/workflows/ci.yaml
vendored
8
.github/workflows/ci.yaml
vendored
@ -24,7 +24,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.4
|
uses: actions/checkout@v4.1.6
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -58,7 +58,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.4
|
uses: actions/checkout@v4.1.6
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -76,7 +76,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.4
|
uses: actions/checkout@v4.1.6
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
@ -100,7 +100,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.4
|
uses: actions/checkout@v4.1.6
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.4
|
uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
# We must fetch at least the immediate parents so that if this is
|
# We must fetch at least the immediate parents so that if this is
|
||||||
# a pull request then we can checkout the head.
|
# a pull request then we can checkout the head.
|
||||||
|
4
.github/workflows/demo_deployment.yaml
vendored
4
.github/workflows/demo_deployment.yaml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.4
|
uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: dev
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.4
|
uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
|
2
.github/workflows/design_deployment.yaml
vendored
2
.github/workflows/design_deployment.yaml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.4
|
uses: actions/checkout@v4.1.6
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.2
|
||||||
|
2
.github/workflows/design_preview.yaml
vendored
2
.github/workflows/design_preview.yaml
vendored
@ -21,7 +21,7 @@ jobs:
|
|||||||
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.4
|
uses: actions/checkout@v4.1.6
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.2
|
||||||
|
2
.github/workflows/nightly.yaml
vendored
2
.github/workflows/nightly.yaml
vendored
@ -20,7 +20,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.4
|
uses: actions/checkout@v4.1.6
|
||||||
|
|
||||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
|
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
contents: write # Required to upload release assets
|
contents: write # Required to upload release assets
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.4
|
uses: actions/checkout@v4.1.6
|
||||||
|
|
||||||
- name: Verify version
|
- name: Verify version
|
||||||
uses: home-assistant/actions/helpers/verify-version@master
|
uses: home-assistant/actions/helpers/verify-version@master
|
||||||
@ -55,7 +55,7 @@ jobs:
|
|||||||
script/release
|
script/release
|
||||||
|
|
||||||
- name: Upload release assets
|
- name: Upload release assets
|
||||||
uses: softprops/action-gh-release@v2.0.4
|
uses: softprops/action-gh-release@v2.0.5
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
dist/*.whl
|
dist/*.whl
|
||||||
|
2
.github/workflows/translations.yaml
vendored
2
.github/workflows/translations.yaml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.4
|
uses: actions/checkout@v4.1.6
|
||||||
|
|
||||||
- name: Upload Translations
|
- name: Upload Translations
|
||||||
run: |
|
run: |
|
||||||
|
File diff suppressed because one or more lines are too long
@ -6,4 +6,4 @@ enableGlobalCache: false
|
|||||||
|
|
||||||
nodeLinker: node-modules
|
nodeLinker: node-modules
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-4.1.1.cjs
|
yarnPath: .yarn/releases/yarn-4.2.2.cjs
|
||||||
|
@ -1,7 +1,56 @@
|
|||||||
import defineProvider from "@babel/helper-define-polyfill-provider";
|
import defineProvider from "@babel/helper-define-polyfill-provider";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import paths from "../paths.cjs";
|
||||||
|
|
||||||
|
const POLYFILL_DIR = join(paths.polymer_dir, "src/resources/polyfills");
|
||||||
|
|
||||||
// List of polyfill keys with supported browser targets for the functionality
|
// List of polyfill keys with supported browser targets for the functionality
|
||||||
const PolyfillSupport = {
|
const PolyfillSupport = {
|
||||||
|
// Note states and shadowRoot properties should be supported.
|
||||||
|
"element-internals": {
|
||||||
|
android: 90,
|
||||||
|
chrome: 90,
|
||||||
|
edge: 90,
|
||||||
|
firefox: 126,
|
||||||
|
ios: 17.4,
|
||||||
|
opera: 76,
|
||||||
|
opera_mobile: 64,
|
||||||
|
safari: 17.4,
|
||||||
|
samsung: 15.0,
|
||||||
|
},
|
||||||
|
"element-append": {
|
||||||
|
android: 54,
|
||||||
|
chrome: 54,
|
||||||
|
edge: 17,
|
||||||
|
firefox: 49,
|
||||||
|
ios: 10.0,
|
||||||
|
opera: 41,
|
||||||
|
opera_mobile: 41,
|
||||||
|
safari: 10.0,
|
||||||
|
samsung: 6.0,
|
||||||
|
},
|
||||||
|
"element-getattributenames": {
|
||||||
|
android: 61,
|
||||||
|
chrome: 61,
|
||||||
|
edge: 18,
|
||||||
|
firefox: 45,
|
||||||
|
ios: 10.3,
|
||||||
|
opera: 48,
|
||||||
|
opera_mobile: 45,
|
||||||
|
safari: 10.1,
|
||||||
|
samsung: 8.0,
|
||||||
|
},
|
||||||
|
"element-toggleattribute": {
|
||||||
|
android: 69,
|
||||||
|
chrome: 69,
|
||||||
|
edge: 18,
|
||||||
|
firefox: 63,
|
||||||
|
ios: 12.0,
|
||||||
|
opera: 56,
|
||||||
|
opera_mobile: 48,
|
||||||
|
safari: 12.0,
|
||||||
|
samsung: 10.0,
|
||||||
|
},
|
||||||
fetch: {
|
fetch: {
|
||||||
android: 42,
|
android: 42,
|
||||||
chrome: 42,
|
chrome: 42,
|
||||||
@ -13,6 +62,31 @@ const PolyfillSupport = {
|
|||||||
safari: 10.1,
|
safari: 10.1,
|
||||||
samsung: 4.0,
|
samsung: 4.0,
|
||||||
},
|
},
|
||||||
|
"intl-getcanonicallocales": {
|
||||||
|
android: 54,
|
||||||
|
chrome: 54,
|
||||||
|
edge: 16,
|
||||||
|
firefox: 48,
|
||||||
|
ios: 10.3,
|
||||||
|
opera: 41,
|
||||||
|
opera_mobile: 41,
|
||||||
|
safari: 10.1,
|
||||||
|
samsung: 6.0,
|
||||||
|
},
|
||||||
|
"intl-locale": {
|
||||||
|
android: 74,
|
||||||
|
chrome: 74,
|
||||||
|
edge: 79,
|
||||||
|
firefox: 75,
|
||||||
|
ios: 14.0,
|
||||||
|
opera: 62,
|
||||||
|
opera_mobile: 53,
|
||||||
|
safari: 14.0,
|
||||||
|
samsung: 11.0,
|
||||||
|
},
|
||||||
|
"intl-other": {
|
||||||
|
// Not specified (i.e. always try polyfill) since compatibility depends on supported locales
|
||||||
|
},
|
||||||
proxy: {
|
proxy: {
|
||||||
android: 49,
|
android: 49,
|
||||||
chrome: 49,
|
chrome: 49,
|
||||||
@ -24,17 +98,67 @@ const PolyfillSupport = {
|
|||||||
safari: 10.0,
|
safari: 10.0,
|
||||||
samsung: 5.0,
|
samsung: 5.0,
|
||||||
},
|
},
|
||||||
|
"resize-observer": {
|
||||||
|
android: 64,
|
||||||
|
chrome: 64,
|
||||||
|
edge: 79,
|
||||||
|
firefox: 69,
|
||||||
|
ios: 13.4,
|
||||||
|
opera: 51,
|
||||||
|
opera_mobile: 47,
|
||||||
|
safari: 13.1,
|
||||||
|
samsung: 9.0,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Map of global variables and/or instance and static properties to the
|
// Map of global variables and/or instance and static properties to the
|
||||||
// corresponding polyfill key and actual module to import
|
// corresponding polyfill key and actual module to import
|
||||||
const polyfillMap = {
|
const polyfillMap = {
|
||||||
global: {
|
global: {
|
||||||
Proxy: { key: "proxy", module: "proxy-polyfill" },
|
|
||||||
fetch: { key: "fetch", module: "unfetch/polyfill" },
|
fetch: { key: "fetch", module: "unfetch/polyfill" },
|
||||||
|
Proxy: { key: "proxy", module: "proxy-polyfill" },
|
||||||
|
ResizeObserver: {
|
||||||
|
key: "resize-observer",
|
||||||
|
module: join(POLYFILL_DIR, "resize-observer.ts"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
instance: {
|
||||||
|
attachInternals: {
|
||||||
|
key: "element-internals",
|
||||||
|
module: "element-internals-polyfill",
|
||||||
|
},
|
||||||
|
...Object.fromEntries(
|
||||||
|
["append", "getAttributeNames", "toggleAttribute"].map((prop) => {
|
||||||
|
const key = `element-${prop.toLowerCase()}`;
|
||||||
|
return [prop, { key, module: join(POLYFILL_DIR, `${key}.ts`) }];
|
||||||
|
})
|
||||||
|
),
|
||||||
|
},
|
||||||
|
static: {
|
||||||
|
Intl: {
|
||||||
|
getCanonicalLocales: {
|
||||||
|
key: "intl-getcanonicallocales",
|
||||||
|
module: join(POLYFILL_DIR, "intl-polyfill.ts"),
|
||||||
|
},
|
||||||
|
Locale: {
|
||||||
|
key: "intl-locale",
|
||||||
|
module: join(POLYFILL_DIR, "intl-polyfill.ts"),
|
||||||
|
},
|
||||||
|
...Object.fromEntries(
|
||||||
|
[
|
||||||
|
"DateTimeFormat",
|
||||||
|
"DisplayNames",
|
||||||
|
"ListFormat",
|
||||||
|
"NumberFormat",
|
||||||
|
"PluralRules",
|
||||||
|
"RelativeTimeFormat",
|
||||||
|
].map((obj) => [
|
||||||
|
obj,
|
||||||
|
{ key: "intl-other", module: join(POLYFILL_DIR, "intl-polyfill.ts") },
|
||||||
|
])
|
||||||
|
),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
instance: {},
|
|
||||||
static: {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create plugin using the same factory as for CoreJS
|
// Create plugin using the same factory as for CoreJS
|
||||||
@ -42,14 +166,16 @@ export default defineProvider(
|
|||||||
({ createMetaResolver, debug, shouldInjectPolyfill }) => {
|
({ createMetaResolver, debug, shouldInjectPolyfill }) => {
|
||||||
const resolvePolyfill = createMetaResolver(polyfillMap);
|
const resolvePolyfill = createMetaResolver(polyfillMap);
|
||||||
return {
|
return {
|
||||||
name: "HA Custom",
|
name: "custom-polyfill",
|
||||||
polyfills: PolyfillSupport,
|
polyfills: PolyfillSupport,
|
||||||
usageGlobal(meta, utils) {
|
usageGlobal(meta, utils) {
|
||||||
const polyfill = resolvePolyfill(meta);
|
const polyfill = resolvePolyfill(meta);
|
||||||
if (polyfill && shouldInjectPolyfill(polyfill.desc.key)) {
|
if (polyfill && shouldInjectPolyfill(polyfill.desc.key)) {
|
||||||
debug(polyfill.desc.key);
|
debug(polyfill.desc.key);
|
||||||
utils.injectGlobalImport(polyfill.desc.module);
|
utils.injectGlobalImport(polyfill.desc.module);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ const env = require("./env.cjs");
|
|||||||
const paths = require("./paths.cjs");
|
const paths = require("./paths.cjs");
|
||||||
const { dependencies } = require("../package.json");
|
const { dependencies } = require("../package.json");
|
||||||
|
|
||||||
|
const BABEL_PLUGINS = path.join(__dirname, "babel-plugins");
|
||||||
|
|
||||||
// GitHub base URL to use for production source maps
|
// GitHub base URL to use for production source maps
|
||||||
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
|
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
|
||||||
module.exports.sourceMapURL = () => {
|
module.exports.sourceMapURL = () => {
|
||||||
@ -100,22 +102,12 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
|||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
[
|
[
|
||||||
path.resolve(
|
path.join(BABEL_PLUGINS, "inline-constants-plugin.cjs"),
|
||||||
paths.polymer_dir,
|
|
||||||
"build-scripts/babel-plugins/inline-constants-plugin.cjs"
|
|
||||||
),
|
|
||||||
{
|
{
|
||||||
modules: ["@mdi/js"],
|
modules: ["@mdi/js"],
|
||||||
ignoreModuleNotFound: true,
|
ignoreModuleNotFound: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
|
||||||
path.resolve(
|
|
||||||
paths.polymer_dir,
|
|
||||||
"build-scripts/babel-plugins/custom-polyfill-plugin.js"
|
|
||||||
),
|
|
||||||
{ method: "usage-global" },
|
|
||||||
],
|
|
||||||
// Minify template literals for production
|
// Minify template literals for production
|
||||||
isProdBuild && [
|
isProdBuild && [
|
||||||
"template-html-minifier",
|
"template-html-minifier",
|
||||||
@ -153,6 +145,27 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
|||||||
],
|
],
|
||||||
sourceMaps: !isTestBuild,
|
sourceMaps: !isTestBuild,
|
||||||
overrides: [
|
overrides: [
|
||||||
|
{
|
||||||
|
// Add plugin to inject various polyfills, excluding the polyfills
|
||||||
|
// themselves to prevent self-injection.
|
||||||
|
plugins: [
|
||||||
|
[
|
||||||
|
path.join(BABEL_PLUGINS, "custom-polyfill-plugin.js"),
|
||||||
|
{ method: "usage-global" },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
exclude: [
|
||||||
|
path.join(paths.polymer_dir, "src/resources/polyfills"),
|
||||||
|
...[
|
||||||
|
"@formatjs/intl-\\w+",
|
||||||
|
"@lit-labs/virtualizer/polyfills",
|
||||||
|
"@webcomponents/scoped-custom-element-registry",
|
||||||
|
"element-internals-polyfill",
|
||||||
|
"proxy-polyfill",
|
||||||
|
"unfetch",
|
||||||
|
].map((p) => new RegExp(`/node_modules/${p}/`)),
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// Use unambiguous for dependencies so that require() is correctly injected into CommonJS files
|
// Use unambiguous for dependencies so that require() is correctly injected into CommonJS files
|
||||||
// Exclusions are needed in some cases where ES modules have no static imports or exports, such as polyfills
|
// Exclusions are needed in some cases where ES modules have no static imports or exports, such as polyfills
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
|
/* eslint-disable max-classes-per-file */
|
||||||
|
|
||||||
import { deleteAsync } from "del";
|
import { deleteAsync } from "del";
|
||||||
import { glob } from "glob";
|
import { glob } from "glob";
|
||||||
import gulp from "gulp";
|
import gulp from "gulp";
|
||||||
import merge from "gulp-merge-json";
|
|
||||||
import rename from "gulp-rename";
|
import rename from "gulp-rename";
|
||||||
|
import merge from "lodash.merge";
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
import { mkdir, readFile } from "node:fs/promises";
|
import { mkdir, readFile } from "node:fs/promises";
|
||||||
import { basename, join } from "node:path";
|
import { basename, join } from "node:path";
|
||||||
import { Transform } from "node:stream";
|
import { PassThrough, Transform } from "node:stream";
|
||||||
import { finished } from "node:stream/promises";
|
import { finished } from "node:stream/promises";
|
||||||
import env from "../env.cjs";
|
import env from "../env.cjs";
|
||||||
import paths from "../paths.cjs";
|
import paths from "../paths.cjs";
|
||||||
@ -17,6 +19,7 @@ const inBackendDir = "translations/backend";
|
|||||||
const workDir = "build/translations";
|
const workDir = "build/translations";
|
||||||
const outDir = join(workDir, "output");
|
const outDir = join(workDir, "output");
|
||||||
const EN_SRC = join(paths.translations_src, "en.json");
|
const EN_SRC = join(paths.translations_src, "en.json");
|
||||||
|
const TEST_LOCALE = "en-x-test";
|
||||||
|
|
||||||
let mergeBackend = false;
|
let mergeBackend = false;
|
||||||
|
|
||||||
@ -54,6 +57,39 @@ class CustomJSON extends Transform {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transform stream to merge Vinyl JSON files (buffer mode only).
|
||||||
|
class MergeJSON extends Transform {
|
||||||
|
_objects = [];
|
||||||
|
|
||||||
|
constructor(stem, startObj = {}, reviver = null) {
|
||||||
|
super({ objectMode: true, allowHalfOpen: false });
|
||||||
|
this._stem = stem;
|
||||||
|
this._startObj = structuredClone(startObj);
|
||||||
|
this._reviver = reviver;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _transform(file, _, callback) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _flush(callback) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Utility to flatten object keys to single level using separator
|
// Utility to flatten object keys to single level using separator
|
||||||
const flatten = (data, prefix = "", sep = ".") => {
|
const flatten = (data, prefix = "", sep = ".") => {
|
||||||
const output = {};
|
const output = {};
|
||||||
@ -115,7 +151,7 @@ const createTestTranslation = () =>
|
|||||||
: gulp
|
: gulp
|
||||||
.src(EN_SRC)
|
.src(EN_SRC)
|
||||||
.pipe(new CustomJSON(null, testReviver))
|
.pipe(new CustomJSON(null, testReviver))
|
||||||
.pipe(rename("test.json"))
|
.pipe(rename(`${TEST_LOCALE}.json`))
|
||||||
.pipe(gulp.dest(workDir));
|
.pipe(gulp.dest(workDir));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,12 +167,7 @@ const createMasterTranslation = () =>
|
|||||||
gulp
|
gulp
|
||||||
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
|
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
|
||||||
.pipe(new CustomJSON(lokaliseTransform))
|
.pipe(new CustomJSON(lokaliseTransform))
|
||||||
.pipe(
|
.pipe(new MergeJSON("en"))
|
||||||
merge({
|
|
||||||
fileName: "en.json",
|
|
||||||
jsonSpace: undefined,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest(workDir));
|
.pipe(gulp.dest(workDir));
|
||||||
|
|
||||||
const FRAGMENTS = ["base"];
|
const FRAGMENTS = ["base"];
|
||||||
@ -162,7 +193,7 @@ const createTranslations = async () => {
|
|||||||
// each locale, then fragmentizes and flattens the data for final output.
|
// each locale, then fragmentizes and flattens the data for final output.
|
||||||
const translationFiles = await glob([
|
const translationFiles = await glob([
|
||||||
`${inFrontendDir}/!(en).json`,
|
`${inFrontendDir}/!(en).json`,
|
||||||
...(env.isProdBuild() ? [] : [`${workDir}/test.json`]),
|
...(env.isProdBuild() ? [] : [`${workDir}/${TEST_LOCALE}.json`]),
|
||||||
]);
|
]);
|
||||||
const hashStream = new Transform({
|
const hashStream = new Transform({
|
||||||
objectMode: true,
|
objectMode: true,
|
||||||
@ -213,7 +244,10 @@ const createTranslations = async () => {
|
|||||||
// TODO: This is a naive interpretation of BCP47 that should be improved.
|
// TODO: This is a naive interpretation of BCP47 that should be improved.
|
||||||
// Will be OK for now as long as we don't have anything more complicated
|
// Will be OK for now as long as we don't have anything more complicated
|
||||||
// than a base translation + region.
|
// than a base translation + region.
|
||||||
gulp.src(`${workDir}/en.json`).pipe(hashStream, { end: false });
|
gulp
|
||||||
|
.src(`${workDir}/en.json`)
|
||||||
|
.pipe(new PassThrough({ objectMode: true }))
|
||||||
|
.pipe(hashStream, { end: false });
|
||||||
const mergesFinished = [];
|
const mergesFinished = [];
|
||||||
for (const translationFile of translationFiles) {
|
for (const translationFile of translationFiles) {
|
||||||
const locale = basename(translationFile, ".json");
|
const locale = basename(translationFile, ".json");
|
||||||
@ -221,8 +255,8 @@ const createTranslations = async () => {
|
|||||||
const mergeFiles = [];
|
const mergeFiles = [];
|
||||||
for (let i = 1; i <= subtags.length; i++) {
|
for (let i = 1; i <= subtags.length; i++) {
|
||||||
const lang = subtags.slice(0, i).join("-");
|
const lang = subtags.slice(0, i).join("-");
|
||||||
if (lang === "test") {
|
if (lang === TEST_LOCALE) {
|
||||||
mergeFiles.push(`${workDir}/test.json`);
|
mergeFiles.push(`${workDir}/${TEST_LOCALE}.json`);
|
||||||
} else if (lang !== "en") {
|
} else if (lang !== "en") {
|
||||||
mergeFiles.push(`${inFrontendDir}/${lang}.json`);
|
mergeFiles.push(`${inFrontendDir}/${lang}.json`);
|
||||||
if (mergeBackend) {
|
if (mergeBackend) {
|
||||||
@ -230,14 +264,9 @@ const createTranslations = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const mergeStream = gulp.src(mergeFiles, { allowEmpty: true }).pipe(
|
const mergeStream = gulp
|
||||||
merge({
|
.src(mergeFiles, { allowEmpty: true })
|
||||||
fileName: `${locale}.json`,
|
.pipe(new MergeJSON(locale, enMaster, emptyReviver));
|
||||||
startObj: enMaster,
|
|
||||||
jsonReviver: emptyReviver,
|
|
||||||
jsonSpace: undefined,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
mergesFinished.push(finished(mergeStream));
|
mergesFinished.push(finished(mergeStream));
|
||||||
mergeStream.pipe(hashStream, { end: false });
|
mergeStream.pipe(hashStream, { end: false });
|
||||||
}
|
}
|
||||||
@ -256,7 +285,7 @@ const writeTranslationMetaData = () =>
|
|||||||
new CustomJSON((meta) => {
|
new CustomJSON((meta) => {
|
||||||
// Add the test translation in development.
|
// Add the test translation in development.
|
||||||
if (!env.isProdBuild()) {
|
if (!env.isProdBuild()) {
|
||||||
meta.test = { nativeName: "Test" };
|
meta[TEST_LOCALE] = { nativeName: "Translation Test" };
|
||||||
}
|
}
|
||||||
// Filter out locales without a native name, and add the hashes.
|
// Filter out locales without a native name, and add the hashes.
|
||||||
for (const locale of Object.keys(meta)) {
|
for (const locale of Object.keys(meta)) {
|
||||||
|
@ -10,6 +10,7 @@ const WebpackBar = require("webpackbar");
|
|||||||
const {
|
const {
|
||||||
TransformAsyncModulesPlugin,
|
TransformAsyncModulesPlugin,
|
||||||
} = require("transform-async-modules-webpack-plugin");
|
} = require("transform-async-modules-webpack-plugin");
|
||||||
|
const { dependencies } = require("../package.json");
|
||||||
const paths = require("./paths.cjs");
|
const paths = require("./paths.cjs");
|
||||||
const bundle = require("./bundle.cjs");
|
const bundle = require("./bundle.cjs");
|
||||||
|
|
||||||
@ -156,7 +157,10 @@ const createWebpackConfig = ({
|
|||||||
transform: (stats) => JSON.stringify(filterStats(stats)),
|
transform: (stats) => JSON.stringify(filterStats(stats)),
|
||||||
}),
|
}),
|
||||||
!latestBuild &&
|
!latestBuild &&
|
||||||
new TransformAsyncModulesPlugin({ browserslistEnv: "legacy" }),
|
new TransformAsyncModulesPlugin({
|
||||||
|
browserslistEnv: "legacy",
|
||||||
|
runtime: { version: dependencies["@babel/runtime"] },
|
||||||
|
}),
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".ts", ".js", ".json"],
|
extensions: [".ts", ".js", ".json"],
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import { mdiCast, mdiCastConnected } from "@mdi/js";
|
import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js";
|
||||||
import "@polymer/paper-item/paper-icon-item";
|
import "@polymer/paper-item/paper-icon-item";
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
import { Auth, Connection } from "home-assistant-js-websocket";
|
import { Auth, Connection } from "home-assistant-js-websocket";
|
||||||
@ -104,8 +104,11 @@ class HcCast extends LitElement {
|
|||||||
slot="item-icon"
|
slot="item-icon"
|
||||||
></ha-icon>
|
></ha-icon>
|
||||||
`
|
`
|
||||||
: ""}
|
: html`<ha-svg-icon
|
||||||
${view.title || view.path}
|
slot="item-icon"
|
||||||
|
.path=${mdiViewDashboard}
|
||||||
|
></ha-svg-icon>`}
|
||||||
|
${view.title || view.path || "Unnamed view"}
|
||||||
</paper-icon-item>
|
</paper-icon-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
@ -250,7 +253,8 @@ class HcCast extends LitElement {
|
|||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-listbox ha-icon {
|
paper-listbox ha-icon,
|
||||||
|
paper-listbox ha-svg-icon {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import { LovelaceConfig } from "../../../../src/data/lovelace/config/types";
|
import { LovelaceConfig } from "../../../../src/data/lovelace/config/types";
|
||||||
|
import { getPanelTitleFromUrlPath } from "../../../../src/data/panel";
|
||||||
import { Lovelace } from "../../../../src/panels/lovelace/types";
|
import { Lovelace } from "../../../../src/panels/lovelace/types";
|
||||||
import "../../../../src/panels/lovelace/views/hui-view";
|
import "../../../../src/panels/lovelace/views/hui-view";
|
||||||
import { HomeAssistant } from "../../../../src/types";
|
import { HomeAssistant } from "../../../../src/types";
|
||||||
@ -61,7 +62,12 @@ class HcLovelace extends LitElement {
|
|||||||
const index = this._viewIndex;
|
const index = this._viewIndex;
|
||||||
|
|
||||||
if (index !== undefined) {
|
if (index !== undefined) {
|
||||||
const dashboardTitle = this.lovelaceConfig.title || this.urlPath;
|
const title = getPanelTitleFromUrlPath(
|
||||||
|
this.hass,
|
||||||
|
this.urlPath || "lovelace"
|
||||||
|
);
|
||||||
|
|
||||||
|
const dashboardTitle = title || this.urlPath;
|
||||||
|
|
||||||
const viewTitle =
|
const viewTitle =
|
||||||
this.lovelaceConfig.views[index].title ||
|
this.lovelaceConfig.views[index].title ||
|
||||||
@ -80,10 +86,17 @@ class HcLovelace extends LitElement {
|
|||||||
this.lovelaceConfig.views[index].background ||
|
this.lovelaceConfig.views[index].background ||
|
||||||
this.lovelaceConfig.background;
|
this.lovelaceConfig.background;
|
||||||
|
|
||||||
if (configBackground) {
|
const backgroundStyle =
|
||||||
|
typeof configBackground === "string"
|
||||||
|
? configBackground
|
||||||
|
: configBackground?.image
|
||||||
|
? `center / cover no-repeat url('${configBackground.image}')`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (backgroundStyle) {
|
||||||
this._huiView!.style.setProperty(
|
this._huiView!.style.setProperty(
|
||||||
"--lovelace-background",
|
"--lovelace-background",
|
||||||
configBackground
|
backgroundStyle
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this._huiView!.style.removeProperty("--lovelace-background");
|
this._huiView!.style.removeProperty("--lovelace-background");
|
||||||
|
@ -35,6 +35,7 @@ import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/lo
|
|||||||
import { HassElement } from "../../../../src/state/hass-element";
|
import { HassElement } from "../../../../src/state/hass-element";
|
||||||
import { castContext } from "../cast_context";
|
import { castContext } from "../cast_context";
|
||||||
import "./hc-launch-screen";
|
import "./hc-launch-screen";
|
||||||
|
import { getPanelTitleFromUrlPath } from "../../../../src/data/panel";
|
||||||
|
|
||||||
const DEFAULT_CONFIG: LovelaceDashboardStrategyConfig = {
|
const DEFAULT_CONFIG: LovelaceDashboardStrategyConfig = {
|
||||||
strategy: {
|
strategy: {
|
||||||
@ -359,7 +360,11 @@ export class HcMain extends HassElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) {
|
private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) {
|
||||||
castContext.setApplicationState(lovelaceConfig.title || "");
|
const title = getPanelTitleFromUrlPath(
|
||||||
|
this.hass!,
|
||||||
|
this._urlPath || "lovelace"
|
||||||
|
);
|
||||||
|
castContext.setApplicationState(title || "");
|
||||||
this._lovelaceConfig = lovelaceConfig;
|
this._lovelaceConfig = lovelaceConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +64,12 @@ const ACTIONS = [
|
|||||||
entity_id: "input_boolean.toggle_4",
|
entity_id: "input_boolean.toggle_4",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
sequence: [
|
||||||
|
{ scene: "scene.kitchen_morning" },
|
||||||
|
{ service: "light.turn_off", target: { entity_id: "light.kitchen" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
parallel: [
|
parallel: [
|
||||||
{ scene: "scene.kitchen_morning" },
|
{ scene: "scene.kitchen_morning" },
|
||||||
|
@ -20,6 +20,7 @@ import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation
|
|||||||
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
|
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
|
||||||
import { Action } from "../../../../src/data/script";
|
import { Action } from "../../../../src/data/script";
|
||||||
import { HaConditionAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-condition";
|
import { HaConditionAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-condition";
|
||||||
|
import { HaSequenceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-sequence";
|
||||||
import { HaParallelAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-parallel";
|
import { HaParallelAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-parallel";
|
||||||
import { HaIfAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-if";
|
import { HaIfAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-if";
|
||||||
import { HaStopAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-stop";
|
import { HaStopAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-stop";
|
||||||
@ -39,6 +40,7 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [
|
|||||||
{ name: "If-Then", actions: [HaIfAction.defaultConfig] },
|
{ name: "If-Then", actions: [HaIfAction.defaultConfig] },
|
||||||
{ name: "Choose", actions: [HaChooseAction.defaultConfig] },
|
{ name: "Choose", actions: [HaChooseAction.defaultConfig] },
|
||||||
{ name: "Variables", actions: [{ variables: { hello: "1" } }] },
|
{ name: "Variables", actions: [{ variables: { hello: "1" } }] },
|
||||||
|
{ name: "Sequence", actions: [HaSequenceAction.defaultConfig] },
|
||||||
{ name: "Parallel", actions: [HaParallelAction.defaultConfig] },
|
{ name: "Parallel", actions: [HaParallelAction.defaultConfig] },
|
||||||
{ name: "Stop", actions: [HaStopAction.defaultConfig] },
|
{ name: "Stop", actions: [HaStopAction.defaultConfig] },
|
||||||
];
|
];
|
||||||
|
@ -56,48 +56,46 @@ export class DemoDateTimeDateTimeNumeric extends LitElement {
|
|||||||
<div class="center">12 Hours</div>
|
<div class="center">12 Hours</div>
|
||||||
<div class="center">24 Hours</div>
|
<div class="center">24 Hours</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatDateTimeNumeric(
|
||||||
<div class="center">
|
this.date,
|
||||||
${formatDateTimeNumeric(
|
{
|
||||||
this.date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
time_format: TimeFormat.language,
|
||||||
language: key,
|
},
|
||||||
time_format: TimeFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateTimeNumeric(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.am_pm,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateTimeNumeric(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.twenty_four,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatDateTimeNumeric(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatDateTimeNumeric(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -56,48 +56,46 @@ export class DemoDateTimeDateTimeSeconds extends LitElement {
|
|||||||
<div class="center">12 Hours</div>
|
<div class="center">12 Hours</div>
|
||||||
<div class="center">24 Hours</div>
|
<div class="center">24 Hours</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatDateTimeWithSeconds(
|
||||||
<div class="center">
|
this.date,
|
||||||
${formatDateTimeWithSeconds(
|
{
|
||||||
this.date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
time_format: TimeFormat.language,
|
||||||
language: key,
|
},
|
||||||
time_format: TimeFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateTimeWithSeconds(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.am_pm,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateTimeWithSeconds(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.twenty_four,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatDateTimeWithSeconds(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatDateTimeWithSeconds(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -56,48 +56,46 @@ export class DemoDateTimeDateTimeShortYear extends LitElement {
|
|||||||
<div class="center">12 Hours</div>
|
<div class="center">12 Hours</div>
|
||||||
<div class="center">24 Hours</div>
|
<div class="center">24 Hours</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatShortDateTimeWithYear(
|
||||||
<div class="center">
|
this.date,
|
||||||
${formatShortDateTimeWithYear(
|
{
|
||||||
this.date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
time_format: TimeFormat.language,
|
||||||
language: key,
|
},
|
||||||
time_format: TimeFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatShortDateTimeWithYear(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.am_pm,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatShortDateTimeWithYear(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.twenty_four,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatShortDateTimeWithYear(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatShortDateTimeWithYear(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -56,48 +56,46 @@ export class DemoDateTimeDateTimeShort extends LitElement {
|
|||||||
<div class="center">12 Hours</div>
|
<div class="center">12 Hours</div>
|
||||||
<div class="center">24 Hours</div>
|
<div class="center">24 Hours</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatShortDateTime(
|
||||||
<div class="center">
|
this.date,
|
||||||
${formatShortDateTime(
|
{
|
||||||
this.date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
time_format: TimeFormat.language,
|
||||||
language: key,
|
},
|
||||||
time_format: TimeFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatShortDateTime(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.am_pm,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatShortDateTime(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.twenty_four,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatShortDateTime(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatShortDateTime(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -56,48 +56,46 @@ export class DemoDateTimeDateTime extends LitElement {
|
|||||||
<div class="center">12 Hours</div>
|
<div class="center">12 Hours</div>
|
||||||
<div class="center">24 Hours</div>
|
<div class="center">24 Hours</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatDateTime(
|
||||||
<div class="center">
|
this.date,
|
||||||
${formatDateTime(
|
{
|
||||||
this.date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
time_format: TimeFormat.language,
|
||||||
language: key,
|
},
|
||||||
time_format: TimeFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateTime(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.am_pm,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateTime(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.twenty_four,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatDateTime(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatDateTime(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -35,59 +35,57 @@ export class DemoDateTimeDate extends LitElement {
|
|||||||
<div class="center">Month-Day-Year</div>
|
<div class="center">Month-Day-Year</div>
|
||||||
<div class="center">Year-Month-Day</div>
|
<div class="center">Year-Month-Day</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatDateNumeric(
|
||||||
<div class="center">
|
date,
|
||||||
${formatDateNumeric(
|
{
|
||||||
date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
date_format: DateFormat.language,
|
||||||
language: key,
|
},
|
||||||
date_format: DateFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateNumeric(
|
|
||||||
date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
date_format: DateFormat.DMY,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateNumeric(
|
|
||||||
date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
date_format: DateFormat.MDY,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateNumeric(
|
|
||||||
date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
date_format: DateFormat.YMD,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatDateNumeric(
|
||||||
|
date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
date_format: DateFormat.DMY,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatDateNumeric(
|
||||||
|
date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
date_format: DateFormat.MDY,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatDateNumeric(
|
||||||
|
date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
date_format: DateFormat.YMD,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -56,48 +56,46 @@ export class DemoDateTimeTimeSeconds extends LitElement {
|
|||||||
<div class="center">12 Hours</div>
|
<div class="center">12 Hours</div>
|
||||||
<div class="center">24 Hours</div>
|
<div class="center">24 Hours</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatTimeWithSeconds(
|
||||||
<div class="center">
|
this.date,
|
||||||
${formatTimeWithSeconds(
|
{
|
||||||
this.date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
time_format: TimeFormat.language,
|
||||||
language: key,
|
},
|
||||||
time_format: TimeFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatTimeWithSeconds(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.am_pm,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatTimeWithSeconds(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.twenty_four,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatTimeWithSeconds(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatTimeWithSeconds(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -56,48 +56,46 @@ export class DemoDateTimeTimeWeekday extends LitElement {
|
|||||||
<div class="center">12 Hours</div>
|
<div class="center">12 Hours</div>
|
||||||
<div class="center">24 Hours</div>
|
<div class="center">24 Hours</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatTimeWeekday(
|
||||||
<div class="center">
|
this.date,
|
||||||
${formatTimeWeekday(
|
{
|
||||||
this.date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
time_format: TimeFormat.language,
|
||||||
language: key,
|
},
|
||||||
time_format: TimeFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatTimeWeekday(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.am_pm,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatTimeWeekday(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.twenty_four,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatTimeWeekday(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatTimeWeekday(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -56,48 +56,46 @@ export class DemoDateTimeTime extends LitElement {
|
|||||||
<div class="center">12 Hours</div>
|
<div class="center">12 Hours</div>
|
||||||
<div class="center">24 Hours</div>
|
<div class="center">24 Hours</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatTime(
|
||||||
<div class="center">
|
this.date,
|
||||||
${formatTime(
|
{
|
||||||
this.date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
time_format: TimeFormat.language,
|
||||||
language: key,
|
},
|
||||||
time_format: TimeFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatTime(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.am_pm,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatTime(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.twenty_four,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatTime(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatTime(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -368,6 +368,7 @@ export class DemoEntityState extends LitElement {
|
|||||||
hass.localize,
|
hass.localize,
|
||||||
entry.stateObj,
|
entry.stateObj,
|
||||||
hass.locale,
|
hass.locale,
|
||||||
|
[], // numericDeviceClasses
|
||||||
hass.config,
|
hass.config,
|
||||||
hass.entities
|
hass.entities
|
||||||
)}`,
|
)}`,
|
||||||
|
92
package.json
92
package.json
@ -25,8 +25,8 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.24.4",
|
"@babel/runtime": "7.24.6",
|
||||||
"@braintree/sanitize-url": "7.0.1",
|
"@braintree/sanitize-url": "7.0.2",
|
||||||
"@codemirror/autocomplete": "6.16.0",
|
"@codemirror/autocomplete": "6.16.0",
|
||||||
"@codemirror/commands": "6.5.0",
|
"@codemirror/commands": "6.5.0",
|
||||||
"@codemirror/language": "6.10.1",
|
"@codemirror/language": "6.10.1",
|
||||||
@ -35,20 +35,20 @@
|
|||||||
"@codemirror/state": "6.4.1",
|
"@codemirror/state": "6.4.1",
|
||||||
"@codemirror/view": "6.26.3",
|
"@codemirror/view": "6.26.3",
|
||||||
"@egjs/hammerjs": "2.0.17",
|
"@egjs/hammerjs": "2.0.17",
|
||||||
"@formatjs/intl-datetimeformat": "6.12.3",
|
"@formatjs/intl-datetimeformat": "6.12.5",
|
||||||
"@formatjs/intl-displaynames": "6.6.6",
|
"@formatjs/intl-displaynames": "6.6.8",
|
||||||
"@formatjs/intl-getcanonicallocales": "2.3.0",
|
"@formatjs/intl-getcanonicallocales": "2.3.0",
|
||||||
"@formatjs/intl-listformat": "7.5.5",
|
"@formatjs/intl-listformat": "7.5.7",
|
||||||
"@formatjs/intl-locale": "3.4.5",
|
"@formatjs/intl-locale": "4.0.0",
|
||||||
"@formatjs/intl-numberformat": "8.10.1",
|
"@formatjs/intl-numberformat": "8.10.3",
|
||||||
"@formatjs/intl-pluralrules": "5.2.12",
|
"@formatjs/intl-pluralrules": "5.2.14",
|
||||||
"@formatjs/intl-relativetimeformat": "11.2.12",
|
"@formatjs/intl-relativetimeformat": "11.2.14",
|
||||||
"@fullcalendar/core": "6.1.11",
|
"@fullcalendar/core": "6.1.13",
|
||||||
"@fullcalendar/daygrid": "6.1.11",
|
"@fullcalendar/daygrid": "6.1.13",
|
||||||
"@fullcalendar/interaction": "6.1.11",
|
"@fullcalendar/interaction": "6.1.13",
|
||||||
"@fullcalendar/list": "6.1.11",
|
"@fullcalendar/list": "6.1.13",
|
||||||
"@fullcalendar/luxon3": "6.1.11",
|
"@fullcalendar/luxon3": "6.1.13",
|
||||||
"@fullcalendar/timegrid": "6.1.11",
|
"@fullcalendar/timegrid": "6.1.13",
|
||||||
"@lezer/highlight": "1.2.0",
|
"@lezer/highlight": "1.2.0",
|
||||||
"@lit-labs/context": "0.4.1",
|
"@lit-labs/context": "0.4.1",
|
||||||
"@lit-labs/motion": "1.0.7",
|
"@lit-labs/motion": "1.0.7",
|
||||||
@ -70,7 +70,6 @@
|
|||||||
"@material/mwc-list": "0.27.0",
|
"@material/mwc-list": "0.27.0",
|
||||||
"@material/mwc-menu": "0.27.0",
|
"@material/mwc-menu": "0.27.0",
|
||||||
"@material/mwc-radio": "0.27.0",
|
"@material/mwc-radio": "0.27.0",
|
||||||
"@material/mwc-ripple": "0.27.0",
|
|
||||||
"@material/mwc-select": "0.27.0",
|
"@material/mwc-select": "0.27.0",
|
||||||
"@material/mwc-snackbar": "0.27.0",
|
"@material/mwc-snackbar": "0.27.0",
|
||||||
"@material/mwc-switch": "0.27.0",
|
"@material/mwc-switch": "0.27.0",
|
||||||
@ -81,7 +80,7 @@
|
|||||||
"@material/mwc-top-app-bar": "0.27.0",
|
"@material/mwc-top-app-bar": "0.27.0",
|
||||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/web": "1.4.1",
|
"@material/web": "1.5.0",
|
||||||
"@mdi/js": "7.4.47",
|
"@mdi/js": "7.4.47",
|
||||||
"@mdi/svg": "7.4.47",
|
"@mdi/svg": "7.4.47",
|
||||||
"@polymer/paper-item": "3.0.1",
|
"@polymer/paper-item": "3.0.1",
|
||||||
@ -89,8 +88,8 @@
|
|||||||
"@polymer/paper-tabs": "3.1.0",
|
"@polymer/paper-tabs": "3.1.0",
|
||||||
"@polymer/polymer": "3.5.1",
|
"@polymer/polymer": "3.5.1",
|
||||||
"@thomasloven/round-slider": "0.6.0",
|
"@thomasloven/round-slider": "0.6.0",
|
||||||
"@vaadin/combo-box": "24.3.11",
|
"@vaadin/combo-box": "24.3.13",
|
||||||
"@vaadin/vaadin-themable-mixin": "24.3.11",
|
"@vaadin/vaadin-themable-mixin": "24.3.13",
|
||||||
"@vibrant/color": "3.2.1-alpha.1",
|
"@vibrant/color": "3.2.1-alpha.1",
|
||||||
"@vibrant/core": "3.2.1-alpha.1",
|
"@vibrant/core": "3.2.1-alpha.1",
|
||||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||||
@ -98,10 +97,10 @@
|
|||||||
"@webcomponents/scoped-custom-element-registry": "0.0.9",
|
"@webcomponents/scoped-custom-element-registry": "0.0.9",
|
||||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||||
"app-datepicker": "5.1.1",
|
"app-datepicker": "5.1.1",
|
||||||
"chart.js": "4.4.2",
|
"chart.js": "4.4.3",
|
||||||
"color-name": "2.0.0",
|
"color-name": "2.0.0",
|
||||||
"comlink": "4.4.1",
|
"comlink": "4.4.1",
|
||||||
"core-js": "3.37.0",
|
"core-js": "3.37.1",
|
||||||
"cropperjs": "1.6.2",
|
"cropperjs": "1.6.2",
|
||||||
"date-fns": "3.6.0",
|
"date-fns": "3.6.0",
|
||||||
"date-fns-tz": "3.1.3",
|
"date-fns-tz": "3.1.3",
|
||||||
@ -113,7 +112,7 @@
|
|||||||
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
|
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
|
||||||
"home-assistant-js-websocket": "9.3.0",
|
"home-assistant-js-websocket": "9.3.0",
|
||||||
"idb-keyval": "6.2.1",
|
"idb-keyval": "6.2.1",
|
||||||
"intl-messageformat": "10.5.11",
|
"intl-messageformat": "10.5.14",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"leaflet": "1.9.4",
|
"leaflet": "1.9.4",
|
||||||
"leaflet-draw": "1.0.4",
|
"leaflet-draw": "1.0.4",
|
||||||
@ -150,33 +149,34 @@
|
|||||||
"xss": "1.0.15"
|
"xss": "1.0.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.24.4",
|
"@babel/core": "7.24.6",
|
||||||
"@babel/helper-define-polyfill-provider": "0.6.2",
|
"@babel/helper-define-polyfill-provider": "0.6.2",
|
||||||
"@babel/plugin-proposal-decorators": "7.24.1",
|
"@babel/plugin-proposal-decorators": "7.24.6",
|
||||||
"@babel/plugin-transform-runtime": "7.24.3",
|
"@babel/plugin-transform-runtime": "7.24.6",
|
||||||
"@babel/preset-env": "7.24.4",
|
"@babel/preset-env": "7.24.6",
|
||||||
"@babel/preset-typescript": "7.24.1",
|
"@babel/preset-typescript": "7.24.6",
|
||||||
"@bundle-stats/plugin-webpack-filter": "4.12.2",
|
"@bundle-stats/plugin-webpack-filter": "4.13.2",
|
||||||
"@koa/cors": "5.0.0",
|
"@koa/cors": "5.0.0",
|
||||||
"@lokalise/node-api": "12.4.1",
|
"@lokalise/node-api": "12.5.0",
|
||||||
"@octokit/auth-oauth-device": "7.1.1",
|
"@octokit/auth-oauth-device": "7.1.1",
|
||||||
"@octokit/plugin-retry": "7.1.1",
|
"@octokit/plugin-retry": "7.1.1",
|
||||||
"@octokit/rest": "20.1.0",
|
"@octokit/rest": "20.1.1",
|
||||||
"@open-wc/dev-server-hmr": "0.1.4",
|
"@open-wc/dev-server-hmr": "0.1.4",
|
||||||
"@rollup/plugin-babel": "6.0.4",
|
"@rollup/plugin-babel": "6.0.4",
|
||||||
"@rollup/plugin-commonjs": "25.0.7",
|
"@rollup/plugin-commonjs": "25.0.8",
|
||||||
"@rollup/plugin-json": "6.1.0",
|
"@rollup/plugin-json": "6.1.0",
|
||||||
"@rollup/plugin-node-resolve": "15.2.3",
|
"@rollup/plugin-node-resolve": "15.2.3",
|
||||||
"@rollup/plugin-replace": "5.0.5",
|
"@rollup/plugin-replace": "5.0.5",
|
||||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||||
"@types/chromecast-caf-receiver": "6.0.14",
|
"@types/chromecast-caf-receiver": "6.0.14",
|
||||||
"@types/chromecast-caf-sender": "1.0.9",
|
"@types/chromecast-caf-sender": "1.0.10",
|
||||||
"@types/color-name": "1.1.4",
|
"@types/color-name": "1.1.4",
|
||||||
"@types/glob": "8.1.0",
|
"@types/glob": "8.1.0",
|
||||||
"@types/html-minifier-terser": "7.0.2",
|
"@types/html-minifier-terser": "7.0.2",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/leaflet": "1.9.12",
|
"@types/leaflet": "1.9.12",
|
||||||
"@types/leaflet-draw": "1.0.11",
|
"@types/leaflet-draw": "1.0.11",
|
||||||
|
"@types/lodash.merge": "4.6.9",
|
||||||
"@types/luxon": "3.4.2",
|
"@types/luxon": "3.4.2",
|
||||||
"@types/mocha": "10.0.6",
|
"@types/mocha": "10.0.6",
|
||||||
"@types/qrcode": "1.5.5",
|
"@types/qrcode": "1.5.5",
|
||||||
@ -185,13 +185,13 @@
|
|||||||
"@types/tar": "6.1.13",
|
"@types/tar": "6.1.13",
|
||||||
"@types/ua-parser-js": "0.7.39",
|
"@types/ua-parser-js": "0.7.39",
|
||||||
"@types/webspeechapi": "0.0.29",
|
"@types/webspeechapi": "0.0.29",
|
||||||
"@typescript-eslint/eslint-plugin": "7.7.1",
|
"@typescript-eslint/eslint-plugin": "7.10.0",
|
||||||
"@typescript-eslint/parser": "7.7.1",
|
"@typescript-eslint/parser": "7.10.0",
|
||||||
"@web/dev-server": "0.1.38",
|
"@web/dev-server": "0.1.38",
|
||||||
"@web/dev-server-rollup": "0.4.1",
|
"@web/dev-server-rollup": "0.4.1",
|
||||||
"babel-loader": "9.1.3",
|
"babel-loader": "9.1.3",
|
||||||
"babel-plugin-template-html-minifier": "4.1.0",
|
"babel-plugin-template-html-minifier": "4.1.0",
|
||||||
"chai": "5.1.0",
|
"chai": "5.1.1",
|
||||||
"del": "7.1.0",
|
"del": "7.1.0",
|
||||||
"eslint": "8.57.0",
|
"eslint": "8.57.0",
|
||||||
"eslint-config-airbnb-base": "15.0.0",
|
"eslint-config-airbnb-base": "15.0.0",
|
||||||
@ -200,24 +200,24 @@
|
|||||||
"eslint-import-resolver-webpack": "0.13.8",
|
"eslint-import-resolver-webpack": "0.13.8",
|
||||||
"eslint-plugin-disable": "2.0.3",
|
"eslint-plugin-disable": "2.0.3",
|
||||||
"eslint-plugin-import": "2.29.1",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"eslint-plugin-lit": "1.11.0",
|
"eslint-plugin-lit": "1.13.0",
|
||||||
"eslint-plugin-lit-a11y": "4.1.2",
|
"eslint-plugin-lit-a11y": "4.1.2",
|
||||||
"eslint-plugin-unused-imports": "3.1.0",
|
"eslint-plugin-unused-imports": "4.0.0",
|
||||||
"eslint-plugin-wc": "2.1.0",
|
"eslint-plugin-wc": "2.1.0",
|
||||||
"fancy-log": "2.0.0",
|
"fancy-log": "2.0.0",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"glob": "10.3.12",
|
"glob": "10.4.1",
|
||||||
"gulp": "4.0.2",
|
"gulp": "5.0.0",
|
||||||
"gulp-json-transform": "0.5.0",
|
"gulp-json-transform": "0.5.0",
|
||||||
"gulp-merge-json": "2.2.1",
|
|
||||||
"gulp-rename": "2.0.0",
|
"gulp-rename": "2.0.0",
|
||||||
"gulp-zopfli-green": "6.0.1",
|
"gulp-zopfli-green": "6.0.1",
|
||||||
"html-minifier-terser": "7.2.0",
|
"html-minifier-terser": "7.2.0",
|
||||||
"husky": "9.0.11",
|
"husky": "9.0.11",
|
||||||
"instant-mocha": "1.5.2",
|
"instant-mocha": "1.5.2",
|
||||||
"jszip": "3.10.1",
|
"jszip": "3.10.1",
|
||||||
"lint-staged": "15.2.2",
|
"lint-staged": "15.2.5",
|
||||||
"lit-analyzer": "2.0.3",
|
"lit-analyzer": "2.0.3",
|
||||||
|
"lodash.merge": "4.6.2",
|
||||||
"lodash.template": "4.5.0",
|
"lodash.template": "4.5.0",
|
||||||
"magic-string": "0.30.10",
|
"magic-string": "0.30.10",
|
||||||
"map-stream": "0.0.7",
|
"map-stream": "0.0.7",
|
||||||
@ -231,12 +231,12 @@
|
|||||||
"rollup-plugin-terser": "7.0.2",
|
"rollup-plugin-terser": "7.0.2",
|
||||||
"rollup-plugin-visualizer": "5.12.0",
|
"rollup-plugin-visualizer": "5.12.0",
|
||||||
"serve-handler": "6.1.5",
|
"serve-handler": "6.1.5",
|
||||||
"sinon": "17.0.1",
|
"sinon": "18.0.0",
|
||||||
"source-map-url": "0.4.1",
|
"source-map-url": "0.4.1",
|
||||||
"systemjs": "6.14.3",
|
"systemjs": "6.15.1",
|
||||||
"tar": "7.0.1",
|
"tar": "7.1.0",
|
||||||
"terser-webpack-plugin": "5.3.10",
|
"terser-webpack-plugin": "5.3.10",
|
||||||
"transform-async-modules-webpack-plugin": "1.1.0",
|
"transform-async-modules-webpack-plugin": "1.1.1",
|
||||||
"ts-lit-plugin": "2.0.2",
|
"ts-lit-plugin": "2.0.2",
|
||||||
"typescript": "5.4.5",
|
"typescript": "5.4.5",
|
||||||
"webpack": "5.91.0",
|
"webpack": "5.91.0",
|
||||||
@ -257,5 +257,5 @@
|
|||||||
"sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
|
"sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
|
||||||
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.1.1"
|
"packageManager": "yarn@4.2.2"
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20240501.1"
|
version = "20240529.0"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { getWeekStartByLocale } from "weekstart";
|
import { getWeekStartByLocale } from "weekstart";
|
||||||
import { FrontendLocaleData, FirstWeekday } from "../../data/translation";
|
import { FrontendLocaleData, FirstWeekday } from "../../data/translation";
|
||||||
|
|
||||||
import "../../resources/intl-polyfill";
|
|
||||||
|
|
||||||
export const weekdays = [
|
export const weekdays = [
|
||||||
"sunday",
|
"sunday",
|
||||||
"monday",
|
"monday",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { HassConfig } from "home-assistant-js-websocket";
|
import { HassConfig } from "home-assistant-js-websocket";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { DateFormat, FrontendLocaleData } from "../../data/translation";
|
import { DateFormat, FrontendLocaleData } from "../../data/translation";
|
||||||
import "../../resources/intl-polyfill";
|
|
||||||
import { resolveTimeZone } from "./resolve-time-zone";
|
import { resolveTimeZone } from "./resolve-time-zone";
|
||||||
|
|
||||||
// Tuesday, August 10
|
// Tuesday, August 10
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { HassConfig } from "home-assistant-js-websocket";
|
import { HassConfig } from "home-assistant-js-websocket";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { FrontendLocaleData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import "../../resources/intl-polyfill";
|
|
||||||
import { formatDateNumeric } from "./format_date";
|
import { formatDateNumeric } from "./format_date";
|
||||||
import { formatTime } from "./format_time";
|
import { formatTime } from "./format_time";
|
||||||
import { resolveTimeZone } from "./resolve-time-zone";
|
import { resolveTimeZone } from "./resolve-time-zone";
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { HaDurationData } from "../../components/ha-duration-input";
|
import { HaDurationData } from "../../components/ha-duration-input";
|
||||||
import { FrontendLocaleData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import "../../resources/intl-polyfill";
|
|
||||||
|
|
||||||
const leftPad = (num: number) => (num < 10 ? `0${num}` : num);
|
const leftPad = (num: number) => (num < 10 ? `0${num}` : num);
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { HassConfig } from "home-assistant-js-websocket";
|
import { HassConfig } from "home-assistant-js-websocket";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { FrontendLocaleData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import "../../resources/intl-polyfill";
|
|
||||||
import { resolveTimeZone } from "./resolve-time-zone";
|
import { resolveTimeZone } from "./resolve-time-zone";
|
||||||
import { useAmPm } from "./use_am_pm";
|
import { useAmPm } from "./use_am_pm";
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import "../../resources/intl-polyfill";
|
|
||||||
|
|
||||||
export const localizeWeekdays = memoizeOne(
|
export const localizeWeekdays = memoizeOne(
|
||||||
(language: string, short: boolean): string[] => {
|
(language: string, short: boolean): string[] => {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { FrontendLocaleData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import "../../resources/intl-polyfill";
|
|
||||||
import { selectUnit } from "../util/select-unit";
|
import { selectUnit } from "../util/select-unit";
|
||||||
|
|
||||||
const formatRelTimeMem = memoizeOne(
|
const formatRelTimeMem = memoizeOne(
|
||||||
|
@ -108,6 +108,8 @@ export const storage =
|
|||||||
subscribe?: boolean;
|
subscribe?: boolean;
|
||||||
state?: boolean;
|
state?: boolean;
|
||||||
stateOptions?: InternalPropertyDeclaration;
|
stateOptions?: InternalPropertyDeclaration;
|
||||||
|
serializer?: (value: any) => any;
|
||||||
|
deserializer?: (value: any) => any;
|
||||||
}): any =>
|
}): any =>
|
||||||
(clsElement: ClassElement) => {
|
(clsElement: ClassElement) => {
|
||||||
const storageName = options.storage || "localStorage";
|
const storageName = options.storage || "localStorage";
|
||||||
@ -141,7 +143,9 @@ export const storage =
|
|||||||
|
|
||||||
const getValue = (): any =>
|
const getValue = (): any =>
|
||||||
storageInstance.hasKey(storageKey!)
|
storageInstance.hasKey(storageKey!)
|
||||||
? storageInstance.getValue(storageKey!)
|
? options.deserializer
|
||||||
|
? options.deserializer(storageInstance.getValue(storageKey!))
|
||||||
|
: storageInstance.getValue(storageKey!)
|
||||||
: initVal;
|
: initVal;
|
||||||
|
|
||||||
const setValue = (el: ReactiveElement, value: any) => {
|
const setValue = (el: ReactiveElement, value: any) => {
|
||||||
@ -149,7 +153,10 @@ export const storage =
|
|||||||
if (options.state) {
|
if (options.state) {
|
||||||
oldValue = getValue();
|
oldValue = getValue();
|
||||||
}
|
}
|
||||||
storageInstance.setValue(storageKey!, value);
|
storageInstance.setValue(
|
||||||
|
storageKey!,
|
||||||
|
options.serializer ? options.serializer(value) : value
|
||||||
|
);
|
||||||
if (options.state) {
|
if (options.state) {
|
||||||
el.requestUpdate(clsElement.key, oldValue);
|
el.requestUpdate(clsElement.key, oldValue);
|
||||||
}
|
}
|
||||||
|
@ -61,8 +61,11 @@ export const applyThemesOnElement = (
|
|||||||
const accentColor = themeSettings?.accentColor;
|
const accentColor = themeSettings?.accentColor;
|
||||||
|
|
||||||
if (darkMode && primaryColor) {
|
if (darkMode && primaryColor) {
|
||||||
themeRules["app-theme-color"] = hexBlend(primaryColor, "#121212", 8);
|
themeRules["app-header-background-color"] = hexBlend(
|
||||||
themeRules["app-header-background-color"] = themeRules["app-theme-color"];
|
primaryColor,
|
||||||
|
"#121212",
|
||||||
|
8
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (primaryColor) {
|
if (primaryColor) {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
export type MediaQueriesListener = () => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach a media query. Listener is called right away and when it matches.
|
* Attach a media query. Listener is called right away and when it matches.
|
||||||
* @param mediaQuery media query to match.
|
* @param mediaQuery media query to match.
|
||||||
@ -7,7 +9,7 @@
|
|||||||
export const listenMediaQuery = (
|
export const listenMediaQuery = (
|
||||||
mediaQuery: string,
|
mediaQuery: string,
|
||||||
matchesChanged: (matches: boolean) => void
|
matchesChanged: (matches: boolean) => void
|
||||||
) => {
|
): MediaQueriesListener => {
|
||||||
const mql = matchMedia(mediaQuery);
|
const mql = matchMedia(mediaQuery);
|
||||||
const listener = (e) => matchesChanged(e.matches);
|
const listener = (e) => matchesChanged(e.matches);
|
||||||
mql.addListener(listener);
|
mql.addListener(listener);
|
||||||
|
@ -19,28 +19,11 @@ import { blankBeforeUnit } from "../translations/blank_before_unit";
|
|||||||
import { LocalizeFunc } from "../translations/localize";
|
import { LocalizeFunc } from "../translations/localize";
|
||||||
import { computeDomain } from "./compute_domain";
|
import { computeDomain } from "./compute_domain";
|
||||||
|
|
||||||
export const computeStateDisplaySingleEntity = (
|
|
||||||
localize: LocalizeFunc,
|
|
||||||
stateObj: HassEntity,
|
|
||||||
locale: FrontendLocaleData,
|
|
||||||
config: HassConfig,
|
|
||||||
entity: EntityRegistryDisplayEntry | undefined,
|
|
||||||
state?: string
|
|
||||||
): string =>
|
|
||||||
computeStateDisplayFromEntityAttributes(
|
|
||||||
localize,
|
|
||||||
locale,
|
|
||||||
config,
|
|
||||||
entity,
|
|
||||||
stateObj.entity_id,
|
|
||||||
stateObj.attributes,
|
|
||||||
state !== undefined ? state : stateObj.state
|
|
||||||
);
|
|
||||||
|
|
||||||
export const computeStateDisplay = (
|
export const computeStateDisplay = (
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
stateObj: HassEntity,
|
stateObj: HassEntity,
|
||||||
locale: FrontendLocaleData,
|
locale: FrontendLocaleData,
|
||||||
|
sensorNumericDeviceClasses: string[],
|
||||||
config: HassConfig,
|
config: HassConfig,
|
||||||
entities: HomeAssistant["entities"],
|
entities: HomeAssistant["entities"],
|
||||||
state?: string
|
state?: string
|
||||||
@ -52,6 +35,7 @@ export const computeStateDisplay = (
|
|||||||
return computeStateDisplayFromEntityAttributes(
|
return computeStateDisplayFromEntityAttributes(
|
||||||
localize,
|
localize,
|
||||||
locale,
|
locale,
|
||||||
|
sensorNumericDeviceClasses,
|
||||||
config,
|
config,
|
||||||
entity,
|
entity,
|
||||||
stateObj.entity_id,
|
stateObj.entity_id,
|
||||||
@ -63,6 +47,7 @@ export const computeStateDisplay = (
|
|||||||
export const computeStateDisplayFromEntityAttributes = (
|
export const computeStateDisplayFromEntityAttributes = (
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
locale: FrontendLocaleData,
|
locale: FrontendLocaleData,
|
||||||
|
sensorNumericDeviceClasses: string[],
|
||||||
config: HassConfig,
|
config: HassConfig,
|
||||||
entity: EntityRegistryDisplayEntry | undefined,
|
entity: EntityRegistryDisplayEntry | undefined,
|
||||||
entityId: string,
|
entityId: string,
|
||||||
@ -73,8 +58,15 @@ export const computeStateDisplayFromEntityAttributes = (
|
|||||||
return localize(`state.default.${state}`);
|
return localize(`state.default.${state}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const domain = computeDomain(entityId);
|
||||||
|
|
||||||
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
|
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
|
||||||
if (isNumericFromAttributes(attributes)) {
|
if (
|
||||||
|
isNumericFromAttributes(
|
||||||
|
attributes,
|
||||||
|
domain === "sensor" ? sensorNumericDeviceClasses : []
|
||||||
|
)
|
||||||
|
) {
|
||||||
// state is duration
|
// state is duration
|
||||||
if (
|
if (
|
||||||
attributes.device_class === "duration" &&
|
attributes.device_class === "duration" &&
|
||||||
@ -120,8 +112,6 @@ export const computeStateDisplayFromEntityAttributes = (
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const domain = computeDomain(entityId);
|
|
||||||
|
|
||||||
if (domain === "datetime") {
|
if (domain === "datetime") {
|
||||||
const time = new Date(state);
|
const time = new Date(state);
|
||||||
return formatDateTime(time, locale, config);
|
return formatDateTime(time, locale, config);
|
||||||
|
@ -28,7 +28,15 @@ export const FIXED_DOMAIN_STATES = {
|
|||||||
input_button: [],
|
input_button: [],
|
||||||
lawn_mower: ["error", "paused", "mowing", "docked"],
|
lawn_mower: ["error", "paused", "mowing", "docked"],
|
||||||
light: ["on", "off"],
|
light: ["on", "off"],
|
||||||
lock: ["jammed", "locked", "locking", "unlocked", "unlocking"],
|
lock: [
|
||||||
|
"jammed",
|
||||||
|
"locked",
|
||||||
|
"locking",
|
||||||
|
"unlocked",
|
||||||
|
"unlocking",
|
||||||
|
"opening",
|
||||||
|
"open",
|
||||||
|
],
|
||||||
media_player: [
|
media_player: [
|
||||||
"off",
|
"off",
|
||||||
"on",
|
"on",
|
||||||
|
@ -12,11 +12,10 @@ export const formatLanguageCode = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatLanguageCodeMem = memoizeOne((locale: FrontendLocaleData) =>
|
const formatLanguageCodeMem = memoizeOne(
|
||||||
Intl && "DisplayNames" in Intl
|
(locale: FrontendLocaleData) =>
|
||||||
? new Intl.DisplayNames(locale.language, {
|
new Intl.DisplayNames(locale.language, {
|
||||||
type: "language",
|
type: "language",
|
||||||
fallback: "code",
|
fallback: "code",
|
||||||
})
|
})
|
||||||
: undefined
|
|
||||||
);
|
);
|
||||||
|
@ -11,6 +11,7 @@ declare global {
|
|||||||
|
|
||||||
export interface NavigateOptions {
|
export interface NavigateOptions {
|
||||||
replace?: boolean;
|
replace?: boolean;
|
||||||
|
data?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const navigate = (path: string, options?: NavigateOptions) => {
|
export const navigate = (path: string, options?: NavigateOptions) => {
|
||||||
@ -24,7 +25,7 @@ export const navigate = (path: string, options?: NavigateOptions) => {
|
|||||||
if (__DEMO__) {
|
if (__DEMO__) {
|
||||||
if (replace) {
|
if (replace) {
|
||||||
mainWindow.history.replaceState(
|
mainWindow.history.replaceState(
|
||||||
mainWindow.history.state?.root ? { root: true } : null,
|
mainWindow.history.state?.root ? { root: true } : options?.data ?? null,
|
||||||
"",
|
"",
|
||||||
`${mainWindow.location.pathname}#${path}`
|
`${mainWindow.location.pathname}#${path}`
|
||||||
);
|
);
|
||||||
@ -33,12 +34,12 @@ export const navigate = (path: string, options?: NavigateOptions) => {
|
|||||||
}
|
}
|
||||||
} else if (replace) {
|
} else if (replace) {
|
||||||
mainWindow.history.replaceState(
|
mainWindow.history.replaceState(
|
||||||
mainWindow.history.state?.root ? { root: true } : null,
|
mainWindow.history.state?.root ? { root: true } : options?.data ?? null,
|
||||||
"",
|
"",
|
||||||
path
|
path
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
mainWindow.history.pushState(null, "", path);
|
mainWindow.history.pushState(options?.data ?? null, "", path);
|
||||||
}
|
}
|
||||||
fireEvent(mainWindow, "location-changed", {
|
fireEvent(mainWindow, "location-changed", {
|
||||||
replace,
|
replace,
|
||||||
|
@ -14,8 +14,12 @@ export const isNumericState = (stateObj: HassEntity): boolean =>
|
|||||||
isNumericFromAttributes(stateObj.attributes);
|
isNumericFromAttributes(stateObj.attributes);
|
||||||
|
|
||||||
export const isNumericFromAttributes = (
|
export const isNumericFromAttributes = (
|
||||||
attributes: HassEntityAttributeBase
|
attributes: HassEntityAttributeBase,
|
||||||
): boolean => !!attributes.unit_of_measurement || !!attributes.state_class;
|
numericDeviceClasses?: string[]
|
||||||
|
): boolean =>
|
||||||
|
!!attributes.unit_of_measurement ||
|
||||||
|
!!attributes.state_class ||
|
||||||
|
(numericDeviceClasses || []).includes(attributes.device_class || "");
|
||||||
|
|
||||||
export const numberFormatToLocale = (
|
export const numberFormatToLocale = (
|
||||||
localeOptions: FrontendLocaleData
|
localeOptions: FrontendLocaleData
|
||||||
@ -59,30 +63,18 @@ export const formatNumber = (
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
localeOptions?.number_format !== NumberFormat.none &&
|
localeOptions?.number_format !== NumberFormat.none &&
|
||||||
!Number.isNaN(Number(num)) &&
|
!Number.isNaN(Number(num))
|
||||||
Intl
|
|
||||||
) {
|
) {
|
||||||
try {
|
return new Intl.NumberFormat(
|
||||||
return new Intl.NumberFormat(
|
locale,
|
||||||
locale,
|
getDefaultFormatOptions(num, options)
|
||||||
getDefaultFormatOptions(num, options)
|
).format(Number(num));
|
||||||
).format(Number(num));
|
|
||||||
} catch (err: any) {
|
|
||||||
// Don't fail when using "TEST" language
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(err);
|
|
||||||
return new Intl.NumberFormat(
|
|
||||||
undefined,
|
|
||||||
getDefaultFormatOptions(num, options)
|
|
||||||
).format(Number(num));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!Number.isNaN(Number(num)) &&
|
!Number.isNaN(Number(num)) &&
|
||||||
num !== "" &&
|
num !== "" &&
|
||||||
localeOptions?.number_format === NumberFormat.none &&
|
localeOptions?.number_format === NumberFormat.none
|
||||||
Intl
|
|
||||||
) {
|
) {
|
||||||
// If NumberFormat is none, use en-US format without grouping.
|
// If NumberFormat is none, use en-US format without grouping.
|
||||||
return new Intl.NumberFormat(
|
return new Intl.NumberFormat(
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import "../../resources/intl-polyfill";
|
|
||||||
import { FrontendLocaleData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
|
|
||||||
export const formatListWithAnds = (
|
export const formatListWithAnds = (
|
||||||
|
@ -21,7 +21,8 @@ export const computeFormatFunctions = async (
|
|||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
locale: FrontendLocaleData,
|
locale: FrontendLocaleData,
|
||||||
config: HassConfig,
|
config: HassConfig,
|
||||||
entities: HomeAssistant["entities"]
|
entities: HomeAssistant["entities"],
|
||||||
|
sensorNumericDeviceClasses: string[]
|
||||||
): Promise<{
|
): Promise<{
|
||||||
formatEntityState: FormatEntityStateFunc;
|
formatEntityState: FormatEntityStateFunc;
|
||||||
formatEntityAttributeValue: FormatEntityAttributeValueFunc;
|
formatEntityAttributeValue: FormatEntityAttributeValueFunc;
|
||||||
@ -35,7 +36,15 @@ export const computeFormatFunctions = async (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
formatEntityState: (stateObj, state) =>
|
formatEntityState: (stateObj, state) =>
|
||||||
computeStateDisplay(localize, stateObj, locale, config, entities, state),
|
computeStateDisplay(
|
||||||
|
localize,
|
||||||
|
stateObj,
|
||||||
|
locale,
|
||||||
|
sensorNumericDeviceClasses,
|
||||||
|
config,
|
||||||
|
entities,
|
||||||
|
state
|
||||||
|
),
|
||||||
formatEntityAttributeValue: (stateObj, attribute, value) =>
|
formatEntityAttributeValue: (stateObj, attribute, value) =>
|
||||||
computeAttributeValueDisplay(
|
computeAttributeValueDisplay(
|
||||||
localize,
|
localize,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import IntlMessageFormat from "intl-messageformat";
|
import type { IntlMessageFormat } from "intl-messageformat";
|
||||||
import type { HTMLTemplateResult } from "lit";
|
import type { HTMLTemplateResult } from "lit";
|
||||||
import { polyfillLocaleData } from "../../resources/locale-data-polyfill";
|
import { polyfillLocaleData } from "../../resources/polyfills/locale-data-polyfill";
|
||||||
import { Resources, TranslationDict } from "../../types";
|
import { Resources, TranslationDict } from "../../types";
|
||||||
import { fireEvent } from "../dom/fire_event";
|
import { fireEvent } from "../dom/fire_event";
|
||||||
|
|
||||||
@ -89,9 +89,8 @@ export const computeLocalize = async <Keys extends string = LocalizeKeys>(
|
|||||||
resources: Resources,
|
resources: Resources,
|
||||||
formats?: FormatsType
|
formats?: FormatsType
|
||||||
): Promise<LocalizeFunc<Keys>> => {
|
): Promise<LocalizeFunc<Keys>> => {
|
||||||
await import("../../resources/intl-polyfill").then(() =>
|
const { IntlMessageFormat } = await import("intl-messageformat");
|
||||||
polyfillLocaleData(language)
|
await polyfillLocaleData(language);
|
||||||
);
|
|
||||||
|
|
||||||
// Every time any of the parameters change, invalidate the strings cache.
|
// Every time any of the parameters change, invalidate the strings cache.
|
||||||
cache._localizationCache = {};
|
cache._localizationCache = {};
|
||||||
|
@ -313,31 +313,38 @@ export class HaChartBase extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _loading = false;
|
||||||
|
|
||||||
private async _setupChart() {
|
private async _setupChart() {
|
||||||
|
if (this._loading) return;
|
||||||
const ctx: CanvasRenderingContext2D = this.renderRoot
|
const ctx: CanvasRenderingContext2D = this.renderRoot
|
||||||
.querySelector("canvas")!
|
.querySelector("canvas")!
|
||||||
.getContext("2d")!;
|
.getContext("2d")!;
|
||||||
|
this._loading = true;
|
||||||
|
try {
|
||||||
|
const ChartConstructor = (await import("../../resources/chartjs")).Chart;
|
||||||
|
|
||||||
const ChartConstructor = (await import("../../resources/chartjs")).Chart;
|
const computedStyles = getComputedStyle(this);
|
||||||
|
|
||||||
const computedStyles = getComputedStyle(this);
|
ChartConstructor.defaults.borderColor =
|
||||||
|
computedStyles.getPropertyValue("--divider-color");
|
||||||
|
ChartConstructor.defaults.color = computedStyles.getPropertyValue(
|
||||||
|
"--secondary-text-color"
|
||||||
|
);
|
||||||
|
ChartConstructor.defaults.font.family =
|
||||||
|
computedStyles.getPropertyValue("--mdc-typography-body1-font-family") ||
|
||||||
|
computedStyles.getPropertyValue("--mdc-typography-font-family") ||
|
||||||
|
"Roboto, Noto, sans-serif";
|
||||||
|
|
||||||
ChartConstructor.defaults.borderColor =
|
this.chart = new ChartConstructor(ctx, {
|
||||||
computedStyles.getPropertyValue("--divider-color");
|
type: this.chartType,
|
||||||
ChartConstructor.defaults.color = computedStyles.getPropertyValue(
|
data: this.data,
|
||||||
"--secondary-text-color"
|
options: this._createOptions(),
|
||||||
);
|
plugins: this._createPlugins(),
|
||||||
ChartConstructor.defaults.font.family =
|
});
|
||||||
computedStyles.getPropertyValue("--mdc-typography-body1-font-family") ||
|
} finally {
|
||||||
computedStyles.getPropertyValue("--mdc-typography-font-family") ||
|
this._loading = false;
|
||||||
"Roboto, Noto, sans-serif";
|
}
|
||||||
|
|
||||||
this.chart = new ChartConstructor(ctx, {
|
|
||||||
type: this.chartType,
|
|
||||||
data: this.data,
|
|
||||||
options: this._createOptions(),
|
|
||||||
plugins: this._createPlugins(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createOptions() {
|
private _createOptions() {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "element-internals-polyfill";
|
|
||||||
import { MdAssistChip } from "@material/web/chips/assist-chip";
|
import { MdAssistChip } from "@material/web/chips/assist-chip";
|
||||||
import { css, html } from "lit";
|
import { css, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "element-internals-polyfill";
|
|
||||||
import { MdChipSet } from "@material/web/chips/chip-set";
|
import { MdChipSet } from "@material/web/chips/chip-set";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "element-internals-polyfill";
|
|
||||||
import { MdFilterChip } from "@material/web/chips/filter-chip";
|
import { MdFilterChip } from "@material/web/chips/filter-chip";
|
||||||
import { css, html } from "lit";
|
import { css, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "element-internals-polyfill";
|
|
||||||
import { MdInputChip } from "@material/web/chips/input-chip";
|
import { MdInputChip } from "@material/web/chips/input-chip";
|
||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
|
@ -730,6 +730,28 @@ export class HaDataTable extends LitElement {
|
|||||||
fireEvent(this, "collapsed-changed", { value: this._collapsedGroups });
|
fireEvent(this, "collapsed-changed", { value: this._collapsedGroups });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public expandAllGroups() {
|
||||||
|
this._collapsedGroups = [];
|
||||||
|
fireEvent(this, "collapsed-changed", { value: this._collapsedGroups });
|
||||||
|
}
|
||||||
|
|
||||||
|
public collapseAllGroups() {
|
||||||
|
if (
|
||||||
|
!this.groupColumn ||
|
||||||
|
!this.data.some((item) => item[this.groupColumn!])
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const grouped = groupBy(this.data, (item) => item[this.groupColumn!]);
|
||||||
|
if (grouped.undefined) {
|
||||||
|
// undefined is a reserved group name
|
||||||
|
grouped[UNDEFINED_GROUP_KEY] = grouped.undefined;
|
||||||
|
delete grouped.undefined;
|
||||||
|
}
|
||||||
|
this._collapsedGroups = Object.keys(grouped);
|
||||||
|
fireEvent(this, "collapsed-changed", { value: this._collapsedGroups });
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyleScrollbar,
|
haStyleScrollbar,
|
||||||
@ -984,6 +1006,7 @@ export class HaDataTable extends LitElement {
|
|||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
padding-inline-start: 12px;
|
padding-inline-start: 12px;
|
||||||
|
padding-inline-end: initial;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -76,6 +76,8 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public entityFilter?: HaEntityPickerEntityFilterFunc;
|
public entityFilter?: HaEntityPickerEntityFilterFunc;
|
||||||
|
|
||||||
|
@property({ type: Array }) public createDomains?: string[];
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return nothing;
|
return nothing;
|
||||||
@ -103,6 +105,7 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
.value=${entityId}
|
.value=${entityId}
|
||||||
.label=${this.pickedEntityLabel}
|
.label=${this.pickedEntityLabel}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
|
.createDomains=${this.createDomains}
|
||||||
@value-changed=${this._entityChanged}
|
@value-changed=${this._entityChanged}
|
||||||
></ha-entity-picker>
|
></ha-entity-picker>
|
||||||
</div>
|
</div>
|
||||||
@ -122,6 +125,7 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
.label=${this.pickEntityLabel}
|
.label=${this.pickEntityLabel}
|
||||||
.helper=${this.helper}
|
.helper=${this.helper}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
|
.createDomains=${this.createDomains}
|
||||||
.required=${this.required && !currentEntities.length}
|
.required=${this.required && !currentEntities.length}
|
||||||
@value-changed=${this._addEntity}
|
@value-changed=${this._addEntity}
|
||||||
></ha-entity-picker>
|
></ha-entity-picker>
|
||||||
|
@ -405,9 +405,9 @@ export class HaEntityPicker extends LitElement {
|
|||||||
this._opened = ev.detail.value;
|
this._opened = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: ValueChangedEvent<string>) {
|
private _valueChanged(ev: ValueChangedEvent<string | undefined>) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const newValue = ev.detail.value;
|
const newValue = ev.detail.value?.trim();
|
||||||
|
|
||||||
if (newValue && newValue.startsWith(CREATE_ID)) {
|
if (newValue && newValue.startsWith(CREATE_ID)) {
|
||||||
const domain = newValue.substring(CREATE_ID.length);
|
const domain = newValue.substring(CREATE_ID.length);
|
||||||
@ -427,13 +427,13 @@ export class HaEntityPicker extends LitElement {
|
|||||||
|
|
||||||
private _filterChanged(ev: CustomEvent): void {
|
private _filterChanged(ev: CustomEvent): void {
|
||||||
const target = ev.target as HaComboBox;
|
const target = ev.target as HaComboBox;
|
||||||
const filterString = ev.detail.value.toLowerCase();
|
const filterString = ev.detail.value.trim().toLowerCase();
|
||||||
target.filteredItems = filterString.length
|
target.filteredItems = filterString.length
|
||||||
? fuzzyFilterSort<HassEntityWithCachedName>(filterString, this._states)
|
? fuzzyFilterSort<HassEntityWithCachedName>(filterString, this._states)
|
||||||
: this._states;
|
: this._states;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setValue(value: string) {
|
private _setValue(value: string | undefined) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fireEvent(this, "value-changed", { value });
|
fireEvent(this, "value-changed", { value });
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "element-internals-polyfill";
|
|
||||||
import { MdCircularProgress } from "@material/web/progress/circular-progress";
|
import { MdCircularProgress } from "@material/web/progress/circular-progress";
|
||||||
import { PropertyValues, css } from "lit";
|
import { PropertyValues, css } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
@ -1,14 +1,7 @@
|
|||||||
import { Ripple } from "@material/mwc-ripple";
|
|
||||||
import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import {
|
import { customElement, property } from "lit/decorators";
|
||||||
customElement,
|
|
||||||
eventOptions,
|
|
||||||
property,
|
|
||||||
queryAsync,
|
|
||||||
state,
|
|
||||||
} from "lit/decorators";
|
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
|
import "./ha-ripple";
|
||||||
|
|
||||||
@customElement("ha-control-button")
|
@customElement("ha-control-button")
|
||||||
export class HaControlButton extends LitElement {
|
export class HaControlButton extends LitElement {
|
||||||
@ -16,10 +9,6 @@ export class HaControlButton extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@queryAsync("mwc-ripple") private _ripple!: Promise<Ripple | null>;
|
|
||||||
|
|
||||||
@state() private _shouldRenderRipple = false;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<button
|
<button
|
||||||
@ -28,54 +17,13 @@ export class HaControlButton extends LitElement {
|
|||||||
aria-label=${ifDefined(this.label)}
|
aria-label=${ifDefined(this.label)}
|
||||||
title=${ifDefined(this.label)}
|
title=${ifDefined(this.label)}
|
||||||
.disabled=${Boolean(this.disabled)}
|
.disabled=${Boolean(this.disabled)}
|
||||||
@focus=${this.handleRippleFocus}
|
|
||||||
@blur=${this.handleRippleBlur}
|
|
||||||
@mousedown=${this.handleRippleActivate}
|
|
||||||
@mouseup=${this.handleRippleDeactivate}
|
|
||||||
@mouseenter=${this.handleRippleMouseEnter}
|
|
||||||
@mouseleave=${this.handleRippleMouseLeave}
|
|
||||||
@touchstart=${this.handleRippleActivate}
|
|
||||||
@touchend=${this.handleRippleDeactivate}
|
|
||||||
@touchcancel=${this.handleRippleDeactivate}
|
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
${this._shouldRenderRipple && !this.disabled
|
<ha-ripple .disabled=${this.disabled}></ha-ripple>
|
||||||
? html`<mwc-ripple></mwc-ripple>`
|
|
||||||
: ""}
|
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _rippleHandlers: RippleHandlers = new RippleHandlers(() => {
|
|
||||||
this._shouldRenderRipple = true;
|
|
||||||
return this._ripple;
|
|
||||||
});
|
|
||||||
|
|
||||||
@eventOptions({ passive: true })
|
|
||||||
private handleRippleActivate(evt?: Event) {
|
|
||||||
this._rippleHandlers.startPress(evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRippleDeactivate() {
|
|
||||||
this._rippleHandlers.endPress();
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRippleMouseEnter() {
|
|
||||||
this._rippleHandlers.startHover();
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRippleMouseLeave() {
|
|
||||||
this._rippleHandlers.endHover();
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRippleFocus() {
|
|
||||||
this._rippleHandlers.startFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRippleBlur() {
|
|
||||||
this._rippleHandlers.endFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
:host {
|
:host {
|
||||||
@ -86,6 +34,7 @@ export class HaControlButton extends LitElement {
|
|||||||
--control-button-border-radius: 10px;
|
--control-button-border-radius: 10px;
|
||||||
--control-button-padding: 8px;
|
--control-button-padding: 8px;
|
||||||
--mdc-icon-size: 20px;
|
--mdc-icon-size: 20px;
|
||||||
|
--ha-ripple-color: var(--secondary-text-color);
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
@ -113,12 +62,14 @@ export class HaControlButton extends LitElement {
|
|||||||
outline: none;
|
outline: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: none;
|
background: none;
|
||||||
--mdc-ripple-color: var(--control-button-background-color);
|
|
||||||
/* For safari border-radius overflow */
|
/* For safari border-radius overflow */
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
.button:focus-visible {
|
||||||
|
--control-button-background-opacity: 0.4;
|
||||||
|
}
|
||||||
.button::before {
|
.button::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -1,22 +1,14 @@
|
|||||||
import { Ripple } from "@material/mwc-ripple";
|
|
||||||
import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers";
|
|
||||||
import { SelectBase } from "@material/mwc-select/mwc-select-base";
|
import { SelectBase } from "@material/mwc-select/mwc-select-base";
|
||||||
import { mdiMenuDown } from "@mdi/js";
|
import { mdiMenuDown } from "@mdi/js";
|
||||||
import { css, html, nothing } from "lit";
|
import { css, html, nothing } from "lit";
|
||||||
import {
|
import { customElement, property, query } from "lit/decorators";
|
||||||
customElement,
|
|
||||||
eventOptions,
|
|
||||||
property,
|
|
||||||
query,
|
|
||||||
queryAsync,
|
|
||||||
state,
|
|
||||||
} from "lit/decorators";
|
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import { debounce } from "../common/util/debounce";
|
import { debounce } from "../common/util/debounce";
|
||||||
import { nextRender } from "../common/util/render-status";
|
import { nextRender } from "../common/util/render-status";
|
||||||
import "./ha-icon";
|
import "./ha-icon";
|
||||||
import type { HaIcon } from "./ha-icon";
|
import type { HaIcon } from "./ha-icon";
|
||||||
|
import "./ha-ripple";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
import type { HaSvgIcon } from "./ha-svg-icon";
|
import type { HaSvgIcon } from "./ha-svg-icon";
|
||||||
|
|
||||||
@ -32,10 +24,6 @@ export class HaControlSelectMenu extends SelectBase {
|
|||||||
@property({ type: Boolean, attribute: "hide-label" })
|
@property({ type: Boolean, attribute: "hide-label" })
|
||||||
public hideLabel = false;
|
public hideLabel = false;
|
||||||
|
|
||||||
@queryAsync("mwc-ripple") private _ripple!: Promise<Ripple | null>;
|
|
||||||
|
|
||||||
@state() private _shouldRenderRipple = false;
|
|
||||||
|
|
||||||
public override render() {
|
public override render() {
|
||||||
const classes = {
|
const classes = {
|
||||||
"select-disabled": this.disabled,
|
"select-disabled": this.disabled,
|
||||||
@ -69,17 +57,10 @@ export class HaControlSelectMenu extends SelectBase {
|
|||||||
aria-labelledby=${ifDefined(labelledby)}
|
aria-labelledby=${ifDefined(labelledby)}
|
||||||
aria-label=${ifDefined(labelAttribute)}
|
aria-label=${ifDefined(labelAttribute)}
|
||||||
aria-required=${this.required}
|
aria-required=${this.required}
|
||||||
@click=${this.onClick}
|
|
||||||
@focus=${this.onFocus}
|
@focus=${this.onFocus}
|
||||||
@blur=${this.onBlur}
|
@blur=${this.onBlur}
|
||||||
|
@click=${this.onClick}
|
||||||
@keydown=${this.onKeydown}
|
@keydown=${this.onKeydown}
|
||||||
@mousedown=${this.handleRippleActivate}
|
|
||||||
@mouseup=${this.handleRippleDeactivate}
|
|
||||||
@mouseenter=${this.handleRippleMouseEnter}
|
|
||||||
@mouseleave=${this.handleRippleMouseLeave}
|
|
||||||
@touchstart=${this.handleRippleActivate}
|
|
||||||
@touchend=${this.handleRippleDeactivate}
|
|
||||||
@touchcancel=${this.handleRippleDeactivate}
|
|
||||||
>
|
>
|
||||||
${this.renderIcon()}
|
${this.renderIcon()}
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@ -91,9 +72,7 @@ export class HaControlSelectMenu extends SelectBase {
|
|||||||
: nothing}
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
${this.renderArrow()}
|
${this.renderArrow()}
|
||||||
${this._shouldRenderRipple && !this.disabled
|
<ha-ripple .disabled=${this.disabled}></ha-ripple>
|
||||||
? html` <mwc-ripple></mwc-ripple> `
|
|
||||||
: nothing}
|
|
||||||
</div>
|
</div>
|
||||||
${this.renderMenu()}
|
${this.renderMenu()}
|
||||||
</div>
|
</div>
|
||||||
@ -135,46 +114,6 @@ export class HaControlSelectMenu extends SelectBase {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onFocus() {
|
|
||||||
this.handleRippleFocus();
|
|
||||||
super.onFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onBlur() {
|
|
||||||
this.handleRippleBlur();
|
|
||||||
super.onBlur();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _rippleHandlers: RippleHandlers = new RippleHandlers(() => {
|
|
||||||
this._shouldRenderRipple = true;
|
|
||||||
return this._ripple;
|
|
||||||
});
|
|
||||||
|
|
||||||
@eventOptions({ passive: true })
|
|
||||||
private handleRippleActivate(evt?: Event) {
|
|
||||||
this._rippleHandlers.startPress(evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRippleDeactivate() {
|
|
||||||
this._rippleHandlers.endPress();
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRippleMouseEnter() {
|
|
||||||
this._rippleHandlers.startHover();
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRippleMouseLeave() {
|
|
||||||
this._rippleHandlers.endHover();
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRippleFocus() {
|
|
||||||
this._rippleHandlers.startFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRippleBlur() {
|
|
||||||
this._rippleHandlers.endFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
window.addEventListener("translations-updated", this._translationsUpdated);
|
window.addEventListener("translations-updated", this._translationsUpdated);
|
||||||
@ -204,6 +143,7 @@ export class HaControlSelectMenu extends SelectBase {
|
|||||||
--control-select-menu-height: 48px;
|
--control-select-menu-height: 48px;
|
||||||
--control-select-menu-padding: 6px 10px;
|
--control-select-menu-padding: 6px 10px;
|
||||||
--mdc-icon-size: 20px;
|
--mdc-icon-size: 20px;
|
||||||
|
--ha-ripple-color: var(--secondary-text-color);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
width: auto;
|
width: auto;
|
||||||
@ -224,7 +164,6 @@ export class HaControlSelectMenu extends SelectBase {
|
|||||||
outline: none;
|
outline: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: none;
|
background: none;
|
||||||
--mdc-ripple-color: var(--control-select-menu-background-color);
|
|
||||||
/* For safari border-radius overflow */
|
/* For safari border-radius overflow */
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
transition: color 180ms ease-in-out;
|
transition: color 180ms ease-in-out;
|
||||||
@ -264,6 +203,10 @@ export class HaControlSelectMenu extends SelectBase {
|
|||||||
letter-spacing: inherit;
|
letter-spacing: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select-anchor:focus-visible {
|
||||||
|
--control-select-menu-background-opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
.select-anchor::before {
|
.select-anchor::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -4,7 +4,6 @@ import memoizeOne from "memoize-one";
|
|||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||||
import "../resources/intl-polyfill";
|
|
||||||
import "./ha-list-item";
|
import "./ha-list-item";
|
||||||
import "./ha-select";
|
import "./ha-select";
|
||||||
import type { HaSelect } from "./ha-select";
|
import type { HaSelect } from "./ha-select";
|
||||||
@ -282,14 +281,10 @@ export class HaCountryPicker extends LitElement {
|
|||||||
private _getOptions = memoizeOne(
|
private _getOptions = memoizeOne(
|
||||||
(language?: string, countries?: string[]) => {
|
(language?: string, countries?: string[]) => {
|
||||||
let options: { label: string; value: string }[] = [];
|
let options: { label: string; value: string }[] = [];
|
||||||
const countryDisplayNames =
|
const countryDisplayNames = new Intl.DisplayNames(language, {
|
||||||
Intl && "DisplayNames" in Intl
|
type: "region",
|
||||||
? new Intl.DisplayNames(language, {
|
fallback: "code",
|
||||||
type: "region",
|
});
|
||||||
fallback: "code",
|
|
||||||
})
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (countries) {
|
if (countries) {
|
||||||
options = countries.map((country) => ({
|
options = countries.map((country) => ({
|
||||||
value: country,
|
value: country,
|
||||||
|
@ -4,7 +4,6 @@ import memoizeOne from "memoize-one";
|
|||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||||
import "../resources/intl-polyfill";
|
|
||||||
import "./ha-list-item";
|
import "./ha-list-item";
|
||||||
import "./ha-select";
|
import "./ha-select";
|
||||||
import type { HaSelect } from "./ha-select";
|
import type { HaSelect } from "./ha-select";
|
||||||
@ -170,12 +169,9 @@ const CURRENCIES = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const curSymbol = (currency: string, locale?: string) =>
|
const curSymbol = (currency: string, locale?: string) =>
|
||||||
Intl && "NumberFormat" in Intl
|
new Intl.NumberFormat(locale, { style: "currency", currency })
|
||||||
? new Intl.NumberFormat(locale, { style: "currency", currency })
|
.formatToParts(1)
|
||||||
.formatToParts(1)
|
.find((x) => x.type === "currency")?.value;
|
||||||
.find((x) => x.type === "currency")?.value
|
|
||||||
: currency;
|
|
||||||
|
|
||||||
@customElement("ha-currency-picker")
|
@customElement("ha-currency-picker")
|
||||||
export class HaCurrencyPicker extends LitElement {
|
export class HaCurrencyPicker extends LitElement {
|
||||||
@property() public language = "en";
|
@property() public language = "en";
|
||||||
@ -189,13 +185,10 @@ export class HaCurrencyPicker extends LitElement {
|
|||||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
private _getOptions = memoizeOne((language?: string) => {
|
private _getOptions = memoizeOne((language?: string) => {
|
||||||
const currencyDisplayNames =
|
const currencyDisplayNames = new Intl.DisplayNames(language, {
|
||||||
Intl && "DisplayNames" in Intl
|
type: "currency",
|
||||||
? new Intl.DisplayNames(language, {
|
fallback: "code",
|
||||||
type: "currency",
|
});
|
||||||
fallback: "code",
|
|
||||||
})
|
|
||||||
: undefined;
|
|
||||||
const options = CURRENCIES.map((currency) => ({
|
const options = CURRENCIES.map((currency) => ({
|
||||||
value: currency,
|
value: currency,
|
||||||
label: `${
|
label: `${
|
||||||
|
@ -21,6 +21,8 @@ export class HaExpansionPanel extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) leftChevron = false;
|
@property({ type: Boolean, reflect: true }) leftChevron = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) noCollapse = false;
|
||||||
|
|
||||||
@property() header?: string;
|
@property() header?: string;
|
||||||
|
|
||||||
@property() secondary?: string;
|
@property() secondary?: string;
|
||||||
@ -34,16 +36,17 @@ export class HaExpansionPanel extends LitElement {
|
|||||||
<div class="top ${classMap({ expanded: this.expanded })}">
|
<div class="top ${classMap({ expanded: this.expanded })}">
|
||||||
<div
|
<div
|
||||||
id="summary"
|
id="summary"
|
||||||
|
class=${classMap({ noCollapse: this.noCollapse })}
|
||||||
@click=${this._toggleContainer}
|
@click=${this._toggleContainer}
|
||||||
@keydown=${this._toggleContainer}
|
@keydown=${this._toggleContainer}
|
||||||
@focus=${this._focusChanged}
|
@focus=${this._focusChanged}
|
||||||
@blur=${this._focusChanged}
|
@blur=${this._focusChanged}
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex=${this.noCollapse ? -1 : 0}
|
||||||
aria-expanded=${this.expanded}
|
aria-expanded=${this.expanded}
|
||||||
aria-controls="sect1"
|
aria-controls="sect1"
|
||||||
>
|
>
|
||||||
${this.leftChevron
|
${this.leftChevron && !this.noCollapse
|
||||||
? html`
|
? html`
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
.path=${mdiChevronDown}
|
.path=${mdiChevronDown}
|
||||||
@ -57,7 +60,7 @@ export class HaExpansionPanel extends LitElement {
|
|||||||
<slot class="secondary" name="secondary">${this.secondary}</slot>
|
<slot class="secondary" name="secondary">${this.secondary}</slot>
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
${!this.leftChevron
|
${!this.leftChevron && !this.noCollapse
|
||||||
? html`
|
? html`
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
.path=${mdiChevronDown}
|
.path=${mdiChevronDown}
|
||||||
@ -106,6 +109,9 @@ export class HaExpansionPanel extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
if (this.noCollapse) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const newExpanded = !this.expanded;
|
const newExpanded = !this.expanded;
|
||||||
fireEvent(this, "expanded-will-change", { expanded: newExpanded });
|
fireEvent(this, "expanded-will-change", { expanded: newExpanded });
|
||||||
this._container.style.overflow = "hidden";
|
this._container.style.overflow = "hidden";
|
||||||
@ -130,6 +136,9 @@ export class HaExpansionPanel extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _focusChanged(ev) {
|
private _focusChanged(ev) {
|
||||||
|
if (this.noCollapse) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.shadowRoot!.querySelector(".top")!.classList.toggle(
|
this.shadowRoot!.querySelector(".top")!.classList.toggle(
|
||||||
"focused",
|
"focused",
|
||||||
ev.type === "focus"
|
ev.type === "focus"
|
||||||
@ -191,6 +200,9 @@ export class HaExpansionPanel extends LitElement {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
#summary.noCollapse {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
.summary-icon.expanded {
|
.summary-icon.expanded {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
import { SelectedDetail } from "@material/mwc-list";
|
import { SelectedDetail } from "@material/mwc-list";
|
||||||
import "@material/mwc-menu/mwc-menu-surface";
|
import "@material/mwc-menu/mwc-menu-surface";
|
||||||
import { mdiFilterVariantRemove } from "@mdi/js";
|
import { mdiFilterVariantRemove } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
nothing,
|
||||||
|
PropertyValues,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { Blueprints, fetchBlueprints } from "../data/blueprint";
|
import { Blueprints, fetchBlueprints } from "../data/blueprint";
|
||||||
@ -25,6 +32,16 @@ export class HaFilterBlueprints extends LitElement {
|
|||||||
|
|
||||||
@state() private _blueprints?: Blueprints;
|
@state() private _blueprints?: Blueprints;
|
||||||
|
|
||||||
|
public willUpdate(properties: PropertyValues) {
|
||||||
|
super.willUpdate(properties);
|
||||||
|
|
||||||
|
if (!this.hasUpdated) {
|
||||||
|
if (this.value?.length) {
|
||||||
|
this._findRelated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<ha-expansion-panel
|
<ha-expansion-panel
|
||||||
@ -96,7 +113,6 @@ export class HaFilterBlueprints extends LitElement {
|
|||||||
ev: CustomEvent<SelectedDetail<Set<number>>>
|
ev: CustomEvent<SelectedDetail<Set<number>>>
|
||||||
) {
|
) {
|
||||||
const blueprints = this._blueprints!;
|
const blueprints = this._blueprints!;
|
||||||
const relatedPromises: Promise<RelatedResult>[] = [];
|
|
||||||
|
|
||||||
if (!ev.detail.index.size) {
|
if (!ev.detail.index.size) {
|
||||||
fireEvent(this, "data-table-filter-changed", {
|
fireEvent(this, "data-table-filter-changed", {
|
||||||
@ -112,13 +128,33 @@ export class HaFilterBlueprints extends LitElement {
|
|||||||
for (const index of ev.detail.index) {
|
for (const index of ev.detail.index) {
|
||||||
const blueprintId = Object.keys(blueprints)[index];
|
const blueprintId = Object.keys(blueprints)[index];
|
||||||
value.push(blueprintId);
|
value.push(blueprintId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.value = value;
|
||||||
|
|
||||||
|
this._findRelated();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _findRelated() {
|
||||||
|
if (!this.value?.length) {
|
||||||
|
fireEvent(this, "data-table-filter-changed", {
|
||||||
|
value: [],
|
||||||
|
items: undefined,
|
||||||
|
});
|
||||||
|
this.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const relatedPromises: Promise<RelatedResult>[] = [];
|
||||||
|
|
||||||
|
for (const blueprintId of this.value) {
|
||||||
if (this.type) {
|
if (this.type) {
|
||||||
relatedPromises.push(
|
relatedPromises.push(
|
||||||
findRelated(this.hass, `${this.type}_blueprint`, blueprintId)
|
findRelated(this.hass, `${this.type}_blueprint`, blueprintId)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.value = value;
|
|
||||||
const results = await Promise.all(relatedPromises);
|
const results = await Promise.all(relatedPromises);
|
||||||
const items: Set<string> = new Set();
|
const items: Set<string> = new Set();
|
||||||
for (const result of results) {
|
for (const result of results) {
|
||||||
@ -128,7 +164,7 @@ export class HaFilterBlueprints extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fireEvent(this, "data-table-filter-changed", {
|
fireEvent(this, "data-table-filter-changed", {
|
||||||
value,
|
value: this.value,
|
||||||
items: this.type ? items : undefined,
|
items: this.type ? items : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,9 @@ export class HaFilterDevices extends LitElement {
|
|||||||
|
|
||||||
if (!this.hasUpdated) {
|
if (!this.hasUpdated) {
|
||||||
loadVirtualizer();
|
loadVirtualizer();
|
||||||
|
if (this.value?.length) {
|
||||||
|
this._findRelated();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,8 +87,14 @@ export class HaFilterDomains extends LitElement {
|
|||||||
Object.keys(states).forEach((entityId) => {
|
Object.keys(states).forEach((entityId) => {
|
||||||
domains.add(computeDomain(entityId));
|
domains.add(computeDomain(entityId));
|
||||||
});
|
});
|
||||||
return Array.from(domains)
|
|
||||||
.filter((domain) => !filter || domain.toLowerCase().includes(filter))
|
return Array.from(domains.values())
|
||||||
|
.filter(
|
||||||
|
(entry) =>
|
||||||
|
!filter ||
|
||||||
|
entry.toLowerCase().includes(filter) ||
|
||||||
|
domainToName(this.hass.localize, entry).toLowerCase().includes(filter)
|
||||||
|
)
|
||||||
.sort((a, b) => stringCompare(a, b, this.hass.locale.language));
|
.sort((a, b) => stringCompare(a, b, this.hass.locale.language));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -163,14 +169,14 @@ export class HaFilterDomains extends LitElement {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.header ha-icon-button {
|
.header ha-icon-button {
|
||||||
margin-inline-start: auto;
|
margin-inline-start: initial;
|
||||||
margin-inline-end: 8px;
|
margin-inline-end: 8px;
|
||||||
}
|
}
|
||||||
.badge {
|
.badge {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
margin-inline-start: 8px;
|
margin-inline-start: 8px;
|
||||||
margin-inline-end: 0;
|
margin-inline-end: initial;
|
||||||
min-width: 16px;
|
min-width: 16px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
@ -42,6 +42,9 @@ export class HaFilterEntities extends LitElement {
|
|||||||
|
|
||||||
if (!this.hasUpdated) {
|
if (!this.hasUpdated) {
|
||||||
loadVirtualizer();
|
loadVirtualizer();
|
||||||
|
if (this.value?.length) {
|
||||||
|
this._findRelated();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,15 +189,12 @@ export class HaFilterEntities extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const value: string[] = [];
|
|
||||||
|
|
||||||
for (const entityId of this.value) {
|
for (const entityId of this.value) {
|
||||||
value.push(entityId);
|
|
||||||
if (this.type) {
|
if (this.type) {
|
||||||
relatedPromises.push(findRelated(this.hass, "entity", entityId));
|
relatedPromises.push(findRelated(this.hass, "entity", entityId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.value = value;
|
|
||||||
const results = await Promise.all(relatedPromises);
|
const results = await Promise.all(relatedPromises);
|
||||||
const items: Set<string> = new Set();
|
const items: Set<string> = new Set();
|
||||||
for (const result of results) {
|
for (const result of results) {
|
||||||
@ -204,7 +204,7 @@ export class HaFilterEntities extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fireEvent(this, "data-table-filter-changed", {
|
fireEvent(this, "data-table-filter-changed", {
|
||||||
value,
|
value: this.value,
|
||||||
items: this.type ? items : undefined,
|
items: this.type ? items : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
import "@material/mwc-menu/mwc-menu-surface";
|
import "@material/mwc-menu/mwc-menu-surface";
|
||||||
import { mdiFilterVariantRemove, mdiTextureBox } from "@mdi/js";
|
import { mdiFilterVariantRemove, mdiTextureBox } from "@mdi/js";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
import {
|
||||||
|
CSSResultGroup,
|
||||||
|
LitElement,
|
||||||
|
PropertyValues,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
|
nothing,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { repeat } from "lit/directives/repeat";
|
import { repeat } from "lit/directives/repeat";
|
||||||
@ -42,6 +49,16 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _floors?: FloorRegistryEntry[];
|
@state() private _floors?: FloorRegistryEntry[];
|
||||||
|
|
||||||
|
public willUpdate(properties: PropertyValues) {
|
||||||
|
super.willUpdate(properties);
|
||||||
|
|
||||||
|
if (!this.hasUpdated) {
|
||||||
|
if (this.value?.floors?.length || this.value?.areas?.length) {
|
||||||
|
this._findRelated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const areas = this._areas(this.hass.areas, this._floors);
|
const areas = this._areas(this.hass.areas, this._floors);
|
||||||
|
|
||||||
@ -190,6 +207,10 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected firstUpdated() {
|
||||||
|
this._findRelated();
|
||||||
|
}
|
||||||
|
|
||||||
private _expandedWillChange(ev) {
|
private _expandedWillChange(ev) {
|
||||||
this._shouldRender = ev.detail.expanded;
|
this._shouldRender = ev.detail.expanded;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||||
import {
|
import {
|
||||||
ComboBoxDataProviderCallback,
|
ComboBoxDataProviderCallback,
|
||||||
@ -11,6 +10,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
|||||||
import { customIcons } from "../data/custom_icons";
|
import { customIcons } from "../data/custom_icons";
|
||||||
import { HomeAssistant, ValueChangedEvent } from "../types";
|
import { HomeAssistant, ValueChangedEvent } from "../types";
|
||||||
import "./ha-combo-box";
|
import "./ha-combo-box";
|
||||||
|
import "./ha-list-item";
|
||||||
import "./ha-icon";
|
import "./ha-icon";
|
||||||
|
|
||||||
type IconItem = {
|
type IconItem = {
|
||||||
@ -67,10 +67,10 @@ const loadCustomIconItems = async (iconsetPrefix: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const rowRenderer: ComboBoxLitRenderer<IconItem | RankedIcon> = (item) =>
|
const rowRenderer: ComboBoxLitRenderer<IconItem | RankedIcon> = (item) =>
|
||||||
html`<mwc-list-item graphic="avatar">
|
html`<ha-list-item graphic="avatar">
|
||||||
<ha-icon .icon=${item.icon} slot="graphic"></ha-icon>
|
<ha-icon .icon=${item.icon} slot="graphic"></ha-icon>
|
||||||
${item.icon}
|
${item.icon}
|
||||||
</mwc-list-item>`;
|
</ha-list-item>`;
|
||||||
|
|
||||||
@customElement("ha-icon-picker")
|
@customElement("ha-icon-picker")
|
||||||
export class HaIconPicker extends LitElement {
|
export class HaIconPicker extends LitElement {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "@material/web/ripple/ripple";
|
|
||||||
|
|
||||||
@customElement("ha-label")
|
@customElement("ha-label")
|
||||||
class HaLabel extends LitElement {
|
class HaLabel extends LitElement {
|
||||||
@ -11,7 +10,6 @@ class HaLabel extends LitElement {
|
|||||||
<span class="content">
|
<span class="content">
|
||||||
<slot name="icon"></slot>
|
<slot name="icon"></slot>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<md-ripple></md-ripple>
|
|
||||||
</span>
|
</span>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -27,7 +25,6 @@ class HaLabel extends LitElement {
|
|||||||
0.15
|
0.15
|
||||||
);
|
);
|
||||||
--ha-label-background-opacity: 1;
|
--ha-label-background-opacity: 1;
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
@ -6,7 +6,6 @@ import { stopPropagation } from "../common/dom/stop_propagation";
|
|||||||
import { formatLanguageCode } from "../common/language/format_language";
|
import { formatLanguageCode } from "../common/language/format_language";
|
||||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||||
import { FrontendLocaleData } from "../data/translation";
|
import { FrontendLocaleData } from "../data/translation";
|
||||||
import "../resources/intl-polyfill";
|
|
||||||
import { translationMetadata } from "../resources/translations-metadata";
|
import { translationMetadata } from "../resources/translations-metadata";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./ha-list-item";
|
import "./ha-list-item";
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { customElement } from "lit/decorators";
|
|
||||||
import "element-internals-polyfill";
|
|
||||||
import { MdListItem } from "@material/web/list/list-item";
|
import { MdListItem } from "@material/web/list/list-item";
|
||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
|
import { customElement } from "lit/decorators";
|
||||||
|
|
||||||
@customElement("ha-list-item-new")
|
@customElement("ha-list-item-new")
|
||||||
export class HaListItemNew extends MdListItem {
|
export class HaListItemNew extends MdListItem {
|
||||||
|
@ -100,6 +100,7 @@ export class HaListItem extends ListItemBase {
|
|||||||
span.material-icons:first-of-type,
|
span.material-icons:first-of-type,
|
||||||
span.material-icons:last-of-type {
|
span.material-icons:last-of-type {
|
||||||
direction: rtl !important;
|
direction: rtl !important;
|
||||||
|
--direction: rtl;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
: css``,
|
: css``,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { customElement } from "lit/decorators";
|
|
||||||
import "element-internals-polyfill";
|
|
||||||
import { MdList } from "@material/web/list/list";
|
import { MdList } from "@material/web/list/list";
|
||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
|
import { customElement } from "lit/decorators";
|
||||||
|
|
||||||
@customElement("ha-list-new")
|
@customElement("ha-list-new")
|
||||||
export class HaListNew extends MdList {
|
export class HaListNew extends MdList {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { MdMenuItem } from "@material/web/menu/menu-item";
|
import { MdMenuItem } from "@material/web/menu/menu-item";
|
||||||
import "element-internals-polyfill";
|
|
||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { customElement } from "lit/decorators";
|
|
||||||
import "element-internals-polyfill";
|
|
||||||
import { css } from "lit";
|
|
||||||
import { MdMenu } from "@material/web/menu/menu";
|
import { MdMenu } from "@material/web/menu/menu";
|
||||||
|
import { css } from "lit";
|
||||||
|
import { customElement } from "lit/decorators";
|
||||||
|
|
||||||
@customElement("ha-menu")
|
@customElement("ha-menu")
|
||||||
export class HaMenu extends MdMenu {
|
export class HaMenu extends MdMenu {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
|
import { MdOutlinedButton } from "@material/web/button/outlined-button";
|
||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import "element-internals-polyfill";
|
|
||||||
import { MdOutlinedButton } from "@material/web/button/outlined-button";
|
|
||||||
|
|
||||||
@customElement("ha-outlined-button")
|
@customElement("ha-outlined-button")
|
||||||
export class HaOutlinedButton extends MdOutlinedButton {
|
export class HaOutlinedButton extends MdOutlinedButton {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { MdOutlinedField } from "@material/web/field/outlined-field";
|
import { MdOutlinedField } from "@material/web/field/outlined-field";
|
||||||
import "element-internals-polyfill";
|
|
||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import { literal } from "lit/static-html";
|
import { literal } from "lit/static-html";
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
|
import { MdOutlinedIconButton } from "@material/web/iconbutton/outlined-icon-button";
|
||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import "element-internals-polyfill";
|
|
||||||
import { MdOutlinedIconButton } from "@material/web/iconbutton/outlined-icon-button";
|
|
||||||
|
|
||||||
@customElement("ha-outlined-icon-button")
|
@customElement("ha-outlined-icon-button")
|
||||||
export class HaOutlinedIconButton extends MdOutlinedIconButton {
|
export class HaOutlinedIconButton extends MdOutlinedIconButton {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { MdOutlinedTextField } from "@material/web/textfield/outlined-text-field";
|
import { MdOutlinedTextField } from "@material/web/textfield/outlined-text-field";
|
||||||
import "element-internals-polyfill";
|
|
||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import { literal } from "lit/static-html";
|
import { literal } from "lit/static-html";
|
||||||
|
@ -2,6 +2,7 @@ import { mdiImagePlus } from "@mdi/js";
|
|||||||
import { LitElement, TemplateResult, css, html } from "lit";
|
import { LitElement, TemplateResult, css, html } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { haStyle } from "../resources/styles";
|
||||||
import { createImage, generateImageThumbnailUrl } from "../data/image_upload";
|
import { createImage, generateImageThumbnailUrl } from "../data/image_upload";
|
||||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||||
import {
|
import {
|
||||||
@ -31,6 +32,8 @@ export class HaPictureUpload extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public cropOptions?: CropOptions;
|
@property({ attribute: false }) public cropOptions?: CropOptions;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public original = false;
|
||||||
|
|
||||||
@property({ type: Number }) public size = 512;
|
@property({ type: Number }) public size = 512;
|
||||||
|
|
||||||
@state() private _uploading = false;
|
@state() private _uploading = false;
|
||||||
@ -60,13 +63,15 @@ export class HaPictureUpload extends LitElement {
|
|||||||
alt=${this.currentImageAltText ||
|
alt=${this.currentImageAltText ||
|
||||||
this.hass.localize("ui.components.picture-upload.current_image_alt")}
|
this.hass.localize("ui.components.picture-upload.current_image_alt")}
|
||||||
/>
|
/>
|
||||||
<ha-button
|
<div>
|
||||||
@click=${this._handleChangeClick}
|
<ha-button
|
||||||
.label=${this.hass.localize(
|
@click=${this._handleChangeClick}
|
||||||
"ui.components.picture-upload.change_picture"
|
.label=${this.hass.localize(
|
||||||
)}
|
"ui.components.picture-upload.change_picture"
|
||||||
>
|
)}
|
||||||
</ha-button>
|
>
|
||||||
|
</ha-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@ -122,7 +127,11 @@ export class HaPictureUpload extends LitElement {
|
|||||||
this._uploading = true;
|
this._uploading = true;
|
||||||
try {
|
try {
|
||||||
const media = await createImage(this.hass, file);
|
const media = await createImage(this.hass, file);
|
||||||
this.value = generateImageThumbnailUrl(media.id, this.size);
|
this.value = generateImageThumbnailUrl(
|
||||||
|
media.id,
|
||||||
|
this.size,
|
||||||
|
this.original
|
||||||
|
);
|
||||||
fireEvent(this, "change");
|
fireEvent(this, "change");
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
@ -134,32 +143,35 @@ export class HaPictureUpload extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return css`
|
return [
|
||||||
:host {
|
haStyle,
|
||||||
display: block;
|
css`
|
||||||
height: 240px;
|
:host {
|
||||||
}
|
display: block;
|
||||||
ha-file-upload {
|
height: 240px;
|
||||||
height: 100%;
|
}
|
||||||
}
|
ha-file-upload {
|
||||||
.center-vertical {
|
height: 100%;
|
||||||
display: flex;
|
}
|
||||||
align-items: center;
|
.center-vertical {
|
||||||
height: 100%;
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
.value {
|
height: 100%;
|
||||||
width: 100%;
|
}
|
||||||
display: flex;
|
.value {
|
||||||
flex-direction: column;
|
width: 100%;
|
||||||
align-items: center;
|
display: flex;
|
||||||
}
|
flex-direction: column;
|
||||||
img {
|
align-items: center;
|
||||||
max-width: 100%;
|
}
|
||||||
max-height: 200px;
|
img {
|
||||||
margin-bottom: 4px;
|
max-width: 100%;
|
||||||
border-radius: var(--file-upload-image-border-radius);
|
max-height: 200px;
|
||||||
}
|
margin-bottom: 4px;
|
||||||
`;
|
border-radius: var(--file-upload-image-border-radius);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
62
src/components/ha-ripple.ts
Normal file
62
src/components/ha-ripple.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { AttachableController } from "@material/web/internal/controller/attachable-controller";
|
||||||
|
import { MdRipple } from "@material/web/ripple/ripple";
|
||||||
|
import { css } from "lit";
|
||||||
|
import { customElement } from "lit/decorators";
|
||||||
|
|
||||||
|
@customElement("ha-ripple")
|
||||||
|
export class HaRipple extends MdRipple {
|
||||||
|
private readonly attachableTouchController = new AttachableController(
|
||||||
|
this,
|
||||||
|
this.onTouchControlChange.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
|
attach(control: HTMLElement) {
|
||||||
|
super.attach(control);
|
||||||
|
this.attachableTouchController.attach(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
detach() {
|
||||||
|
super.detach();
|
||||||
|
this.attachableTouchController.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleTouchEnd = () => {
|
||||||
|
if (!this.disabled) {
|
||||||
|
// @ts-ignore
|
||||||
|
super.endPressAnimation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private onTouchControlChange(
|
||||||
|
prev: HTMLElement | null,
|
||||||
|
next: HTMLElement | null
|
||||||
|
) {
|
||||||
|
// Add touchend event to clean ripple on touch devices using action handler
|
||||||
|
prev?.removeEventListener("touchend", this._handleTouchEnd);
|
||||||
|
next?.addEventListener("touchend", this._handleTouchEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static override styles = [
|
||||||
|
...super.styles,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
--md-ripple-hover-opacity: var(--ha-ripple-hover-opacity, 0.08);
|
||||||
|
--md-ripple-pressed-opacity: var(--ha-ripple-pressed-opacity, 0.12);
|
||||||
|
--md-ripple-hover-color: var(
|
||||||
|
--ha-ripple-hover-color,
|
||||||
|
var(--ha-ripple-color, var(--secondary-text-color))
|
||||||
|
);
|
||||||
|
--md-ripple-pressed-color: var(
|
||||||
|
--ha-ripple-pressed-color,
|
||||||
|
var(--ha-ripple-color, var(--secondary-text-color))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-ripple": HaRipple;
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,10 @@ import {
|
|||||||
fetchEntitySourcesWithCache,
|
fetchEntitySourcesWithCache,
|
||||||
} from "../../data/entity_sources";
|
} from "../../data/entity_sources";
|
||||||
import type { EntitySelector } from "../../data/selector";
|
import type { EntitySelector } from "../../data/selector";
|
||||||
import { filterSelectorEntities } from "../../data/selector";
|
import {
|
||||||
|
filterSelectorEntities,
|
||||||
|
computeCreateDomains,
|
||||||
|
} from "../../data/selector";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../entity/ha-entities-picker";
|
import "../entity/ha-entities-picker";
|
||||||
import "../entity/ha-entity-picker";
|
import "../entity/ha-entity-picker";
|
||||||
@ -31,6 +34,8 @@ export class HaEntitySelector extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public required = true;
|
@property({ type: Boolean }) public required = true;
|
||||||
|
|
||||||
|
@state() private _createDomains: string[] | undefined;
|
||||||
|
|
||||||
private _hasIntegration(selector: EntitySelector) {
|
private _hasIntegration(selector: EntitySelector) {
|
||||||
return (
|
return (
|
||||||
selector.entity?.filter &&
|
selector.entity?.filter &&
|
||||||
@ -64,6 +69,7 @@ export class HaEntitySelector extends LitElement {
|
|||||||
.includeEntities=${this.selector.entity?.include_entities}
|
.includeEntities=${this.selector.entity?.include_entities}
|
||||||
.excludeEntities=${this.selector.entity?.exclude_entities}
|
.excludeEntities=${this.selector.entity?.exclude_entities}
|
||||||
.entityFilter=${this._filterEntities}
|
.entityFilter=${this._filterEntities}
|
||||||
|
.createDomains=${this._createDomains}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
allow-custom-entity
|
allow-custom-entity
|
||||||
@ -79,6 +85,7 @@ export class HaEntitySelector extends LitElement {
|
|||||||
.includeEntities=${this.selector.entity.include_entities}
|
.includeEntities=${this.selector.entity.include_entities}
|
||||||
.excludeEntities=${this.selector.entity.exclude_entities}
|
.excludeEntities=${this.selector.entity.exclude_entities}
|
||||||
.entityFilter=${this._filterEntities}
|
.entityFilter=${this._filterEntities}
|
||||||
|
.createDomains=${this._createDomains}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
></ha-entities-picker>
|
></ha-entities-picker>
|
||||||
@ -96,6 +103,9 @@ export class HaEntitySelector extends LitElement {
|
|||||||
this._entitySources = sources;
|
this._entitySources = sources;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (changedProps.has("selector")) {
|
||||||
|
this._createDomains = computeCreateDomains(this.selector);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _filterEntities = (entity: HassEntity): boolean => {
|
private _filterEntities = (entity: HassEntity): boolean => {
|
||||||
|
145
src/components/ha-selector/ha-selector-image.ts
Normal file
145
src/components/ha-selector/ha-selector-image.ts
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { ImageSelector } from "../../data/selector";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-icon-button";
|
||||||
|
import "../ha-textarea";
|
||||||
|
import "../ha-textfield";
|
||||||
|
import "../ha-picture-upload";
|
||||||
|
import "../ha-radio";
|
||||||
|
import type { HaPictureUpload } from "../ha-picture-upload";
|
||||||
|
import { URL_PREFIX } from "../../data/image_upload";
|
||||||
|
|
||||||
|
@customElement("ha-selector-image")
|
||||||
|
export class HaImageSelector extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public value?: any;
|
||||||
|
|
||||||
|
@property() public name?: string;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property() public placeholder?: string;
|
||||||
|
|
||||||
|
@property() public helper?: string;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public selector!: ImageSelector;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public required = true;
|
||||||
|
|
||||||
|
@state() private showUpload = false;
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps): void {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
|
||||||
|
if (!this.value || this.value.startsWith(URL_PREFIX)) {
|
||||||
|
this.showUpload = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
${this.hass.localize("ui.components.selectors.image.select_image")}
|
||||||
|
<ha-formfield
|
||||||
|
.label=${this.hass.localize("ui.components.selectors.image.upload")}
|
||||||
|
>
|
||||||
|
<ha-radio
|
||||||
|
name="mode"
|
||||||
|
value="upload"
|
||||||
|
.checked=${this.showUpload}
|
||||||
|
@change=${this._radioGroupPicked}
|
||||||
|
></ha-radio>
|
||||||
|
</ha-formfield>
|
||||||
|
<ha-formfield
|
||||||
|
.label=${this.hass.localize("ui.components.selectors.image.url")}
|
||||||
|
>
|
||||||
|
<ha-radio
|
||||||
|
name="mode"
|
||||||
|
value="url"
|
||||||
|
.checked=${!this.showUpload}
|
||||||
|
@change=${this._radioGroupPicked}
|
||||||
|
></ha-radio>
|
||||||
|
</ha-formfield>
|
||||||
|
</label>
|
||||||
|
${!this.showUpload
|
||||||
|
? html`
|
||||||
|
<ha-textfield
|
||||||
|
.name=${this.name}
|
||||||
|
.value=${this.value || ""}
|
||||||
|
.placeholder=${this.placeholder || ""}
|
||||||
|
.helper=${this.helper}
|
||||||
|
helperPersistent
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@input=${this._handleChange}
|
||||||
|
.label=${this.label || ""}
|
||||||
|
.required=${this.required}
|
||||||
|
></ha-textfield>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<ha-picture-upload
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.value?.startsWith(URL_PREFIX) ? this.value : null}
|
||||||
|
.original=${this.selector.image?.original}
|
||||||
|
.cropOptions=${this.selector.image?.crop}
|
||||||
|
@change=${this._pictureChanged}
|
||||||
|
></ha-picture-upload>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _radioGroupPicked(ev): void {
|
||||||
|
this.showUpload = ev.target.value === "upload";
|
||||||
|
}
|
||||||
|
|
||||||
|
private _pictureChanged(ev) {
|
||||||
|
const value = (ev.target as HaPictureUpload).value;
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", { value: value ?? undefined });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleChange(ev) {
|
||||||
|
let value = ev.target.value;
|
||||||
|
if (this.value === value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value === "" && !this.required) {
|
||||||
|
value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", { value });
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
ha-textarea,
|
||||||
|
ha-textfield {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-selector-image": HaImageSelector;
|
||||||
|
}
|
||||||
|
}
|
@ -278,6 +278,14 @@ export class HaSelectSelector extends LitElement {
|
|||||||
|
|
||||||
private _valueChanged(ev) {
|
private _valueChanged(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
if (ev.detail?.index === -1 && this.value !== undefined) {
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: undefined,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const value = ev.detail?.value || ev.target.value;
|
const value = ev.detail?.value || ev.target.value;
|
||||||
if (this.disabled || value === undefined || value === (this.value ?? "")) {
|
if (this.disabled || value === undefined || value === (this.value ?? "")) {
|
||||||
return;
|
return;
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
filterSelectorDevices,
|
filterSelectorDevices,
|
||||||
filterSelectorEntities,
|
filterSelectorEntities,
|
||||||
TargetSelector,
|
TargetSelector,
|
||||||
|
computeCreateDomains,
|
||||||
} from "../../data/selector";
|
} from "../../data/selector";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import "../ha-target-picker";
|
import "../ha-target-picker";
|
||||||
@ -42,6 +43,8 @@ export class HaTargetSelector extends LitElement {
|
|||||||
|
|
||||||
@state() private _entitySources?: EntitySources;
|
@state() private _entitySources?: EntitySources;
|
||||||
|
|
||||||
|
@state() private _createDomains: string[] | undefined;
|
||||||
|
|
||||||
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
||||||
|
|
||||||
private _hasIntegration(selector: TargetSelector) {
|
private _hasIntegration(selector: TargetSelector) {
|
||||||
@ -68,6 +71,9 @@ export class HaTargetSelector extends LitElement {
|
|||||||
this._entitySources = sources;
|
this._entitySources = sources;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (changedProperties.has("selector")) {
|
||||||
|
this._createDomains = computeCreateDomains(this.selector);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@ -82,7 +88,7 @@ export class HaTargetSelector extends LitElement {
|
|||||||
.deviceFilter=${this._filterDevices}
|
.deviceFilter=${this._filterDevices}
|
||||||
.entityFilter=${this._filterEntities}
|
.entityFilter=${this._filterEntities}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.createDomains=${this.selector.target?.create_domains}
|
.createDomains=${this._createDomains}
|
||||||
></ha-target-picker>`;
|
></ha-target-picker>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ const LOAD_ELEMENTS = {
|
|||||||
file: () => import("./ha-selector-file"),
|
file: () => import("./ha-selector-file"),
|
||||||
floor: () => import("./ha-selector-floor"),
|
floor: () => import("./ha-selector-floor"),
|
||||||
label: () => import("./ha-selector-label"),
|
label: () => import("./ha-selector-label"),
|
||||||
|
image: () => import("./ha-selector-image"),
|
||||||
language: () => import("./ha-selector-language"),
|
language: () => import("./ha-selector-language"),
|
||||||
navigation: () => import("./ha-selector-navigation"),
|
navigation: () => import("./ha-selector-navigation"),
|
||||||
number: () => import("./ha-selector-number"),
|
number: () => import("./ha-selector-number"),
|
||||||
|
@ -44,7 +44,6 @@ import "./ha-service-picker";
|
|||||||
import "./ha-settings-row";
|
import "./ha-settings-row";
|
||||||
import "./ha-yaml-editor";
|
import "./ha-yaml-editor";
|
||||||
import type { HaYamlEditor } from "./ha-yaml-editor";
|
import type { HaYamlEditor } from "./ha-yaml-editor";
|
||||||
import { isHelperDomain } from "../panels/config/helpers/const";
|
|
||||||
|
|
||||||
const attributeFilter = (values: any[], attribute: any) => {
|
const attributeFilter = (values: any[], attribute: any) => {
|
||||||
if (typeof attribute === "object") {
|
if (typeof attribute === "object") {
|
||||||
@ -366,12 +365,8 @@ export class HaServiceControl extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _targetSelector = memoizeOne(
|
private _targetSelector = memoizeOne(
|
||||||
(targetSelector: TargetSelector | null | undefined, domain?: string) => {
|
(targetSelector: TargetSelector | null | undefined) =>
|
||||||
const create_domains = isHelperDomain(domain) ? [domain] : undefined;
|
targetSelector ? { target: { ...targetSelector } } : { target: {} }
|
||||||
return targetSelector
|
|
||||||
? { target: { ...targetSelector, create_domains } }
|
|
||||||
: { target: { create_domains } };
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@ -462,8 +457,7 @@ export class HaServiceControl extends LitElement {
|
|||||||
><ha-selector
|
><ha-selector
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.selector=${this._targetSelector(
|
.selector=${this._targetSelector(
|
||||||
serviceData.target as TargetSelector,
|
serviceData.target as TargetSelector
|
||||||
domain
|
|
||||||
)}
|
)}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._targetChanged}
|
@value-changed=${this._targetChanged}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { customElement } from "lit/decorators";
|
|
||||||
import "element-internals-polyfill";
|
|
||||||
import { MdSlider } from "@material/web/slider/slider";
|
import { MdSlider } from "@material/web/slider/slider";
|
||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
|
import { customElement } from "lit/decorators";
|
||||||
import { mainWindow } from "../common/dom/get_main_window";
|
import { mainWindow } from "../common/dom/get_main_window";
|
||||||
|
|
||||||
@customElement("ha-slider")
|
@customElement("ha-slider")
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { customElement } from "lit/decorators";
|
|
||||||
import "element-internals-polyfill";
|
|
||||||
import { css } from "lit";
|
|
||||||
import { MdSubMenu } from "@material/web/menu/sub-menu";
|
import { MdSubMenu } from "@material/web/menu/sub-menu";
|
||||||
|
import { css } from "lit";
|
||||||
|
import { customElement } from "lit/decorators";
|
||||||
|
|
||||||
@customElement("ha-sub-menu")
|
@customElement("ha-sub-menu")
|
||||||
export class HaSubMenu extends MdSubMenu {
|
export class HaSubMenu extends MdSubMenu {
|
||||||
|
@ -1,15 +1,7 @@
|
|||||||
import type { Ripple } from "@material/mwc-ripple";
|
|
||||||
import "@material/mwc-ripple/mwc-ripple";
|
|
||||||
import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import {
|
import { customElement, property } from "lit/decorators";
|
||||||
customElement,
|
|
||||||
eventOptions,
|
|
||||||
property,
|
|
||||||
queryAsync,
|
|
||||||
state,
|
|
||||||
} from "lit/decorators";
|
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
|
import "./ha-ripple";
|
||||||
|
|
||||||
@customElement("ha-tab")
|
@customElement("ha-tab")
|
||||||
export class HaTab extends LitElement {
|
export class HaTab extends LitElement {
|
||||||
@ -19,10 +11,6 @@ export class HaTab extends LitElement {
|
|||||||
|
|
||||||
@property() public name?: string;
|
@property() public name?: string;
|
||||||
|
|
||||||
@queryAsync("mwc-ripple") private _ripple!: Promise<Ripple | null>;
|
|
||||||
|
|
||||||
@state() private _shouldRenderRipple = false;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
@ -30,60 +18,21 @@ export class HaTab extends LitElement {
|
|||||||
role="tab"
|
role="tab"
|
||||||
aria-selected=${this.active}
|
aria-selected=${this.active}
|
||||||
aria-label=${ifDefined(this.name)}
|
aria-label=${ifDefined(this.name)}
|
||||||
@focus=${this.handleRippleFocus}
|
|
||||||
@blur=${this.handleRippleBlur}
|
|
||||||
@mousedown=${this.handleRippleActivate}
|
|
||||||
@mouseup=${this.handleRippleDeactivate}
|
|
||||||
@mouseenter=${this.handleRippleMouseEnter}
|
|
||||||
@mouseleave=${this.handleRippleMouseLeave}
|
|
||||||
@touchstart=${this.handleRippleActivate}
|
|
||||||
@touchend=${this.handleRippleDeactivate}
|
|
||||||
@touchcancel=${this.handleRippleDeactivate}
|
|
||||||
@keydown=${this._handleKeyDown}
|
@keydown=${this._handleKeyDown}
|
||||||
>
|
>
|
||||||
${this.narrow ? html`<slot name="icon"></slot>` : ""}
|
${this.narrow ? html`<slot name="icon"></slot>` : ""}
|
||||||
<span class="name">${this.name}</span>
|
<span class="name">${this.name}</span>
|
||||||
${this._shouldRenderRipple ? html`<mwc-ripple></mwc-ripple>` : ""}
|
<ha-ripple></ha-ripple>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _rippleHandlers: RippleHandlers = new RippleHandlers(() => {
|
|
||||||
this._shouldRenderRipple = true;
|
|
||||||
return this._ripple;
|
|
||||||
});
|
|
||||||
|
|
||||||
private _handleKeyDown(ev: KeyboardEvent): void {
|
private _handleKeyDown(ev: KeyboardEvent): void {
|
||||||
if (ev.key === "Enter") {
|
if (ev.key === "Enter") {
|
||||||
(ev.target as HTMLElement).click();
|
(ev.target as HTMLElement).click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@eventOptions({ passive: true })
|
|
||||||
private handleRippleActivate(evt?: Event) {
|
|
||||||
this._rippleHandlers.startPress(evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRippleDeactivate() {
|
|
||||||
this._rippleHandlers.endPress();
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRippleMouseEnter() {
|
|
||||||
this._rippleHandlers.startHover();
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRippleMouseLeave() {
|
|
||||||
this._rippleHandlers.endHover();
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRippleFocus() {
|
|
||||||
this._rippleHandlers.startFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRippleBlur() {
|
|
||||||
this._rippleHandlers.endFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
div {
|
div {
|
||||||
@ -126,6 +75,15 @@ export class HaTab extends LitElement {
|
|||||||
:host([narrow]) div {
|
:host([narrow]) div {
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div:focus-visible:before {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
inset: 0;
|
||||||
|
background-color: var(--secondary-text-color);
|
||||||
|
opacity: 0.08;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,6 +206,7 @@ export class HaTextField extends TextFieldBase {
|
|||||||
.mdc-floating-label,
|
.mdc-floating-label,
|
||||||
.mdc-text-field__input[type="number"] {
|
.mdc-text-field__input[type="number"] {
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
|
--direction: rtl;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
: css``,
|
: css``,
|
||||||
|
@ -22,7 +22,6 @@ import {
|
|||||||
} from "../../common/dom/setup-leaflet-map";
|
} from "../../common/dom/setup-leaflet-map";
|
||||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
import { loadPolyfillIfNeeded } from "../../resources/resize-observer.polyfill";
|
|
||||||
import { HomeAssistant, ThemeMode } from "../../types";
|
import { HomeAssistant, ThemeMode } from "../../types";
|
||||||
import { isTouch } from "../../util/is_touch";
|
import { isTouch } from "../../util/is_touch";
|
||||||
import "../ha-icon-button";
|
import "../ha-icon-button";
|
||||||
@ -178,16 +177,24 @@ export class HaMap extends ReactiveElement {
|
|||||||
map!.classList.toggle("forced-light", this.themeMode === "light");
|
map!.classList.toggle("forced-light", this.themeMode === "light");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _loading = false;
|
||||||
|
|
||||||
private async _loadMap(): Promise<void> {
|
private async _loadMap(): Promise<void> {
|
||||||
|
if (this._loading) return;
|
||||||
let map = this.shadowRoot!.getElementById("map");
|
let map = this.shadowRoot!.getElementById("map");
|
||||||
if (!map) {
|
if (!map) {
|
||||||
map = document.createElement("div");
|
map = document.createElement("div");
|
||||||
map.id = "map";
|
map.id = "map";
|
||||||
this.shadowRoot!.append(map);
|
this.shadowRoot!.append(map);
|
||||||
}
|
}
|
||||||
[this.leafletMap, this.Leaflet] = await setupLeafletMap(map);
|
this._loading = true;
|
||||||
this._updateMapStyle();
|
try {
|
||||||
this._loaded = true;
|
[this.leafletMap, this.Leaflet] = await setupLeafletMap(map);
|
||||||
|
this._updateMapStyle();
|
||||||
|
this._loaded = true;
|
||||||
|
} finally {
|
||||||
|
this._loading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public fitMap(options?: { zoom?: number; pad?: number }): void {
|
public fitMap(options?: { zoom?: number; pad?: number }): void {
|
||||||
@ -528,7 +535,6 @@ export class HaMap extends ReactiveElement {
|
|||||||
|
|
||||||
private async _attachObserver(): Promise<void> {
|
private async _attachObserver(): Promise<void> {
|
||||||
if (!this._resizeObserver) {
|
if (!this._resizeObserver) {
|
||||||
await loadPolyfillIfNeeded();
|
|
||||||
this._resizeObserver = new ResizeObserver(() => {
|
this._resizeObserver = new ResizeObserver(() => {
|
||||||
this.leafletMap?.invalidateSize({ debounceMoveend: true });
|
this.leafletMap?.invalidateSize({ debounceMoveend: true });
|
||||||
});
|
});
|
||||||
|
@ -39,7 +39,6 @@ import {
|
|||||||
import { browseLocalMediaPlayer } from "../../data/media_source";
|
import { browseLocalMediaPlayer } from "../../data/media_source";
|
||||||
import { isTTSMediaSource } from "../../data/tts";
|
import { isTTSMediaSource } from "../../data/tts";
|
||||||
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
||||||
import { loadPolyfillIfNeeded } from "../../resources/resize-observer.polyfill";
|
|
||||||
import { haStyle } from "../../resources/styles";
|
import { haStyle } from "../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import {
|
import {
|
||||||
@ -770,7 +769,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
|
|
||||||
private async _attachResizeObserver(): Promise<void> {
|
private async _attachResizeObserver(): Promise<void> {
|
||||||
if (!this._resizeObserver) {
|
if (!this._resizeObserver) {
|
||||||
await loadPolyfillIfNeeded();
|
|
||||||
this._resizeObserver = new ResizeObserver(
|
this._resizeObserver = new ResizeObserver(
|
||||||
debounce(() => this._measureCard(), 250, false)
|
debounce(() => this._measureCard(), 250, false)
|
||||||
);
|
);
|
||||||
|
@ -79,7 +79,7 @@ class SearchInputOutlined extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _filterInputChanged(e) {
|
private async _filterInputChanged(e) {
|
||||||
this._filterChanged(e.target.value);
|
this._filterChanged(e.target.value?.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _clearSearch() {
|
private async _clearSearch() {
|
||||||
|
@ -67,7 +67,7 @@ class SearchInput extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _filterInputChanged(e) {
|
private async _filterInputChanged(e) {
|
||||||
this._filterChanged(e.target.value);
|
this._filterChanged(e.target.value?.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _clearSearch() {
|
private async _clearSearch() {
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
mdiClose,
|
mdiClose,
|
||||||
mdiCodeBraces,
|
mdiCodeBraces,
|
||||||
mdiCodeBrackets,
|
mdiCodeBrackets,
|
||||||
|
mdiFormatListNumbered,
|
||||||
mdiRefresh,
|
mdiRefresh,
|
||||||
mdiRoomService,
|
mdiRoomService,
|
||||||
mdiShuffleDisabled,
|
mdiShuffleDisabled,
|
||||||
@ -29,6 +30,7 @@ import {
|
|||||||
ManualScriptConfig,
|
ManualScriptConfig,
|
||||||
ParallelAction,
|
ParallelAction,
|
||||||
RepeatAction,
|
RepeatAction,
|
||||||
|
SequenceAction,
|
||||||
ServiceAction,
|
ServiceAction,
|
||||||
WaitAction,
|
WaitAction,
|
||||||
WaitForTriggerAction,
|
WaitForTriggerAction,
|
||||||
@ -119,6 +121,7 @@ export class HatScriptGraph extends LitElement {
|
|||||||
repeat: this.render_repeat_node,
|
repeat: this.render_repeat_node,
|
||||||
choose: this.render_choose_node,
|
choose: this.render_choose_node,
|
||||||
if: this.render_if_node,
|
if: this.render_if_node,
|
||||||
|
sequence: this.render_sequence_node,
|
||||||
parallel: this.render_parallel_node,
|
parallel: this.render_parallel_node,
|
||||||
other: this.render_other_node,
|
other: this.render_other_node,
|
||||||
};
|
};
|
||||||
@ -460,6 +463,44 @@ export class HatScriptGraph extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private render_sequence_node(
|
||||||
|
node: SequenceAction,
|
||||||
|
path: string,
|
||||||
|
graphStart = false,
|
||||||
|
disabled = false
|
||||||
|
) {
|
||||||
|
const trace: any = this.trace.trace[path];
|
||||||
|
return html`
|
||||||
|
<hat-graph-branch
|
||||||
|
tabindex=${trace === undefined ? "-1" : "0"}
|
||||||
|
@focus=${this.selectNode(node, path)}
|
||||||
|
?track=${path in this.trace.trace}
|
||||||
|
?active=${this.selected === path}
|
||||||
|
.notEnabled=${disabled || node.enabled === false}
|
||||||
|
>
|
||||||
|
<div class="graph-container" ?track=${path in this.trace.trace}>
|
||||||
|
<hat-graph-node
|
||||||
|
.graphStart=${graphStart}
|
||||||
|
.iconPath=${mdiFormatListNumbered}
|
||||||
|
?track=${path in this.trace.trace}
|
||||||
|
?active=${this.selected === path}
|
||||||
|
.notEnabled=${disabled || node.enabled === false}
|
||||||
|
slot="head"
|
||||||
|
nofocus
|
||||||
|
></hat-graph-node>
|
||||||
|
${ensureArray(node.sequence).map((action, i) =>
|
||||||
|
this.render_action_node(
|
||||||
|
action,
|
||||||
|
`${path}/sequence/${i}`,
|
||||||
|
false,
|
||||||
|
disabled || node.enabled === false
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</hat-graph-branch>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
private render_parallel_node(
|
private render_parallel_node(
|
||||||
node: ParallelAction,
|
node: ParallelAction,
|
||||||
path: string,
|
path: string,
|
||||||
|
@ -37,6 +37,7 @@ import {
|
|||||||
IfAction,
|
IfAction,
|
||||||
ParallelAction,
|
ParallelAction,
|
||||||
RepeatAction,
|
RepeatAction,
|
||||||
|
SequenceAction,
|
||||||
getActionType,
|
getActionType,
|
||||||
} from "../../data/script";
|
} from "../../data/script";
|
||||||
import { describeAction } from "../../data/script_i18n";
|
import { describeAction } from "../../data/script_i18n";
|
||||||
@ -310,6 +311,10 @@ class ActionRenderer {
|
|||||||
return this._handleIf(index);
|
return this._handleIf(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (actionType === "sequence") {
|
||||||
|
return this._handleSequence(index);
|
||||||
|
}
|
||||||
|
|
||||||
if (actionType === "parallel") {
|
if (actionType === "parallel") {
|
||||||
return this._handleParallel(index);
|
return this._handleParallel(index);
|
||||||
}
|
}
|
||||||
@ -579,6 +584,37 @@ class ActionRenderer {
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleSequence(index: number): number {
|
||||||
|
const sequencePath = this.keys[index];
|
||||||
|
const sequenceConfig = this._getDataFromPath(
|
||||||
|
this.keys[index]
|
||||||
|
) as SequenceAction;
|
||||||
|
|
||||||
|
this._renderEntry(
|
||||||
|
sequencePath,
|
||||||
|
sequenceConfig.alias ||
|
||||||
|
describeAction(
|
||||||
|
this.hass,
|
||||||
|
this.entityReg,
|
||||||
|
this.labelReg,
|
||||||
|
this.floorReg,
|
||||||
|
sequenceConfig,
|
||||||
|
"sequence"
|
||||||
|
),
|
||||||
|
undefined,
|
||||||
|
sequenceConfig.enabled === false
|
||||||
|
);
|
||||||
|
|
||||||
|
let i: number;
|
||||||
|
|
||||||
|
for (i = index + 1; i < this.keys.length; i++) {
|
||||||
|
const path = this.keys[i];
|
||||||
|
this._renderItem(i, getActionType(this._getDataFromPath(path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
private _handleParallel(index: number): number {
|
private _handleParallel(index: number): number {
|
||||||
const parallelPath = this.keys[index];
|
const parallelPath = this.keys[index];
|
||||||
const startLevel = parallelPath.split("/").length;
|
const startLevel = parallelPath.split("/").length;
|
||||||
|
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