mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
20231025.0 (#18395)
This commit is contained in:
commit
fdaefadd18
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.0
|
uses: actions/checkout@v4.1.1
|
||||||
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.0
|
uses: actions/checkout@v4.1.1
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
|
20
.github/workflows/ci.yaml
vendored
20
.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.0
|
uses: actions/checkout@v4.1.1
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3.8.1
|
uses: actions/setup-node@v3.8.1
|
||||||
with:
|
with:
|
||||||
@ -55,7 +55,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.0
|
uses: actions/checkout@v4.1.1
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3.8.1
|
uses: actions/setup-node@v3.8.1
|
||||||
with:
|
with:
|
||||||
@ -73,7 +73,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.0
|
uses: actions/checkout@v4.1.1
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3.8.1
|
uses: actions/setup-node@v3.8.1
|
||||||
with:
|
with:
|
||||||
@ -85,13 +85,19 @@ jobs:
|
|||||||
run: ./node_modules/.bin/gulp build-app
|
run: ./node_modules/.bin/gulp build-app
|
||||||
env:
|
env:
|
||||||
IS_TEST: "true"
|
IS_TEST: "true"
|
||||||
|
- name: Upload bundle stats
|
||||||
|
uses: actions/upload-artifact@v3.1.3
|
||||||
|
with:
|
||||||
|
name: frontend-bundle-stats
|
||||||
|
path: build/stats/*.json
|
||||||
|
if-no-files-found: error
|
||||||
supervisor:
|
supervisor:
|
||||||
name: Build supervisor
|
name: Build supervisor
|
||||||
needs: [lint, test]
|
needs: [lint, test]
|
||||||
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.0
|
uses: actions/checkout@v4.1.1
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3.8.1
|
uses: actions/setup-node@v3.8.1
|
||||||
with:
|
with:
|
||||||
@ -103,3 +109,9 @@ jobs:
|
|||||||
run: ./node_modules/.bin/gulp build-hassio
|
run: ./node_modules/.bin/gulp build-hassio
|
||||||
env:
|
env:
|
||||||
IS_TEST: "true"
|
IS_TEST: "true"
|
||||||
|
- name: Upload bundle stats
|
||||||
|
uses: actions/upload-artifact@v3.1.3
|
||||||
|
with:
|
||||||
|
name: supervisor-bundle-stats
|
||||||
|
path: build/stats/*.json
|
||||||
|
if-no-files-found: error
|
||||||
|
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.0
|
uses: actions/checkout@v4.1.1
|
||||||
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.0
|
uses: actions/checkout@v4.1.1
|
||||||
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.0
|
uses: actions/checkout@v4.1.1
|
||||||
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.0
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3.8.1
|
uses: actions/setup-node@v3.8.1
|
||||||
|
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.0
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3.8.1
|
uses: actions/setup-node@v3.8.1
|
||||||
|
6
.github/workflows/nightly.yaml
vendored
6
.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.0
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
@ -57,14 +57,14 @@ jobs:
|
|||||||
run: tar -czvf translations.tar.gz translations
|
run: tar -czvf translations.tar.gz translations
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3.1.3
|
||||||
with:
|
with:
|
||||||
name: wheels
|
name: wheels
|
||||||
path: dist/home_assistant_frontend*.whl
|
path: dist/home_assistant_frontend*.whl
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload translations
|
- name: Upload translations
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3.1.3
|
||||||
with:
|
with:
|
||||||
name: translations
|
name: translations
|
||||||
path: translations.tar.gz
|
path: translations.tar.gz
|
||||||
|
25
.github/workflows/relative-ci.yaml
vendored
Normal file
25
.github/workflows/relative-ci.yaml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
name: RelativeCI
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: [CI]
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
upload:
|
||||||
|
name: Upload stats
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
bundle: [frontend, supervisor]
|
||||||
|
build: [modern, legacy]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Send bundle stats and build information to RelativeCI
|
||||||
|
uses: relative-ci/agent-action@v2.1.10
|
||||||
|
with:
|
||||||
|
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
||||||
|
token: ${{ github.token }}
|
||||||
|
artifactName: ${{ format('{0}-bundle-stats', matrix.bundle) }}
|
||||||
|
webpackStatsFile: ${{ format('{0}-{1}.json', matrix.bundle, matrix.build) }}
|
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.0
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- name: Verify version
|
- name: Verify version
|
||||||
uses: home-assistant/actions/helpers/verify-version@master
|
uses: home-assistant/actions/helpers/verify-version@master
|
||||||
@ -74,7 +74,7 @@ jobs:
|
|||||||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||||
|
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: home-assistant/wheels@2023.10.1
|
uses: home-assistant/wheels@2023.10.5
|
||||||
with:
|
with:
|
||||||
abi: cp311
|
abi: cp311
|
||||||
tag: musllinux_1_2
|
tag: musllinux_1_2
|
||||||
|
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.0
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- name: Upload Translations
|
- name: Upload Translations
|
||||||
run: |
|
run: |
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -47,3 +47,6 @@ src/cast/dev_const.ts
|
|||||||
|
|
||||||
# Home Assistant config
|
# Home Assistant config
|
||||||
/config/
|
/config/
|
||||||
|
|
||||||
|
# Jetbrains
|
||||||
|
/.idea/
|
||||||
|
File diff suppressed because one or more lines are too long
@ -8,4 +8,4 @@ plugins:
|
|||||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||||
spec: "@yarnpkg/plugin-interactive-tools"
|
spec: "@yarnpkg/plugin-interactive-tools"
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-3.6.3.cjs
|
yarnPath: .yarn/releases/yarn-3.6.4.cjs
|
||||||
|
@ -98,7 +98,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
|||||||
"@babel/preset-env",
|
"@babel/preset-env",
|
||||||
{
|
{
|
||||||
useBuiltIns: latestBuild ? false : "entry",
|
useBuiltIns: latestBuild ? false : "entry",
|
||||||
corejs: latestBuild ? false : { version: "3.32", proposals: true },
|
corejs: latestBuild ? false : { version: "3.33", proposals: true },
|
||||||
bugfixes: true,
|
bugfixes: true,
|
||||||
shippedProposals: true,
|
shippedProposals: true,
|
||||||
},
|
},
|
||||||
@ -149,7 +149,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
|||||||
sourceMaps: !isTestBuild,
|
sourceMaps: !isTestBuild,
|
||||||
});
|
});
|
||||||
|
|
||||||
const nameSuffix = (latestBuild) => (latestBuild ? "-latest" : "-es5");
|
const nameSuffix = (latestBuild) => (latestBuild ? "-modern" : "-legacy");
|
||||||
|
|
||||||
const outputPath = (outputRoot, latestBuild) =>
|
const outputPath = (outputRoot, latestBuild) =>
|
||||||
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
|
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
|
||||||
@ -183,7 +183,7 @@ const publicPath = (latestBuild, root = "") =>
|
|||||||
module.exports.config = {
|
module.exports.config = {
|
||||||
app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild, isWDS }) {
|
app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild, isWDS }) {
|
||||||
return {
|
return {
|
||||||
name: "app" + nameSuffix(latestBuild),
|
name: "frontend" + nameSuffix(latestBuild),
|
||||||
entry: {
|
entry: {
|
||||||
service_worker: "./src/entrypoints/service_worker.ts",
|
service_worker: "./src/entrypoints/service_worker.ts",
|
||||||
app: "./src/entrypoints/app.ts",
|
app: "./src/entrypoints/app.ts",
|
||||||
|
@ -4,6 +4,7 @@ import fs from "fs-extra";
|
|||||||
import gulp from "gulp";
|
import gulp from "gulp";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import paths from "../paths.cjs";
|
import paths from "../paths.cjs";
|
||||||
|
import env from "../env.cjs";
|
||||||
|
|
||||||
const npmPath = (...parts) =>
|
const npmPath = (...parts) =>
|
||||||
path.resolve(paths.polymer_dir, "node_modules", ...parts);
|
path.resolve(paths.polymer_dir, "node_modules", ...parts);
|
||||||
@ -62,6 +63,9 @@ function copyPolyfills(staticDir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function copyLoaderJS(staticDir) {
|
function copyLoaderJS(staticDir) {
|
||||||
|
if (!env.useRollup()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const staticPath = genStaticPath(staticDir);
|
const staticPath = genStaticPath(staticDir);
|
||||||
copyFileDir(npmPath("systemjs/dist/s.min.js"), staticPath("js"));
|
copyFileDir(npmPath("systemjs/dist/s.min.js"), staticPath("js"));
|
||||||
copyFileDir(npmPath("systemjs/dist/s.min.js.map"), staticPath("js"));
|
copyFileDir(npmPath("systemjs/dist/s.min.js.map"), staticPath("js"));
|
||||||
|
@ -1,51 +1,54 @@
|
|||||||
import { deleteSync } from "del";
|
import { deleteSync } from "del";
|
||||||
import { mkdir, readFile, writeFile } from "fs/promises";
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
||||||
import gulp from "gulp";
|
import gulp from "gulp";
|
||||||
import path from "path";
|
import { join, resolve } from "node:path";
|
||||||
import paths from "../paths.cjs";
|
import paths from "../paths.cjs";
|
||||||
|
|
||||||
const outDir = path.join(paths.build_dir, "locale-data");
|
const formatjsDir = join(paths.polymer_dir, "node_modules", "@formatjs");
|
||||||
|
const outDir = join(paths.build_dir, "locale-data");
|
||||||
|
|
||||||
const INTL_PACKAGES = {
|
const INTL_POLYFILLS = {
|
||||||
"intl-relativetimeformat": "RelativeTimeFormat",
|
|
||||||
"intl-datetimeformat": "DateTimeFormat",
|
"intl-datetimeformat": "DateTimeFormat",
|
||||||
"intl-numberformat": "NumberFormat",
|
|
||||||
"intl-displaynames": "DisplayNames",
|
"intl-displaynames": "DisplayNames",
|
||||||
"intl-listformat": "ListFormat",
|
"intl-listformat": "ListFormat",
|
||||||
|
"intl-numberformat": "NumberFormat",
|
||||||
|
"intl-relativetimeformat": "RelativeTimeFormat",
|
||||||
};
|
};
|
||||||
|
|
||||||
const convertToJSON = async (pkg, lang) => {
|
const convertToJSON = async (
|
||||||
|
pkg,
|
||||||
|
lang,
|
||||||
|
subDir = "locale-data",
|
||||||
|
addFunc = "__addLocaleData",
|
||||||
|
skipMissing = true
|
||||||
|
) => {
|
||||||
let localeData;
|
let localeData;
|
||||||
try {
|
try {
|
||||||
localeData = await readFile(
|
localeData = await readFile(
|
||||||
path.resolve(
|
join(formatjsDir, pkg, subDir, `${lang}.js`),
|
||||||
paths.polymer_dir,
|
|
||||||
`node_modules/@formatjs/${pkg}/locale-data/${lang}.js`
|
|
||||||
),
|
|
||||||
"utf-8"
|
"utf-8"
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Ignore if language is missing (i.e. not supported by @formatjs)
|
// Ignore if language is missing (i.e. not supported by @formatjs)
|
||||||
if (e.code === "ENOENT") {
|
if (e.code === "ENOENT" && skipMissing) {
|
||||||
|
console.warn(`Skipped missing data for language ${lang} from ${pkg}`);
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
// Convert to JSON
|
// Convert to JSON
|
||||||
const className = INTL_PACKAGES[pkg];
|
const obj = INTL_POLYFILLS[pkg];
|
||||||
localeData = localeData
|
const dataRegex = new RegExp(
|
||||||
.replace(
|
`Intl\\.${obj}\\.${addFunc}\\((?<data>.*)\\)`,
|
||||||
new RegExp(
|
"s"
|
||||||
`\\/\\*\\s*@generated\\s*\\*\\/\\s*\\/\\/\\s*prettier-ignore\\s*if\\s*\\(Intl\\.${className}\\s*&&\\s*typeof\\s*Intl\\.${className}\\.__addLocaleData\\s*===\\s*'function'\\)\\s*{\\s*Intl\\.${className}\\.__addLocaleData\\(`,
|
);
|
||||||
"im"
|
localeData = localeData.match(dataRegex)?.groups?.data;
|
||||||
),
|
if (!localeData) {
|
||||||
""
|
throw Error(`Failed to extract data for language ${lang} from ${pkg}`);
|
||||||
)
|
}
|
||||||
.replace(/\)\s*}/im, "");
|
|
||||||
// Parse to validate JSON, then stringify to minify
|
// Parse to validate JSON, then stringify to minify
|
||||||
localeData = JSON.stringify(JSON.parse(localeData));
|
localeData = JSON.stringify(JSON.parse(localeData));
|
||||||
await writeFile(path.join(outDir, `${pkg}/${lang}.json`), localeData);
|
await writeFile(join(outDir, `${pkg}/${lang}.json`), localeData);
|
||||||
};
|
};
|
||||||
|
|
||||||
gulp.task("clean-locale-data", async () => deleteSync([outDir]));
|
gulp.task("clean-locale-data", async () => deleteSync([outDir]));
|
||||||
@ -53,17 +56,27 @@ gulp.task("clean-locale-data", async () => deleteSync([outDir]));
|
|||||||
gulp.task("create-locale-data", async () => {
|
gulp.task("create-locale-data", async () => {
|
||||||
const translationMeta = JSON.parse(
|
const translationMeta = JSON.parse(
|
||||||
await readFile(
|
await readFile(
|
||||||
path.resolve(paths.translations_src, "translationMetadata.json"),
|
resolve(paths.translations_src, "translationMetadata.json"),
|
||||||
"utf-8"
|
"utf-8"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const conversions = [];
|
const conversions = [];
|
||||||
for (const pkg of Object.keys(INTL_PACKAGES)) {
|
for (const pkg of Object.keys(INTL_POLYFILLS)) {
|
||||||
await mkdir(path.join(outDir, pkg), { recursive: true });
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await mkdir(join(outDir, pkg), { recursive: true });
|
||||||
for (const lang of Object.keys(translationMeta)) {
|
for (const lang of Object.keys(translationMeta)) {
|
||||||
conversions.push(convertToJSON(pkg, lang));
|
conversions.push(convertToJSON(pkg, lang));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
conversions.push(
|
||||||
|
convertToJSON(
|
||||||
|
"intl-datetimeformat",
|
||||||
|
"add-all-tz",
|
||||||
|
".",
|
||||||
|
"__addTZData",
|
||||||
|
false
|
||||||
|
)
|
||||||
|
);
|
||||||
await Promise.all(conversions);
|
await Promise.all(conversions);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
import { createHash } from "crypto";
|
import { createHash } from "crypto";
|
||||||
import { deleteSync } from "del";
|
import { deleteSync } from "del";
|
||||||
import {
|
import { mkdirSync, readdirSync, readFileSync, renameSync } from "fs";
|
||||||
mkdirSync,
|
import { writeFile } from "node:fs/promises";
|
||||||
readdirSync,
|
|
||||||
readFileSync,
|
|
||||||
renameSync,
|
|
||||||
writeFile,
|
|
||||||
} from "fs";
|
|
||||||
import gulp from "gulp";
|
import gulp from "gulp";
|
||||||
import flatmap from "gulp-flatmap";
|
import flatmap from "gulp-flatmap";
|
||||||
import transform from "gulp-json-transform";
|
import transform from "gulp-json-transform";
|
||||||
@ -136,27 +131,23 @@ gulp.task("ensure-translations-build-dir", async () => {
|
|||||||
mkdirSync(workDir, { recursive: true });
|
mkdirSync(workDir, { recursive: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task("create-test-metadata", (cb) => {
|
gulp.task("create-test-metadata", () =>
|
||||||
writeFile(
|
env.isProdBuild()
|
||||||
workDir + "/testMetadata.json",
|
? Promise.resolve()
|
||||||
JSON.stringify({
|
: writeFile(
|
||||||
test: {
|
workDir + "/testMetadata.json",
|
||||||
nativeName: "Test",
|
JSON.stringify({ test: { nativeName: "Test" } })
|
||||||
},
|
)
|
||||||
}),
|
);
|
||||||
cb
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task(
|
gulp.task("create-test-translation", () =>
|
||||||
"create-test-translation",
|
env.isProdBuild()
|
||||||
gulp.series("create-test-metadata", () =>
|
? Promise.resolve()
|
||||||
gulp
|
: gulp
|
||||||
.src(path.join(paths.translations_src, "en.json"))
|
.src(path.join(paths.translations_src, "en.json"))
|
||||||
.pipe(transform((data, _file) => recursiveEmpty(data)))
|
.pipe(transform((data, _file) => recursiveEmpty(data)))
|
||||||
.pipe(rename("test.json"))
|
.pipe(rename("test.json"))
|
||||||
.pipe(gulp.dest(workDir))
|
.pipe(gulp.dest(workDir))
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -188,16 +179,11 @@ gulp.task("build-master-translation", () => {
|
|||||||
|
|
||||||
gulp.task("build-merged-translations", () =>
|
gulp.task("build-merged-translations", () =>
|
||||||
gulp
|
gulp
|
||||||
.src(
|
.src([
|
||||||
[
|
inFrontendDir + "/*.json",
|
||||||
inFrontendDir + "/*.json",
|
"!" + inFrontendDir + "/en.json",
|
||||||
"!" + inFrontendDir + "/en.json",
|
...(env.isProdBuild() ? [] : [workDir + "/test.json"]),
|
||||||
workDir + "/test.json",
|
])
|
||||||
],
|
|
||||||
{
|
|
||||||
allowEmpty: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
||||||
.pipe(
|
.pipe(
|
||||||
flatmap((stream, file) => {
|
flatmap((stream, file) => {
|
||||||
@ -377,14 +363,11 @@ gulp.task("build-translation-flatten-supervisor", () =>
|
|||||||
|
|
||||||
gulp.task("build-translation-write-metadata", () =>
|
gulp.task("build-translation-write-metadata", () =>
|
||||||
gulp
|
gulp
|
||||||
.src(
|
.src([
|
||||||
[
|
path.join(paths.translations_src, "translationMetadata.json"),
|
||||||
path.join(paths.translations_src, "translationMetadata.json"),
|
...(env.isProdBuild() ? [] : [workDir + "/testMetadata.json"]),
|
||||||
workDir + "/testMetadata.json",
|
workDir + "/translationFingerprints.json",
|
||||||
workDir + "/translationFingerprints.json",
|
])
|
||||||
],
|
|
||||||
{ allowEmpty: true }
|
|
||||||
)
|
|
||||||
.pipe(merge({}))
|
.pipe(merge({}))
|
||||||
.pipe(
|
.pipe(
|
||||||
transform((data) => {
|
transform((data) => {
|
||||||
@ -415,7 +398,7 @@ gulp.task("build-translation-write-metadata", () =>
|
|||||||
gulp.task(
|
gulp.task(
|
||||||
"create-translations",
|
"create-translations",
|
||||||
gulp.series(
|
gulp.series(
|
||||||
...(env.isProdBuild() ? [] : ["create-test-translation"]),
|
gulp.parallel("create-test-metadata", "create-test-translation"),
|
||||||
"build-master-translation",
|
"build-master-translation",
|
||||||
"build-merged-translations",
|
"build-merged-translations",
|
||||||
gulp.parallel(...splitTasks),
|
gulp.parallel(...splitTasks),
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
const { existsSync } = require("fs");
|
const { existsSync } = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const webpack = require("webpack");
|
const webpack = require("webpack");
|
||||||
|
const { StatsWriterPlugin } = require("webpack-stats-plugin");
|
||||||
|
const filterStats = require("@bundle-stats/plugin-webpack-filter").default;
|
||||||
const TerserPlugin = require("terser-webpack-plugin");
|
const TerserPlugin = require("terser-webpack-plugin");
|
||||||
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
||||||
const log = require("fancy-log");
|
const log = require("fancy-log");
|
||||||
@ -152,6 +154,15 @@ const createWebpackConfig = ({
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
!isProdBuild && new LogStartCompilePlugin(),
|
!isProdBuild && new LogStartCompilePlugin(),
|
||||||
|
isProdBuild &&
|
||||||
|
new StatsWriterPlugin({
|
||||||
|
filename: path.relative(
|
||||||
|
outputPath,
|
||||||
|
path.join(paths.build_dir, "stats", `${name}.json`)
|
||||||
|
),
|
||||||
|
stats: { assets: true, chunks: true, modules: true },
|
||||||
|
transform: (stats) => JSON.stringify(filterStats(stats)),
|
||||||
|
}),
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".ts", ".js", ".json"],
|
extensions: [".ts", ".js", ".json"],
|
||||||
@ -171,6 +182,8 @@ const createWebpackConfig = ({
|
|||||||
"@lit-labs/virtualizer/layouts/grid.js",
|
"@lit-labs/virtualizer/layouts/grid.js",
|
||||||
"@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver":
|
"@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver":
|
||||||
"@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver.js",
|
"@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver.js",
|
||||||
|
"@lit-labs/observers/resize-controller":
|
||||||
|
"@lit-labs/observers/resize-controller.js",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
@ -183,6 +196,7 @@ const createWebpackConfig = ({
|
|||||||
isProdBuild && !isStatsBuild ? "[id]-[contenthash].js" : "[name].js",
|
isProdBuild && !isStatsBuild ? "[id]-[contenthash].js" : "[name].js",
|
||||||
assetModuleFilename:
|
assetModuleFilename:
|
||||||
isProdBuild && !isStatsBuild ? "[id]-[contenthash][ext]" : "[id][ext]",
|
isProdBuild && !isStatsBuild ? "[id]-[contenthash][ext]" : "[id][ext]",
|
||||||
|
crossOriginLoading: "use-credentials",
|
||||||
hashFunction: "xxhash64",
|
hashFunction: "xxhash64",
|
||||||
hashDigest: "base64url",
|
hashDigest: "base64url",
|
||||||
hashDigestLength: 11, // full length of 64 bit base64url
|
hashDigestLength: 11, // full length of 64 bit base64url
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import "../../../src/resources/safari-14-attachshadow-patch";
|
import "../../../src/resources/safari-14-attachshadow-patch";
|
||||||
import "../../../src/resources/ha-style";
|
|
||||||
import "../../../src/resources/roboto";
|
|
||||||
import "./layout/hc-connect";
|
import "./layout/hc-connect";
|
||||||
|
|
||||||
|
import("../../../src/resources/ha-style");
|
||||||
|
@ -100,7 +100,9 @@ export class HcMain extends HassElement {
|
|||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
protected firstUpdated(changedProps) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
import("../second-load");
|
import("./hc-lovelace");
|
||||||
|
import("../../../../src/resources/ha-style");
|
||||||
|
|
||||||
window.addEventListener("location-changed", () => {
|
window.addEventListener("location-changed", () => {
|
||||||
const panelPath = `/${this._urlPath || "lovelace"}/`;
|
const panelPath = `/${this._urlPath || "lovelace"}/`;
|
||||||
if (location.pathname.startsWith(panelPath)) {
|
if (location.pathname.startsWith(panelPath)) {
|
||||||
@ -260,7 +262,6 @@ export class HcMain extends HassElement {
|
|||||||
{
|
{
|
||||||
strategy: {
|
strategy: {
|
||||||
type: "energy",
|
type: "energy",
|
||||||
show_date_selection: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -308,7 +309,7 @@ export class HcMain extends HassElement {
|
|||||||
? await fetchResources(this.hass!.connection)
|
? await fetchResources(this.hass!.connection)
|
||||||
: (this._lovelaceConfig as LegacyLovelaceConfig).resources;
|
: (this._lovelaceConfig as LegacyLovelaceConfig).resources;
|
||||||
if (resources) {
|
if (resources) {
|
||||||
loadLovelaceResources(resources, this.hass!.auth.data.hassUrl);
|
loadLovelaceResources(resources, this.hass!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,8 +325,7 @@ export class HcMain extends HassElement {
|
|||||||
{
|
{
|
||||||
type: DEFAULT_STRATEGY,
|
type: DEFAULT_STRATEGY,
|
||||||
},
|
},
|
||||||
this.hass!,
|
this.hass!
|
||||||
{ narrow: false }
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
import "../../../src/resources/ha-style";
|
|
||||||
import "../../../src/resources/roboto";
|
|
||||||
import "./layout/hc-lovelace";
|
|
@ -3,6 +3,15 @@ import { DemoConfig } from "../types";
|
|||||||
|
|
||||||
export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
|
export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
|
||||||
convertEntities({
|
convertEntities({
|
||||||
|
"todo.shopping_list": {
|
||||||
|
entity_id: "todo.shopping_list",
|
||||||
|
state: "2",
|
||||||
|
attributes: {
|
||||||
|
supported_features: 15,
|
||||||
|
friendly_name: "Shopping List",
|
||||||
|
icon: "mdi:cart",
|
||||||
|
},
|
||||||
|
},
|
||||||
"zone.home": {
|
"zone.home": {
|
||||||
entity_id: "zone.home",
|
entity_id: "zone.home",
|
||||||
state: "zoning",
|
state: "zoning",
|
||||||
|
@ -3,6 +3,15 @@ import { DemoConfig } from "../types";
|
|||||||
|
|
||||||
export const demoEntitiesJimpower: DemoConfig["entities"] = () =>
|
export const demoEntitiesJimpower: DemoConfig["entities"] = () =>
|
||||||
convertEntities({
|
convertEntities({
|
||||||
|
"todo.shopping_list": {
|
||||||
|
entity_id: "todo.shopping_list",
|
||||||
|
state: "2",
|
||||||
|
attributes: {
|
||||||
|
supported_features: 15,
|
||||||
|
friendly_name: "Shopping List",
|
||||||
|
icon: "mdi:cart",
|
||||||
|
},
|
||||||
|
},
|
||||||
"zone.powertec": {
|
"zone.powertec": {
|
||||||
entity_id: "zone.powertec",
|
entity_id: "zone.powertec",
|
||||||
state: "zoning",
|
state: "zoning",
|
||||||
|
@ -4,16 +4,11 @@ export const demoThemeJimpower = () => ({
|
|||||||
"primary-color": "#5294E2",
|
"primary-color": "#5294E2",
|
||||||
"label-badge-red": "var(--accent-color)",
|
"label-badge-red": "var(--accent-color)",
|
||||||
"paper-tabs-selection-bar-color": "green",
|
"paper-tabs-selection-bar-color": "green",
|
||||||
"paper-slider-knob-color": "var(--accent-color)",
|
|
||||||
"light-primary-color": "var(--accent-color)",
|
"light-primary-color": "var(--accent-color)",
|
||||||
"primary-background-color": "#383C45",
|
"primary-background-color": "#383C45",
|
||||||
"primary-text-color": "#FFFFFF",
|
"primary-text-color": "#FFFFFF",
|
||||||
"paper-item-selected_-_background-color": "#434954",
|
"paper-item-selected_-_background-color": "#434954",
|
||||||
"paper-slider-active-color": "var(--accent-color)",
|
|
||||||
"secondary-background-color": "#383C45",
|
"secondary-background-color": "#383C45",
|
||||||
"paper-slider-container-color":
|
|
||||||
"linear-gradient(var(--primary-background-color), var(--secondary-background-color)) no-repeat",
|
|
||||||
"paper-slider-disabled-active-color": "var(--disabled-text-color)",
|
|
||||||
"disabled-text-color": "#7F848E",
|
"disabled-text-color": "#7F848E",
|
||||||
"paper-item-icon_-_color": "green",
|
"paper-item-icon_-_color": "green",
|
||||||
"paper-grey-200": "#414A59",
|
"paper-grey-200": "#414A59",
|
||||||
@ -32,14 +27,10 @@ export const demoThemeJimpower = () => ({
|
|||||||
"switch-unchecked-button-color": "var(--disabled-text-color)",
|
"switch-unchecked-button-color": "var(--disabled-text-color)",
|
||||||
"label-badge-border-color": "green",
|
"label-badge-border-color": "green",
|
||||||
"paper-listbox-color": "var(--primary-color)",
|
"paper-listbox-color": "var(--primary-color)",
|
||||||
"paper-slider-disabled-secondary-color": "var(--disabled-text-color)",
|
|
||||||
"card-background-color": "#434954",
|
"card-background-color": "#434954",
|
||||||
"label-badge-text-color": "var(--primary-text-color)",
|
"label-badge-text-color": "var(--primary-text-color)",
|
||||||
"paper-slider-knob-start-color": "var(--accent-color)",
|
|
||||||
"switch-unchecked-track-color": "var(--disabled-text-color)",
|
"switch-unchecked-track-color": "var(--disabled-text-color)",
|
||||||
"dark-primary-color": "var(--accent-color)",
|
"dark-primary-color": "var(--accent-color)",
|
||||||
"paper-slider-secondary-color": "var(--secondary-background-color)",
|
|
||||||
"paper-slider-pin-color": "var(--accent-color)",
|
|
||||||
"paper-item-icon-active-color": "#F9C536",
|
"paper-item-icon-active-color": "#F9C536",
|
||||||
"accent-color": "#E45E65",
|
"accent-color": "#E45E65",
|
||||||
"table-row-alternative-background-color": "#3E424B",
|
"table-row-alternative-background-color": "#3E424B",
|
||||||
|
@ -3,6 +3,15 @@ import { DemoConfig } from "../types";
|
|||||||
|
|
||||||
export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
||||||
convertEntities({
|
convertEntities({
|
||||||
|
"todo.shopping_list": {
|
||||||
|
entity_id: "todo.shopping_list",
|
||||||
|
state: "2",
|
||||||
|
attributes: {
|
||||||
|
supported_features: 15,
|
||||||
|
friendly_name: "Shopping List",
|
||||||
|
icon: "mdi:cart",
|
||||||
|
},
|
||||||
|
},
|
||||||
"zone.anna": {
|
"zone.anna": {
|
||||||
entity_id: "zone.anna",
|
entity_id: "zone.anna",
|
||||||
state: "zoning",
|
state: "zoning",
|
||||||
|
@ -5,17 +5,12 @@ export const demoThemeKernehed = () => ({
|
|||||||
"primary-color": "#2980b9",
|
"primary-color": "#2980b9",
|
||||||
"label-badge-red": "var(--accent-color)",
|
"label-badge-red": "var(--accent-color)",
|
||||||
"paper-tabs-selection-bar-color": "green",
|
"paper-tabs-selection-bar-color": "green",
|
||||||
"paper-slider-knob-color": "var(--accent-color)",
|
|
||||||
"primary-text-color": "#FFFFFF",
|
"primary-text-color": "#FFFFFF",
|
||||||
"light-primary-color": "var(--accent-color)",
|
"light-primary-color": "var(--accent-color)",
|
||||||
"primary-background-color": "#222222",
|
"primary-background-color": "#222222",
|
||||||
"sidebar-icon-color": "#777777",
|
"sidebar-icon-color": "#777777",
|
||||||
"paper-item-selected_-_background-color": "#292929",
|
"paper-item-selected_-_background-color": "#292929",
|
||||||
"paper-slider-active-color": "var(--accent-color)",
|
|
||||||
"secondary-background-color": "#222222",
|
"secondary-background-color": "#222222",
|
||||||
"paper-slider-container-color":
|
|
||||||
"linear-gradient(var(--primary-background-color), var(--secondary-background-color)) no-repeat",
|
|
||||||
"paper-slider-disabled-active-color": "var(--disabled-text-color)",
|
|
||||||
"disabled-text-color": "#777777",
|
"disabled-text-color": "#777777",
|
||||||
"paper-item-icon_-_color": "green",
|
"paper-item-icon_-_color": "green",
|
||||||
"paper-grey-200": "#222222",
|
"paper-grey-200": "#222222",
|
||||||
@ -33,14 +28,10 @@ export const demoThemeKernehed = () => ({
|
|||||||
"switch-unchecked-button-color": "var(--disabled-text-color)",
|
"switch-unchecked-button-color": "var(--disabled-text-color)",
|
||||||
"label-badge-border-color": "green",
|
"label-badge-border-color": "green",
|
||||||
"paper-listbox-color": "#777777",
|
"paper-listbox-color": "#777777",
|
||||||
"paper-slider-disabled-secondary-color": "var(--disabled-text-color)",
|
|
||||||
"card-background-color": "#292929",
|
"card-background-color": "#292929",
|
||||||
"label-badge-text-color": "var(--primary-text-color)",
|
"label-badge-text-color": "var(--primary-text-color)",
|
||||||
"paper-slider-knob-start-color": "var(--accent-color)",
|
|
||||||
"switch-unchecked-track-color": "var(--disabled-text-color)",
|
"switch-unchecked-track-color": "var(--disabled-text-color)",
|
||||||
"dark-primary-color": "var(--accent-color)",
|
"dark-primary-color": "var(--accent-color)",
|
||||||
"paper-slider-secondary-color": "var(--secondary-background-color)",
|
|
||||||
"paper-slider-pin-color": "var(--accent-color)",
|
|
||||||
"paper-item-icon-active-color": "#b58e31",
|
"paper-item-icon-active-color": "#b58e31",
|
||||||
"accent-color": "#2980b9",
|
"accent-color": "#2980b9",
|
||||||
"table-row-alternative-background-color": "#292929",
|
"table-row-alternative-background-color": "#292929",
|
||||||
|
@ -3,6 +3,15 @@ import { DemoConfig } from "../types";
|
|||||||
|
|
||||||
export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
|
export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
|
||||||
convertEntities({
|
convertEntities({
|
||||||
|
"todo.shopping_list": {
|
||||||
|
entity_id: "todo.shopping_list",
|
||||||
|
state: "2",
|
||||||
|
attributes: {
|
||||||
|
supported_features: 15,
|
||||||
|
friendly_name: "Shopping List",
|
||||||
|
icon: "mdi:cart",
|
||||||
|
},
|
||||||
|
},
|
||||||
"sensor.pollen_grabo": {
|
"sensor.pollen_grabo": {
|
||||||
entity_id: "sensor.pollen_grabo",
|
entity_id: "sensor.pollen_grabo",
|
||||||
state: "",
|
state: "",
|
||||||
|
@ -220,7 +220,8 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
|||||||
state_filter: ["on"],
|
state_filter: ["on"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "shopping-list",
|
type: "todo-list",
|
||||||
|
entity: "todo.shopping_list",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
entities: [
|
entities: [
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
export const demoThemeTeachingbirds = () => ({
|
export const demoThemeTeachingbirds = () => ({
|
||||||
"paper-card-header-color": "var(--paper-item-icon-color)",
|
"paper-card-header-color": "var(--paper-item-icon-color)",
|
||||||
"paper-slider-pin-color": "var(--primary-color)",
|
|
||||||
"paper-listbox-background-color": "#202020",
|
"paper-listbox-background-color": "#202020",
|
||||||
"paper-grey-50": "var(--primary-text-color)",
|
"paper-grey-50": "var(--primary-text-color)",
|
||||||
"paper-item-icon-color": "#d3d3d3",
|
"paper-item-icon-color": "#d3d3d3",
|
||||||
@ -8,8 +7,6 @@ export const demoThemeTeachingbirds = () => ({
|
|||||||
"primary-color": "#389638",
|
"primary-color": "#389638",
|
||||||
"light-primary-color": "#6f956f",
|
"light-primary-color": "#6f956f",
|
||||||
"label-badge-red": "var(--primary-color)",
|
"label-badge-red": "var(--primary-color)",
|
||||||
"paper-slider-secondary-color": "var(--light-primary-color)",
|
|
||||||
"paper-slider-knob-color": "var(--primary-color)",
|
|
||||||
"paper-listbox-color": "#FFFFFF",
|
"paper-listbox-color": "#FFFFFF",
|
||||||
"paper-toggle-button-checked-bar-color": "var(--light-primary-color)",
|
"paper-toggle-button-checked-bar-color": "var(--light-primary-color)",
|
||||||
"switch-unchecked-track-color": "var(--primary-text-color)",
|
"switch-unchecked-track-color": "var(--primary-text-color)",
|
||||||
@ -17,9 +14,7 @@ export const demoThemeTeachingbirds = () => ({
|
|||||||
"label-badge-text-color": "var(--text-primary-color)",
|
"label-badge-text-color": "var(--text-primary-color)",
|
||||||
"primary-background-color": "#303030",
|
"primary-background-color": "#303030",
|
||||||
"sidebar-icon-color": "var(--paper-item-icon-color)",
|
"sidebar-icon-color": "var(--paper-item-icon-color)",
|
||||||
"paper-slider-active-color": "#d8bf50",
|
|
||||||
"secondary-background-color": "#2b2b2b",
|
"secondary-background-color": "#2b2b2b",
|
||||||
"paper-slider-knob-start-color": "var(--primary-color)",
|
|
||||||
"paper-item-icon-active-color": "#d8bf50",
|
"paper-item-icon-active-color": "#d8bf50",
|
||||||
"switch-checked-color": "var(--primary-color)",
|
"switch-checked-color": "var(--primary-color)",
|
||||||
"secondary-text-color": "#389638",
|
"secondary-text-color": "#389638",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import "../../src/resources/ha-style";
|
|
||||||
import "../../src/resources/roboto";
|
|
||||||
import "../../src/resources/safari-14-attachshadow-patch";
|
import "../../src/resources/safari-14-attachshadow-patch";
|
||||||
import "./ha-demo";
|
import "./ha-demo";
|
||||||
|
|
||||||
|
import("../../src/resources/ha-style");
|
||||||
|
@ -22,7 +22,7 @@ import { mockLovelace } from "./stubs/lovelace";
|
|||||||
import { mockMediaPlayer } from "./stubs/media_player";
|
import { mockMediaPlayer } from "./stubs/media_player";
|
||||||
import { mockPersistentNotification } from "./stubs/persistent_notification";
|
import { mockPersistentNotification } from "./stubs/persistent_notification";
|
||||||
import { mockRecorder } from "./stubs/recorder";
|
import { mockRecorder } from "./stubs/recorder";
|
||||||
import { mockShoppingList } from "./stubs/shopping_list";
|
import { mockTodo } from "./stubs/todo";
|
||||||
import { mockSystemLog } from "./stubs/system_log";
|
import { mockSystemLog } from "./stubs/system_log";
|
||||||
import { mockTemplate } from "./stubs/template";
|
import { mockTemplate } from "./stubs/template";
|
||||||
import { mockTranslations } from "./stubs/translations";
|
import { mockTranslations } from "./stubs/translations";
|
||||||
@ -49,7 +49,7 @@ export class HaDemo extends HomeAssistantAppEl {
|
|||||||
mockTranslations(hass);
|
mockTranslations(hass);
|
||||||
mockHistory(hass);
|
mockHistory(hass);
|
||||||
mockRecorder(hass);
|
mockRecorder(hass);
|
||||||
mockShoppingList(hass);
|
mockTodo(hass);
|
||||||
mockSystemLog(hass);
|
mockSystemLog(hass);
|
||||||
mockTemplate(hass);
|
mockTemplate(hass);
|
||||||
mockEvents(hass);
|
mockEvents(hass);
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
import { ShoppingListItem } from "../../../src/data/shopping-list";
|
|
||||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
|
||||||
|
|
||||||
let items: ShoppingListItem[] = [
|
|
||||||
{
|
|
||||||
id: 12,
|
|
||||||
name: "Milk",
|
|
||||||
complete: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 13,
|
|
||||||
name: "Eggs",
|
|
||||||
complete: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 14,
|
|
||||||
name: "Oranges",
|
|
||||||
complete: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const mockShoppingList = (hass: MockHomeAssistant) => {
|
|
||||||
hass.mockWS("shopping_list/items", () => items);
|
|
||||||
hass.mockWS("shopping_list/items/add", (msg) => {
|
|
||||||
const item: ShoppingListItem = {
|
|
||||||
id: new Date().getTime(),
|
|
||||||
complete: false,
|
|
||||||
name: msg.name,
|
|
||||||
};
|
|
||||||
items.push(item);
|
|
||||||
hass.mockEvent("shopping_list_updated");
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
hass.mockWS("shopping_list/items/update", ({ type, item_id, ...updates }) => {
|
|
||||||
items = items.map((item) =>
|
|
||||||
item.id === item_id ? { ...item, ...updates } : item
|
|
||||||
);
|
|
||||||
hass.mockEvent("shopping_list_updated");
|
|
||||||
});
|
|
||||||
hass.mockWS("shopping_list/items/clear", () => {
|
|
||||||
items = items.filter((item) => !item.complete);
|
|
||||||
hass.mockEvent("shopping_list_updated");
|
|
||||||
});
|
|
||||||
};
|
|
24
demo/src/stubs/todo.ts
Normal file
24
demo/src/stubs/todo.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { TodoItem, TodoItemStatus } from "../../../src/data/todo";
|
||||||
|
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
|
export const mockTodo = (hass: MockHomeAssistant) => {
|
||||||
|
hass.mockWS("todo/item/list", () => ({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
uid: "12",
|
||||||
|
summary: "Milk",
|
||||||
|
status: TodoItemStatus.NeedsAction,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uid: "13",
|
||||||
|
summary: "Eggs",
|
||||||
|
status: TodoItemStatus.NeedsAction,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uid: "14",
|
||||||
|
summary: "Oranges",
|
||||||
|
status: TodoItemStatus.Completed,
|
||||||
|
},
|
||||||
|
] as TodoItem[],
|
||||||
|
}));
|
||||||
|
};
|
@ -1,5 +1,5 @@
|
|||||||
import "../../src/resources/ha-style";
|
|
||||||
import "../../src/resources/roboto";
|
|
||||||
import "./ha-gallery";
|
import "./ha-gallery";
|
||||||
|
|
||||||
|
import("../../src/resources/ha-style");
|
||||||
|
|
||||||
document.body.appendChild(document.createElement("ha-gallery"));
|
document.body.appendChild(document.createElement("ha-gallery"));
|
||||||
|
@ -49,11 +49,11 @@ export class DemoHaCircularSlider extends LitElement {
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<p>Current</p>
|
<p>Current</p>
|
||||||
<ha-slider
|
<ha-slider
|
||||||
|
labeled
|
||||||
min="10"
|
min="10"
|
||||||
max="30"
|
max="30"
|
||||||
.value=${this.current}
|
.value=${this.current}
|
||||||
@change=${this._currentChanged}
|
@change=${this._currentChanged}
|
||||||
pin
|
|
||||||
></ha-slider>
|
></ha-slider>
|
||||||
<p>${this.current} °C</p>
|
<p>${this.current} °C</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,6 +57,7 @@ const DEVICES = [
|
|||||||
sw_version: null,
|
sw_version: null,
|
||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
|
serial_number: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "backyard",
|
area_id: "backyard",
|
||||||
@ -74,6 +75,7 @@ const DEVICES = [
|
|||||||
sw_version: null,
|
sw_version: null,
|
||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
|
serial_number: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: null,
|
area_id: null,
|
||||||
@ -91,6 +93,7 @@ const DEVICES = [
|
|||||||
sw_version: null,
|
sw_version: null,
|
||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
|
serial_number: null,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -57,8 +57,8 @@ export class DemoHaHsColorPicker extends LitElement {
|
|||||||
></ha-hs-color-picker>
|
></ha-hs-color-picker>
|
||||||
<p>Hue : ${this.value[0]}</p>
|
<p>Hue : ${this.value[0]}</p>
|
||||||
<ha-slider
|
<ha-slider
|
||||||
|
labeled
|
||||||
step="1"
|
step="1"
|
||||||
pin
|
|
||||||
min="0"
|
min="0"
|
||||||
max="360"
|
max="360"
|
||||||
.value=${this.value[0]}
|
.value=${this.value[0]}
|
||||||
@ -67,8 +67,8 @@ export class DemoHaHsColorPicker extends LitElement {
|
|||||||
</ha-slider>
|
</ha-slider>
|
||||||
<p>Saturation : ${this.value[1]}</p>
|
<p>Saturation : ${this.value[1]}</p>
|
||||||
<ha-slider
|
<ha-slider
|
||||||
|
labeled
|
||||||
step="0.01"
|
step="0.01"
|
||||||
pin
|
|
||||||
min="0"
|
min="0"
|
||||||
max="1"
|
max="1"
|
||||||
.value=${this.value[1]}
|
.value=${this.value[1]}
|
||||||
@ -77,8 +77,8 @@ export class DemoHaHsColorPicker extends LitElement {
|
|||||||
</ha-slider>
|
</ha-slider>
|
||||||
<p>Color Brighness : ${this.brightness}</p>
|
<p>Color Brighness : ${this.brightness}</p>
|
||||||
<ha-slider
|
<ha-slider
|
||||||
|
labeled
|
||||||
step="1"
|
step="1"
|
||||||
pin
|
|
||||||
min="0"
|
min="0"
|
||||||
max="255"
|
max="255"
|
||||||
.value=${this.brightness}
|
.value=${this.brightness}
|
||||||
|
@ -53,6 +53,7 @@ const DEVICES = [
|
|||||||
sw_version: null,
|
sw_version: null,
|
||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
|
serial_number: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "backyard",
|
area_id: "backyard",
|
||||||
@ -70,6 +71,7 @@ const DEVICES = [
|
|||||||
sw_version: null,
|
sw_version: null,
|
||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
|
serial_number: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: null,
|
area_id: null,
|
||||||
@ -87,6 +89,7 @@ const DEVICES = [
|
|||||||
sw_version: null,
|
sw_version: null,
|
||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
|
serial_number: null,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
title: Shopping List Card
|
|
||||||
---
|
|
3
gallery/src/pages/lovelace/todo-list-card.markdown
Normal file
3
gallery/src/pages/lovelace/todo-list-card.markdown
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: Todo List Card
|
||||||
|
---
|
@ -2,25 +2,39 @@ import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
|||||||
import { customElement, query } from "lit/decorators";
|
import { customElement, query } from "lit/decorators";
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
import "../../components/demo-cards";
|
import "../../components/demo-cards";
|
||||||
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
|
import { mockTodo } from "../../../../demo/src/stubs/todo";
|
||||||
|
|
||||||
|
const ENTITIES = [
|
||||||
|
getEntity("todo", "shopping_list", "2", {
|
||||||
|
friendly_name: "Shopping List",
|
||||||
|
supported_features: 15,
|
||||||
|
}),
|
||||||
|
getEntity("todo", "read_only", "2", {
|
||||||
|
friendly_name: "Read only",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
const CONFIGS = [
|
const CONFIGS = [
|
||||||
{
|
{
|
||||||
heading: "List example",
|
heading: "List example",
|
||||||
config: `
|
config: `
|
||||||
- type: shopping-list
|
- type: todo-list
|
||||||
|
entity: todo.shopping_list
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "List with title example",
|
heading: "List with title example",
|
||||||
config: `
|
config: `
|
||||||
- type: shopping-list
|
- type: todo-list
|
||||||
title: Shopping List
|
title: Shopping List
|
||||||
|
entity: todo.read_only
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("demo-lovelace-shopping-list-card")
|
@customElement("demo-lovelace-todo-list-card")
|
||||||
class DemoShoppingListEntity extends LitElement {
|
class DemoTodoListEntity extends LitElement {
|
||||||
@query("#demos") private _demoRoot!: HTMLElement;
|
@query("#demos") private _demoRoot!: HTMLElement;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
@ -32,18 +46,14 @@ class DemoShoppingListEntity extends LitElement {
|
|||||||
const hass = provideHass(this._demoRoot);
|
const hass = provideHass(this._demoRoot);
|
||||||
hass.updateTranslations(null, "en");
|
hass.updateTranslations(null, "en");
|
||||||
hass.updateTranslations("lovelace", "en");
|
hass.updateTranslations("lovelace", "en");
|
||||||
|
hass.addEntities(ENTITIES);
|
||||||
|
|
||||||
hass.mockAPI("shopping_list", () => [
|
mockTodo(hass);
|
||||||
{ name: "list", id: 1, complete: false },
|
|
||||||
{ name: "all", id: 2, complete: false },
|
|
||||||
{ name: "the", id: 3, complete: false },
|
|
||||||
{ name: "things", id: 4, complete: true },
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"demo-lovelace-shopping-list-card": DemoShoppingListEntity;
|
"demo-lovelace-todo-list-card": DemoTodoListEntity;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -213,6 +213,7 @@ const createDeviceRegistryEntries = (
|
|||||||
name: "Tag Reader",
|
name: "Tag Reader",
|
||||||
sw_version: null,
|
sw_version: null,
|
||||||
hw_version: "1.0.0",
|
hw_version: "1.0.0",
|
||||||
|
serial_number: "00_12_4B_00_22_98_88_7F",
|
||||||
id: "mock-device-id",
|
id: "mock-device-id",
|
||||||
identifiers: [],
|
identifiers: [],
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { mdiClose } from "@mdi/js";
|
import { mdiClose } from "@mdi/js";
|
||||||
|
import { dump } from "js-yaml";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@ -9,7 +10,6 @@ import "../../../../src/components/ha-expansion-panel";
|
|||||||
import "../../../../src/components/ha-icon-button";
|
import "../../../../src/components/ha-icon-button";
|
||||||
import "../../../../src/components/search-input";
|
import "../../../../src/components/search-input";
|
||||||
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
|
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
|
||||||
import { dump } from "../../../../src/resources/js-yaml-dump";
|
|
||||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||||
import { HomeAssistant } from "../../../../src/types";
|
import { HomeAssistant } from "../../../../src/types";
|
||||||
import { HassioHardwareDialogParams } from "./show-dialog-hassio-hardware";
|
import { HassioHardwareDialogParams } from "./show-dialog-hassio-hardware";
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
// Compat needs to be first import
|
// Compat needs to be first import
|
||||||
import "../../src/resources/compatibility";
|
import "../../src/resources/compatibility";
|
||||||
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
|
|
||||||
import "../../src/resources/roboto";
|
|
||||||
import "../../src/resources/ha-style";
|
|
||||||
import "../../src/resources/safari-14-attachshadow-patch";
|
import "../../src/resources/safari-14-attachshadow-patch";
|
||||||
import "./hassio-main";
|
import "./hassio-main";
|
||||||
|
|
||||||
setCancelSyntheticClickEvents(false);
|
import("../../src/resources/ha-style");
|
||||||
|
import("@polymer/polymer/lib/utils/settings").then(
|
||||||
|
({ setCancelSyntheticClickEvents }) => setCancelSyntheticClickEvents(false)
|
||||||
|
);
|
||||||
|
|
||||||
const styleEl = document.createElement("style");
|
const styleEl = document.createElement("style");
|
||||||
styleEl.innerHTML = `
|
styleEl.textContent = `
|
||||||
body {
|
body {
|
||||||
font-family: Roboto, sans-serif;
|
font-family: Roboto, sans-serif;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
117
package.json
117
package.json
@ -25,24 +25,24 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.23.1",
|
"@babel/runtime": "7.23.2",
|
||||||
"@braintree/sanitize-url": "6.0.4",
|
"@braintree/sanitize-url": "6.0.4",
|
||||||
"@codemirror/autocomplete": "6.9.1",
|
"@codemirror/autocomplete": "6.10.2",
|
||||||
"@codemirror/commands": "6.3.0",
|
"@codemirror/commands": "6.3.0",
|
||||||
"@codemirror/language": "6.9.1",
|
"@codemirror/language": "6.9.1",
|
||||||
"@codemirror/legacy-modes": "6.3.3",
|
"@codemirror/legacy-modes": "6.3.3",
|
||||||
"@codemirror/search": "6.5.4",
|
"@codemirror/search": "6.5.4",
|
||||||
"@codemirror/state": "6.2.1",
|
"@codemirror/state": "6.3.1",
|
||||||
"@codemirror/view": "6.21.1",
|
"@codemirror/view": "6.21.3",
|
||||||
"@egjs/hammerjs": "2.0.17",
|
"@egjs/hammerjs": "2.0.17",
|
||||||
"@formatjs/intl-datetimeformat": "6.10.3",
|
"@formatjs/intl-datetimeformat": "6.11.0",
|
||||||
"@formatjs/intl-displaynames": "6.5.2",
|
"@formatjs/intl-displaynames": "6.6.0",
|
||||||
"@formatjs/intl-getcanonicallocales": "2.2.1",
|
"@formatjs/intl-getcanonicallocales": "2.3.0",
|
||||||
"@formatjs/intl-listformat": "7.4.2",
|
"@formatjs/intl-listformat": "7.5.0",
|
||||||
"@formatjs/intl-locale": "3.3.4",
|
"@formatjs/intl-locale": "3.4.0",
|
||||||
"@formatjs/intl-numberformat": "8.7.2",
|
"@formatjs/intl-numberformat": "8.8.0",
|
||||||
"@formatjs/intl-pluralrules": "5.2.6",
|
"@formatjs/intl-pluralrules": "5.2.7",
|
||||||
"@formatjs/intl-relativetimeformat": "11.2.6",
|
"@formatjs/intl-relativetimeformat": "11.2.7",
|
||||||
"@fullcalendar/core": "6.1.9",
|
"@fullcalendar/core": "6.1.9",
|
||||||
"@fullcalendar/daygrid": "6.1.9",
|
"@fullcalendar/daygrid": "6.1.9",
|
||||||
"@fullcalendar/interaction": "6.1.9",
|
"@fullcalendar/interaction": "6.1.9",
|
||||||
@ -52,10 +52,12 @@
|
|||||||
"@lezer/highlight": "1.1.6",
|
"@lezer/highlight": "1.1.6",
|
||||||
"@lit-labs/context": "0.4.1",
|
"@lit-labs/context": "0.4.1",
|
||||||
"@lit-labs/motion": "1.0.4",
|
"@lit-labs/motion": "1.0.4",
|
||||||
|
"@lit-labs/observers": "2.0.1",
|
||||||
"@lit-labs/virtualizer": "2.0.7",
|
"@lit-labs/virtualizer": "2.0.7",
|
||||||
"@lrnwebcomponents/simple-tooltip": "7.0.18",
|
"@lrnwebcomponents/simple-tooltip": "7.0.18",
|
||||||
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||||
|
"@material/mwc-base": "0.27.0",
|
||||||
"@material/mwc-button": "0.27.0",
|
"@material/mwc-button": "0.27.0",
|
||||||
"@material/mwc-checkbox": "0.27.0",
|
"@material/mwc-checkbox": "0.27.0",
|
||||||
"@material/mwc-circular-progress": "0.27.0",
|
"@material/mwc-circular-progress": "0.27.0",
|
||||||
@ -71,7 +73,6 @@
|
|||||||
"@material/mwc-radio": "0.27.0",
|
"@material/mwc-radio": "0.27.0",
|
||||||
"@material/mwc-ripple": "0.27.0",
|
"@material/mwc-ripple": "0.27.0",
|
||||||
"@material/mwc-select": "0.27.0",
|
"@material/mwc-select": "0.27.0",
|
||||||
"@material/mwc-slider": "0.27.0",
|
|
||||||
"@material/mwc-switch": "0.27.0",
|
"@material/mwc-switch": "0.27.0",
|
||||||
"@material/mwc-tab": "0.27.0",
|
"@material/mwc-tab": "0.27.0",
|
||||||
"@material/mwc-tab-bar": "0.27.0",
|
"@material/mwc-tab-bar": "0.27.0",
|
||||||
@ -80,22 +81,21 @@
|
|||||||
"@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.0.0",
|
"@material/web": "=1.0.1",
|
||||||
"@mdi/js": "7.2.96",
|
"@mdi/js": "7.3.67",
|
||||||
"@mdi/svg": "7.2.96",
|
"@mdi/svg": "7.3.67",
|
||||||
"@polymer/iron-flex-layout": "3.0.1",
|
"@polymer/iron-flex-layout": "3.0.1",
|
||||||
"@polymer/iron-input": "3.0.1",
|
"@polymer/iron-input": "3.0.1",
|
||||||
"@polymer/iron-resizable-behavior": "3.0.1",
|
"@polymer/iron-resizable-behavior": "3.0.1",
|
||||||
"@polymer/paper-input": "3.2.1",
|
"@polymer/paper-input": "3.2.1",
|
||||||
"@polymer/paper-item": "3.0.1",
|
"@polymer/paper-item": "3.0.1",
|
||||||
"@polymer/paper-listbox": "3.0.1",
|
"@polymer/paper-listbox": "3.0.1",
|
||||||
"@polymer/paper-slider": "3.0.1",
|
|
||||||
"@polymer/paper-tabs": "3.1.0",
|
"@polymer/paper-tabs": "3.1.0",
|
||||||
"@polymer/paper-toast": "3.0.1",
|
"@polymer/paper-toast": "3.0.1",
|
||||||
"@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.1.10",
|
"@vaadin/combo-box": "24.2.0",
|
||||||
"@vaadin/vaadin-themable-mixin": "24.1.10",
|
"@vaadin/vaadin-themable-mixin": "24.2.0",
|
||||||
"@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",
|
||||||
@ -105,7 +105,7 @@
|
|||||||
"app-datepicker": "5.1.1",
|
"app-datepicker": "5.1.1",
|
||||||
"chart.js": "4.4.0",
|
"chart.js": "4.4.0",
|
||||||
"comlink": "4.4.1",
|
"comlink": "4.4.1",
|
||||||
"core-js": "3.32.2",
|
"core-js": "3.33.1",
|
||||||
"cropperjs": "1.6.1",
|
"cropperjs": "1.6.1",
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
"date-fns-tz": "2.0.0",
|
"date-fns-tz": "2.0.0",
|
||||||
@ -114,15 +114,15 @@
|
|||||||
"fuse.js": "6.6.2",
|
"fuse.js": "6.6.2",
|
||||||
"google-timezones-json": "1.2.0",
|
"google-timezones-json": "1.2.0",
|
||||||
"hls.js": "1.4.12",
|
"hls.js": "1.4.12",
|
||||||
"home-assistant-js-websocket": "8.2.0",
|
"home-assistant-js-websocket": "9.1.0",
|
||||||
"idb-keyval": "6.2.1",
|
"idb-keyval": "6.2.1",
|
||||||
"intl-messageformat": "10.5.3",
|
"intl-messageformat": "10.5.4",
|
||||||
"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",
|
||||||
"lit": "2.8.0",
|
"lit": "2.8.0",
|
||||||
"luxon": "3.4.3",
|
"luxon": "3.4.3",
|
||||||
"marked": "9.0.3",
|
"marked": "9.1.2",
|
||||||
"memoize-one": "6.0.0",
|
"memoize-one": "6.0.0",
|
||||||
"node-vibrant": "3.2.1-alpha.1",
|
"node-vibrant": "3.2.1-alpha.1",
|
||||||
"proxy-polyfill": "0.3.2",
|
"proxy-polyfill": "0.3.2",
|
||||||
@ -141,7 +141,7 @@
|
|||||||
"ua-parser-js": "1.0.36",
|
"ua-parser-js": "1.0.36",
|
||||||
"unfetch": "5.0.0",
|
"unfetch": "5.0.0",
|
||||||
"vis-data": "7.1.7",
|
"vis-data": "7.1.7",
|
||||||
"vis-network": "9.1.6",
|
"vis-network": "9.1.8",
|
||||||
"vue": "2.7.14",
|
"vue": "2.7.14",
|
||||||
"vue2-daterange-picker": "0.6.8",
|
"vue2-daterange-picker": "0.6.8",
|
||||||
"weekstart": "2.0.0",
|
"weekstart": "2.0.0",
|
||||||
@ -154,48 +154,49 @@
|
|||||||
"xss": "1.0.14"
|
"xss": "1.0.14"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.23.0",
|
"@babel/core": "7.23.2",
|
||||||
"@babel/plugin-proposal-decorators": "7.23.0",
|
"@babel/plugin-proposal-decorators": "7.23.2",
|
||||||
"@babel/plugin-transform-runtime": "7.22.15",
|
"@babel/plugin-transform-runtime": "7.23.2",
|
||||||
"@babel/preset-env": "7.22.20",
|
"@babel/preset-env": "7.23.2",
|
||||||
"@babel/preset-typescript": "7.23.0",
|
"@babel/preset-typescript": "7.23.2",
|
||||||
|
"@bundle-stats/plugin-webpack-filter": "4.7.7",
|
||||||
"@koa/cors": "4.0.0",
|
"@koa/cors": "4.0.0",
|
||||||
"@lokalise/node-api": "12.0.0",
|
"@lokalise/node-api": "12.0.0",
|
||||||
"@octokit/auth-oauth-device": "6.0.1",
|
"@octokit/auth-oauth-device": "6.0.1",
|
||||||
"@octokit/plugin-retry": "6.0.1",
|
"@octokit/plugin-retry": "6.0.1",
|
||||||
"@octokit/rest": "20.0.2",
|
"@octokit/rest": "20.0.2",
|
||||||
"@open-wc/dev-server-hmr": "0.1.4",
|
"@open-wc/dev-server-hmr": "0.1.4",
|
||||||
"@rollup/plugin-babel": "6.0.3",
|
"@rollup/plugin-babel": "6.0.4",
|
||||||
"@rollup/plugin-commonjs": "25.0.4",
|
"@rollup/plugin-commonjs": "25.0.7",
|
||||||
"@rollup/plugin-json": "6.0.0",
|
"@rollup/plugin-json": "6.0.1",
|
||||||
"@rollup/plugin-node-resolve": "15.2.1",
|
"@rollup/plugin-node-resolve": "15.2.3",
|
||||||
"@rollup/plugin-replace": "5.0.2",
|
"@rollup/plugin-replace": "5.0.4",
|
||||||
"@types/babel__plugin-transform-runtime": "7.9.3",
|
"@types/babel__plugin-transform-runtime": "7.9.4",
|
||||||
"@types/chromecast-caf-receiver": "6.0.10",
|
"@types/chromecast-caf-receiver": "6.0.11",
|
||||||
"@types/chromecast-caf-sender": "1.0.6",
|
"@types/chromecast-caf-sender": "1.0.7",
|
||||||
"@types/esprima": "4.0.4",
|
"@types/esprima": "4.0.5",
|
||||||
"@types/glob": "8.1.0",
|
"@types/glob": "8.1.0",
|
||||||
"@types/html-minifier-terser": "7.0.0",
|
"@types/html-minifier-terser": "7.0.1",
|
||||||
"@types/js-yaml": "4.0.6",
|
"@types/js-yaml": "4.0.8",
|
||||||
"@types/leaflet": "1.9.6",
|
"@types/leaflet": "1.9.7",
|
||||||
"@types/leaflet-draw": "1.0.8",
|
"@types/leaflet-draw": "1.0.9",
|
||||||
"@types/luxon": "3.3.2",
|
"@types/luxon": "3.3.3",
|
||||||
"@types/mocha": "10.0.2",
|
"@types/mocha": "10.0.3",
|
||||||
"@types/qrcode": "1.5.2",
|
"@types/qrcode": "1.5.4",
|
||||||
"@types/serve-handler": "6.1.2",
|
"@types/serve-handler": "6.1.3",
|
||||||
"@types/sortablejs": "1.15.3",
|
"@types/sortablejs": "1.15.4",
|
||||||
"@types/tar": "6.1.6",
|
"@types/tar": "6.1.7",
|
||||||
"@types/ua-parser-js": "0.7.37",
|
"@types/ua-parser-js": "0.7.38",
|
||||||
"@types/webspeechapi": "0.0.29",
|
"@types/webspeechapi": "0.0.29",
|
||||||
"@typescript-eslint/eslint-plugin": "6.7.3",
|
"@typescript-eslint/eslint-plugin": "6.8.0",
|
||||||
"@typescript-eslint/parser": "6.7.3",
|
"@typescript-eslint/parser": "6.8.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": "4.3.10",
|
"chai": "4.3.10",
|
||||||
"del": "7.1.0",
|
"del": "7.1.0",
|
||||||
"eslint": "8.50.0",
|
"eslint": "8.52.0",
|
||||||
"eslint-config-airbnb-base": "15.0.0",
|
"eslint-config-airbnb-base": "15.0.0",
|
||||||
"eslint-config-airbnb-typescript": "17.1.0",
|
"eslint-config-airbnb-typescript": "17.1.0",
|
||||||
"eslint-config-prettier": "9.0.0",
|
"eslint-config-prettier": "9.0.0",
|
||||||
@ -220,10 +221,10 @@
|
|||||||
"husky": "8.0.3",
|
"husky": "8.0.3",
|
||||||
"instant-mocha": "1.5.2",
|
"instant-mocha": "1.5.2",
|
||||||
"jszip": "3.10.1",
|
"jszip": "3.10.1",
|
||||||
"lint-staged": "14.0.1",
|
"lint-staged": "15.0.2",
|
||||||
"lit-analyzer": "2.0.0-pre.3",
|
"lit-analyzer": "2.0.0-pre.3",
|
||||||
"lodash.template": "4.5.0",
|
"lodash.template": "4.5.0",
|
||||||
"magic-string": "0.30.4",
|
"magic-string": "0.30.5",
|
||||||
"map-stream": "0.0.7",
|
"map-stream": "0.0.7",
|
||||||
"mocha": "10.2.0",
|
"mocha": "10.2.0",
|
||||||
"object-hash": "3.0.0",
|
"object-hash": "3.0.0",
|
||||||
@ -235,7 +236,7 @@
|
|||||||
"rollup-plugin-terser": "7.0.2",
|
"rollup-plugin-terser": "7.0.2",
|
||||||
"rollup-plugin-visualizer": "5.9.2",
|
"rollup-plugin-visualizer": "5.9.2",
|
||||||
"serve-handler": "6.1.5",
|
"serve-handler": "6.1.5",
|
||||||
"sinon": "16.0.0",
|
"sinon": "17.0.0",
|
||||||
"source-map-url": "0.4.1",
|
"source-map-url": "0.4.1",
|
||||||
"systemjs": "6.14.2",
|
"systemjs": "6.14.2",
|
||||||
"tar": "6.2.0",
|
"tar": "6.2.0",
|
||||||
@ -244,10 +245,11 @@
|
|||||||
"typescript": "5.2.2",
|
"typescript": "5.2.2",
|
||||||
"vinyl-buffer": "1.0.1",
|
"vinyl-buffer": "1.0.1",
|
||||||
"vinyl-source-stream": "2.0.0",
|
"vinyl-source-stream": "2.0.0",
|
||||||
"webpack": "5.88.2",
|
"webpack": "5.89.0",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-dev-server": "4.15.1",
|
"webpack-dev-server": "4.15.1",
|
||||||
"webpack-manifest-plugin": "5.0.0",
|
"webpack-manifest-plugin": "5.0.0",
|
||||||
|
"webpack-stats-plugin": "1.1.3",
|
||||||
"webpackbar": "5.0.2",
|
"webpackbar": "5.0.2",
|
||||||
"workbox-build": "7.0.0"
|
"workbox-build": "7.0.0"
|
||||||
},
|
},
|
||||||
@ -255,8 +257,9 @@
|
|||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@polymer/polymer": "patch:@polymer/polymer@3.5.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
"@polymer/polymer": "patch:@polymer/polymer@3.5.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
||||||
"@material/mwc-button@^0.25.3": "^0.27.0",
|
"@material/mwc-button@^0.25.3": "^0.27.0",
|
||||||
|
"lit@^2.7.4 || ^3.0.0": "^2.7.4",
|
||||||
"sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch",
|
"sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch",
|
||||||
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@3.6.3"
|
"packageManager": "yarn@3.6.4"
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20231005.0"
|
version = "20231025.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"
|
||||||
|
@ -33,7 +33,7 @@ fi
|
|||||||
|
|
||||||
docker run \
|
docker run \
|
||||||
-v ${LOCAL_FILE}:/opt/src/${LOCAL_FILE} \
|
-v ${LOCAL_FILE}:/opt/src/${LOCAL_FILE} \
|
||||||
lokalise/lokalise-cli-2@sha256:f1860b26be22fa73b8c93bc5f8690f2afc867610a42de6fc27adc790e5d4425d lokalise2 \
|
lokalise/lokalise-cli-2:v2.6.10 lokalise2 \
|
||||||
--token ${LOKALISE_TOKEN} \
|
--token ${LOKALISE_TOKEN} \
|
||||||
--project-id ${PROJECT_ID} \
|
--project-id ${PROJECT_ID} \
|
||||||
file upload \
|
file upload \
|
||||||
|
@ -26,14 +26,13 @@ export class HaAuthFormString extends HaFormString {
|
|||||||
}
|
}
|
||||||
ha-auth-form-string ha-icon-button {
|
ha-auth-form-string ha-icon-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1em;
|
top: 8px;
|
||||||
right: 12px;
|
right: 8px;
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
ha-auth-form-string ha-icon-button {
|
|
||||||
inset-inline-start: initial;
|
inset-inline-start: initial;
|
||||||
inset-inline-end: 12px;
|
inset-inline-end: 8px;
|
||||||
|
--mdc-icon-button-size: 40px;
|
||||||
|
--mdc-icon-size: 20px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -63,7 +62,7 @@ export class HaAuthFormString extends HaFormString {
|
|||||||
.validationMessage=${this.schema.required ? "Required" : undefined}
|
.validationMessage=${this.schema.required ? "Required" : undefined}
|
||||||
@input=${this._valueChanged}
|
@input=${this._valueChanged}
|
||||||
@change=${this._valueChanged}
|
@change=${this._valueChanged}
|
||||||
></ha-auth-textfield>
|
></ha-auth-textfield>
|
||||||
${this.renderIcon()}
|
${this.renderIcon()}
|
||||||
</ha-auth-textfield>
|
</ha-auth-textfield>
|
||||||
`;
|
`;
|
||||||
|
9
src/common/array/combinations.ts
Normal file
9
src/common/array/combinations.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export function getAllCombinations<T>(arr: T[]) {
|
||||||
|
return arr.reduce<T[][]>(
|
||||||
|
(combinations, element) =>
|
||||||
|
combinations.concat(
|
||||||
|
combinations.map((combination) => [...combination, element])
|
||||||
|
),
|
||||||
|
[[]]
|
||||||
|
);
|
||||||
|
}
|
@ -16,6 +16,7 @@ import {
|
|||||||
mdiCarCoolantLevel,
|
mdiCarCoolantLevel,
|
||||||
mdiCash,
|
mdiCash,
|
||||||
mdiChatSleep,
|
mdiChatSleep,
|
||||||
|
mdiClipboardList,
|
||||||
mdiClock,
|
mdiClock,
|
||||||
mdiCloudUpload,
|
mdiCloudUpload,
|
||||||
mdiCog,
|
mdiCog,
|
||||||
@ -120,6 +121,7 @@ export const FIXED_DOMAIN_ICONS = {
|
|||||||
siren: mdiBullhorn,
|
siren: mdiBullhorn,
|
||||||
stt: mdiMicrophoneMessage,
|
stt: mdiMicrophoneMessage,
|
||||||
text: mdiFormTextbox,
|
text: mdiFormTextbox,
|
||||||
|
todo: mdiClipboardList,
|
||||||
time: mdiClock,
|
time: mdiClock,
|
||||||
timer: mdiTimerOutline,
|
timer: mdiTimerOutline,
|
||||||
tts: mdiSpeakerMessage,
|
tts: mdiSpeakerMessage,
|
||||||
|
@ -5,12 +5,15 @@ import { FrontendLocaleData, TimeZone } from "../../data/translation";
|
|||||||
const calcZonedDate = (
|
const calcZonedDate = (
|
||||||
date: Date,
|
date: Date,
|
||||||
tz: string,
|
tz: string,
|
||||||
fn: (date: Date, options?: any) => Date,
|
fn: (date: Date, options?: any) => Date | number | boolean,
|
||||||
options?
|
options?
|
||||||
) => {
|
) => {
|
||||||
const inputZoned = utcToZonedTime(date, tz);
|
const inputZoned = utcToZonedTime(date, tz);
|
||||||
const fnZoned = fn(inputZoned, options);
|
const fnZoned = fn(inputZoned, options);
|
||||||
return zonedTimeToUtc(fnZoned, tz);
|
if (fnZoned instanceof Date) {
|
||||||
|
return zonedTimeToUtc(fnZoned, tz) as Date;
|
||||||
|
}
|
||||||
|
return fnZoned;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const calcDate = (
|
export const calcDate = (
|
||||||
@ -21,5 +24,16 @@ export const calcDate = (
|
|||||||
options?
|
options?
|
||||||
) =>
|
) =>
|
||||||
locale.time_zone === TimeZone.server
|
locale.time_zone === TimeZone.server
|
||||||
? calcZonedDate(date, config.time_zone, fn, options)
|
? (calcZonedDate(date, config.time_zone, fn, options) as Date)
|
||||||
|
: fn(date, options);
|
||||||
|
|
||||||
|
export const calcDateProperty = (
|
||||||
|
date: Date,
|
||||||
|
fn: (date: Date, options?: any) => boolean | number,
|
||||||
|
locale: FrontendLocaleData,
|
||||||
|
config: HassConfig,
|
||||||
|
options?
|
||||||
|
) =>
|
||||||
|
locale.time_zone === TimeZone.server
|
||||||
|
? (calcZonedDate(date, config.time_zone, fn, options) as number | boolean)
|
||||||
: fn(date, options);
|
: fn(date, options);
|
||||||
|
@ -37,6 +37,23 @@ const formatDateMem = memoizeOne(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Aug 10, 2021
|
||||||
|
export const formatDateShort = (
|
||||||
|
dateObj: Date,
|
||||||
|
locale: FrontendLocaleData,
|
||||||
|
config: HassConfig
|
||||||
|
) => formatDateShortMem(locale, config.time_zone).format(dateObj);
|
||||||
|
|
||||||
|
const formatDateShortMem = memoizeOne(
|
||||||
|
(locale: FrontendLocaleData, serverTimeZone: string) =>
|
||||||
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
|
year: "numeric",
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// 10/08/2021
|
// 10/08/2021
|
||||||
export const formatDateNumeric = (
|
export const formatDateNumeric = (
|
||||||
dateObj: Date,
|
dateObj: Date,
|
||||||
@ -102,13 +119,13 @@ const formatDateNumericMem = memoizeOne(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Aug 10
|
// Aug 10
|
||||||
export const formatDateShort = (
|
export const formatDateVeryShort = (
|
||||||
dateObj: Date,
|
dateObj: Date,
|
||||||
locale: FrontendLocaleData,
|
locale: FrontendLocaleData,
|
||||||
config: HassConfig
|
config: HassConfig
|
||||||
) => formatDateShortMem(locale, config.time_zone).format(dateObj);
|
) => formatDateVeryShortMem(locale, config.time_zone).format(dateObj);
|
||||||
|
|
||||||
const formatDateShortMem = memoizeOne(
|
const formatDateVeryShortMem = memoizeOne(
|
||||||
(locale: FrontendLocaleData, serverTimeZone: string) =>
|
(locale: FrontendLocaleData, serverTimeZone: string) =>
|
||||||
new Intl.DateTimeFormat(locale.language, {
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
import { HaDurationData } from "../../components/ha-duration-input";
|
import { HaDurationData } from "../../components/ha-duration-input";
|
||||||
|
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);
|
||||||
|
|
||||||
export const formatDuration = (duration: HaDurationData) => {
|
export const formatDuration = (
|
||||||
|
locale: FrontendLocaleData,
|
||||||
|
duration: HaDurationData
|
||||||
|
) => {
|
||||||
const d = duration.days || 0;
|
const d = duration.days || 0;
|
||||||
const h = duration.hours || 0;
|
const h = duration.hours || 0;
|
||||||
const m = duration.minutes || 0;
|
const m = duration.minutes || 0;
|
||||||
@ -10,7 +15,11 @@ export const formatDuration = (duration: HaDurationData) => {
|
|||||||
const ms = duration.milliseconds || 0;
|
const ms = duration.milliseconds || 0;
|
||||||
|
|
||||||
if (d > 0) {
|
if (d > 0) {
|
||||||
return `${d} day${d === 1 ? "" : "s"} ${h}:${leftPad(m)}:${leftPad(s)}`;
|
return `${Intl.NumberFormat(locale.language, {
|
||||||
|
style: "unit",
|
||||||
|
unit: "day",
|
||||||
|
unitDisplay: "long",
|
||||||
|
}).format(d)} ${h}:${leftPad(m)}:${leftPad(s)}`;
|
||||||
}
|
}
|
||||||
if (h > 0) {
|
if (h > 0) {
|
||||||
return `${h}:${leftPad(m)}:${leftPad(s)}`;
|
return `${h}:${leftPad(m)}:${leftPad(s)}`;
|
||||||
@ -19,10 +28,18 @@ export const formatDuration = (duration: HaDurationData) => {
|
|||||||
return `${m}:${leftPad(s)}`;
|
return `${m}:${leftPad(s)}`;
|
||||||
}
|
}
|
||||||
if (s > 0) {
|
if (s > 0) {
|
||||||
return `${s} second${s === 1 ? "" : "s"}`;
|
return Intl.NumberFormat(locale.language, {
|
||||||
|
style: "unit",
|
||||||
|
unit: "second",
|
||||||
|
unitDisplay: "long",
|
||||||
|
}).format(s);
|
||||||
}
|
}
|
||||||
if (ms > 0) {
|
if (ms > 0) {
|
||||||
return `${ms} millisecond${ms === 1 ? "" : "s"}`;
|
return Intl.NumberFormat(locale.language, {
|
||||||
|
style: "unit",
|
||||||
|
unit: "millisecond",
|
||||||
|
unitDisplay: "long",
|
||||||
|
}).format(ms);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
@ -41,9 +41,7 @@ export const applyThemesOnElement = (
|
|||||||
// If there is no explicitly desired dark mode provided, we automatically
|
// If there is no explicitly desired dark mode provided, we automatically
|
||||||
// use the active one from `themes`.
|
// use the active one from `themes`.
|
||||||
const darkMode =
|
const darkMode =
|
||||||
themeSettings && themeSettings?.dark !== undefined
|
themeSettings?.dark !== undefined ? themeSettings.dark : themes.darkMode;
|
||||||
? themeSettings?.dark
|
|
||||||
: themes.darkMode;
|
|
||||||
|
|
||||||
let cacheKey = themeToApply;
|
let cacheKey = themeToApply;
|
||||||
let themeRules: Partial<ThemeVars> = {};
|
let themeRules: Partial<ThemeVars> = {};
|
||||||
@ -135,10 +133,19 @@ export const applyThemesOnElement = (
|
|||||||
|
|
||||||
// Set and/or reset styles
|
// Set and/or reset styles
|
||||||
if (element.updateStyles) {
|
if (element.updateStyles) {
|
||||||
|
// Use updateStyles() method of Polymer elements
|
||||||
element.updateStyles(styles);
|
element.updateStyles(styles);
|
||||||
} else if (window.ShadyCSS) {
|
} else if (window.ShadyCSS) {
|
||||||
// Implement updateStyles() method of Polymer elements
|
// Use ShadyCSS if available
|
||||||
window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ element, styles);
|
window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ element, styles);
|
||||||
|
} else {
|
||||||
|
for (const s in styles) {
|
||||||
|
if (s === null) {
|
||||||
|
element.style.removeProperty(s);
|
||||||
|
} else {
|
||||||
|
element.style.setProperty(s, styles[s]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,19 +1,29 @@
|
|||||||
// https://gist.github.com/hagemann/382adfc57adbd5af078dc93feef01fe1
|
// https://gist.github.com/hagemann/382adfc57adbd5af078dc93feef01fe1
|
||||||
export const slugify = (value: string, delimiter = "_") => {
|
export const slugify = (value: string, delimiter = "_") => {
|
||||||
const a =
|
const a =
|
||||||
"àáäâãåăæąçćčđďèéěėëêęğǵḧìíïîįłḿǹńňñòóöôœøṕŕřßşśšșťțùúüûǘůűūųẃẍÿýźžż·/_,:;";
|
"àáâäæãåāăąçćčđďèéêëēėęěğǵḧîïíīįìıİłḿñńǹňôöòóœøōõőṕŕřßśšşșťțûüùúūǘůűųẃẍÿýžźż·";
|
||||||
const b = `aaaaaaaaacccddeeeeeeegghiiiiilmnnnnooooooprrsssssttuuuuuuuuuwxyyzzz${delimiter}${delimiter}${delimiter}${delimiter}${delimiter}${delimiter}`;
|
const b = `aaaaaaaaaacccddeeeeeeeegghiiiiiiiilmnnnnoooooooooprrsssssttuuuuuuuuuwxyyzzz${delimiter}`;
|
||||||
const p = new RegExp(a.split("").join("|"), "g");
|
const p = new RegExp(a.split("").join("|"), "g");
|
||||||
|
|
||||||
return value
|
let slugified;
|
||||||
.toString()
|
|
||||||
.toLowerCase()
|
if (value === "") {
|
||||||
.replace(/\s+/g, delimiter) // Replace spaces with delimiter
|
slugified = "";
|
||||||
.replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special characters
|
} else {
|
||||||
.replace(/&/g, `${delimiter}and${delimiter}`) // Replace & with 'and'
|
slugified = value
|
||||||
.replace(/[^\w-]+/g, "") // Remove all non-word characters
|
.toString()
|
||||||
.replace(/-/g, delimiter) // Replace - with delimiter
|
.toLowerCase()
|
||||||
.replace(new RegExp(`(${delimiter})\\1+`, "g"), "$1") // Replace multiple delimiters with single delimiter
|
.replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special characters
|
||||||
.replace(new RegExp(`^${delimiter}+`), "") // Trim delimiter from start of text
|
.replace(/(?<=\d),(?=\d)/g, "") // Remove Commas between numbers
|
||||||
.replace(new RegExp(`${delimiter}+$`), ""); // Trim delimiter from end of text
|
.replace(/[^a-z0-9]+/g, delimiter) // Replace all non-word characters
|
||||||
|
.replace(new RegExp(`(${delimiter})\\1+`, "g"), "$1") // Replace multiple delimiters with single delimiter
|
||||||
|
.replace(new RegExp(`^${delimiter}+`), "") // Trim delimiter from start of text
|
||||||
|
.replace(new RegExp(`${delimiter}+$`), ""); // Trim delimiter from end of text
|
||||||
|
|
||||||
|
if (slugified === "") {
|
||||||
|
slugified = "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return slugified;
|
||||||
};
|
};
|
||||||
|
@ -13,45 +13,33 @@ export const handleStructError = (
|
|||||||
for (const failure of err.failures()) {
|
for (const failure of err.failures()) {
|
||||||
if (failure.value === undefined) {
|
if (failure.value === undefined) {
|
||||||
errors.push(
|
errors.push(
|
||||||
hass.localize(
|
hass.localize("ui.errors.config.key_missing", {
|
||||||
"ui.errors.config.key_missing",
|
key: failure.path.join("."),
|
||||||
"key",
|
})
|
||||||
failure.path.join(".")
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
} else if (failure.type === "never") {
|
} else if (failure.type === "never") {
|
||||||
warnings.push(
|
warnings.push(
|
||||||
hass.localize(
|
hass.localize("ui.errors.config.key_not_expected", {
|
||||||
"ui.errors.config.key_not_expected",
|
key: failure.path.join("."),
|
||||||
"key",
|
})
|
||||||
failure.path.join(".")
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
} else if (failure.type === "union") {
|
} else if (failure.type === "union") {
|
||||||
continue;
|
continue;
|
||||||
} else if (failure.type === "enums") {
|
} else if (failure.type === "enums") {
|
||||||
warnings.push(
|
warnings.push(
|
||||||
hass.localize(
|
hass.localize("ui.errors.config.key_wrong_type", {
|
||||||
"ui.errors.config.key_wrong_type",
|
key: failure.path.join("."),
|
||||||
"key",
|
type_correct: failure.message.replace("Expected ", "").split(", ")[0],
|
||||||
failure.path.join("."),
|
type_wrong: JSON.stringify(failure.value),
|
||||||
"type_correct",
|
})
|
||||||
failure.message.replace("Expected ", "").split(", ")[0],
|
|
||||||
"type_wrong",
|
|
||||||
JSON.stringify(failure.value)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
warnings.push(
|
warnings.push(
|
||||||
hass.localize(
|
hass.localize("ui.errors.config.key_wrong_type", {
|
||||||
"ui.errors.config.key_wrong_type",
|
key: failure.path.join("."),
|
||||||
"key",
|
type_correct: failure.refinement || failure.type,
|
||||||
failure.path.join("."),
|
type_wrong: JSON.stringify(failure.value),
|
||||||
"type_correct",
|
})
|
||||||
failure.refinement || failure.type,
|
|
||||||
"type_wrong",
|
|
||||||
JSON.stringify(failure.value)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import { mdiAlertOctagram, mdiCheckBold } from "@mdi/js";
|
import { mdiAlertOctagram, mdiCheckBold } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
nothing,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../ha-circular-progress";
|
import "../ha-circular-progress";
|
||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
@ -27,7 +34,7 @@ export class HaProgressButton extends LitElement {
|
|||||||
<slot></slot>
|
<slot></slot>
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
${!overlay
|
${!overlay
|
||||||
? ""
|
? nothing
|
||||||
: html`
|
: html`
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
${this._result === "success"
|
${this._result === "success"
|
||||||
|
@ -39,7 +39,7 @@ import {
|
|||||||
formatDate,
|
formatDate,
|
||||||
formatDateMonth,
|
formatDateMonth,
|
||||||
formatDateMonthYear,
|
formatDateMonthYear,
|
||||||
formatDateShort,
|
formatDateVeryShort,
|
||||||
formatDateWeekdayDay,
|
formatDateWeekdayDay,
|
||||||
formatDateYear,
|
formatDateYear,
|
||||||
} from "../../common/datetime/format_date";
|
} from "../../common/datetime/format_date";
|
||||||
@ -128,7 +128,7 @@ _adapters._date.override({
|
|||||||
this.options.config
|
this.options.config
|
||||||
);
|
);
|
||||||
case "day":
|
case "day":
|
||||||
return formatDateShort(
|
return formatDateVeryShort(
|
||||||
new Date(time),
|
new Date(time),
|
||||||
this.options.locale,
|
this.options.locale,
|
||||||
this.options.config
|
this.options.config
|
||||||
|
@ -12,6 +12,7 @@ import { styleMap } from "lit/directives/style-map";
|
|||||||
import { clamp } from "../../common/number/clamp";
|
import { clamp } from "../../common/number/clamp";
|
||||||
import { computeRTL } from "../../common/util/compute_rtl";
|
import { computeRTL } from "../../common/util/compute_rtl";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
import { debounce } from "../../common/util/debounce";
|
||||||
|
|
||||||
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
|
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
|
||||||
|
|
||||||
@ -52,6 +53,12 @@ export class HaChartBase extends LitElement {
|
|||||||
|
|
||||||
@state() private _hiddenDatasets: Set<number> = new Set();
|
@state() private _hiddenDatasets: Set<number> = new Set();
|
||||||
|
|
||||||
|
private _paddingUpdateCount = 0;
|
||||||
|
|
||||||
|
private _paddingUpdateLock = false;
|
||||||
|
|
||||||
|
private _paddingYAxisInternal = 0;
|
||||||
|
|
||||||
public disconnectedCallback() {
|
public disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this._releaseCanvas();
|
this._releaseCanvas();
|
||||||
@ -104,9 +111,44 @@ export class HaChartBase extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public shouldUpdate(changedProps: PropertyValues): boolean {
|
||||||
|
if (
|
||||||
|
this._paddingUpdateLock &&
|
||||||
|
changedProps.size === 1 &&
|
||||||
|
changedProps.has("paddingYAxis")
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _debouncedClearUpdates = debounce(
|
||||||
|
() => {
|
||||||
|
this._paddingUpdateCount = 0;
|
||||||
|
},
|
||||||
|
2000,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
public willUpdate(changedProps: PropertyValues): void {
|
public willUpdate(changedProps: PropertyValues): void {
|
||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
|
|
||||||
|
if (!this._paddingUpdateLock) {
|
||||||
|
this._paddingYAxisInternal = this.paddingYAxis;
|
||||||
|
if (changedProps.size === 1 && changedProps.has("paddingYAxis")) {
|
||||||
|
this._paddingUpdateCount++;
|
||||||
|
if (this._paddingUpdateCount > 300) {
|
||||||
|
this._paddingUpdateLock = true;
|
||||||
|
// eslint-disable-next-line
|
||||||
|
console.error(
|
||||||
|
"Detected excessive chart padding updates, possibly an infinite loop. Disabling axis padding."
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this._debouncedClearUpdates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.hasUpdated || !this.chart) {
|
if (!this.hasUpdated || !this.chart) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -171,10 +213,10 @@ export class HaChartBase extends LitElement {
|
|||||||
this.height ?? this._chartHeight ?? this.clientWidth / 2
|
this.height ?? this._chartHeight ?? this.clientWidth / 2
|
||||||
}px`,
|
}px`,
|
||||||
"padding-left": `${
|
"padding-left": `${
|
||||||
computeRTL(this.hass) ? 0 : this.paddingYAxis
|
computeRTL(this.hass) ? 0 : this._paddingYAxisInternal
|
||||||
}px`,
|
}px`,
|
||||||
"padding-right": `${
|
"padding-right": `${
|
||||||
computeRTL(this.hass) ? this.paddingYAxis : 0
|
computeRTL(this.hass) ? this._paddingYAxisInternal : 0
|
||||||
}px`,
|
}px`,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
@ -324,7 +366,7 @@ export class HaChartBase extends LitElement {
|
|||||||
clamp(
|
clamp(
|
||||||
context.tooltip.caretX,
|
context.tooltip.caretX,
|
||||||
100,
|
100,
|
||||||
this.clientWidth - 100 - this.paddingYAxis
|
this.clientWidth - 100 - this._paddingYAxisInternal
|
||||||
) -
|
) -
|
||||||
100 +
|
100 +
|
||||||
"px",
|
"px",
|
||||||
|
@ -141,16 +141,10 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
`${context.dataset.label}: ${formatNumber(
|
`${context.dataset.label}: ${formatNumber(
|
||||||
context.parsed.y,
|
context.parsed.y,
|
||||||
this.hass.locale,
|
this.hass.locale,
|
||||||
this.data[context.datasetIndex]?.entity_id
|
getNumberFormatOptions(
|
||||||
? getNumberFormatOptions(
|
undefined,
|
||||||
this.hass.states[
|
this.hass.entities[this._entityIds[context.datasetIndex]]
|
||||||
this.data[context.datasetIndex].entity_id
|
)
|
||||||
],
|
|
||||||
this.hass.entities[
|
|
||||||
this.data[context.datasetIndex].entity_id
|
|
||||||
]
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
)} ${this.unit}`,
|
)} ${this.unit}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -73,9 +73,9 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public isLoadingData = false;
|
@property({ type: Boolean }) public isLoadingData = false;
|
||||||
|
|
||||||
@state() private _computedStartTime!: Date;
|
private _computedStartTime!: Date;
|
||||||
|
|
||||||
@state() private _computedEndTime!: Date;
|
private _computedEndTime!: Date;
|
||||||
|
|
||||||
@state() private _maxYWidth = 0;
|
@state() private _maxYWidth = 0;
|
||||||
|
|
||||||
@ -114,31 +114,6 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
${this.hass.localize("ui.components.history_charts.no_history_found")}
|
${this.hass.localize("ui.components.history_charts.no_history_found")}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
this._computedEndTime =
|
|
||||||
this.upToNow || !this.endTime || this.endTime > now ? now : this.endTime;
|
|
||||||
|
|
||||||
if (this.startTime) {
|
|
||||||
this._computedStartTime = this.startTime;
|
|
||||||
} else if (this.hoursToShow) {
|
|
||||||
this._computedStartTime = new Date(
|
|
||||||
new Date().getTime() - 60 * 60 * this.hoursToShow * 1000
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this._computedStartTime = new Date(
|
|
||||||
this.historyData.timeline.reduce(
|
|
||||||
(minTime, stateInfo) =>
|
|
||||||
Math.min(
|
|
||||||
minTime,
|
|
||||||
new Date(stateInfo.data[0].last_changed).getTime()
|
|
||||||
),
|
|
||||||
new Date().getTime()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const combinedItems = this.historyData.timeline.length
|
const combinedItems = this.historyData.timeline.length
|
||||||
? (this.virtualize
|
? (this.virtualize
|
||||||
? chunkData(this.historyData.timeline, CANVAS_TIMELINE_ROWS_CHUNK)
|
? chunkData(this.historyData.timeline, CANVAS_TIMELINE_ROWS_CHUNK)
|
||||||
@ -220,10 +195,45 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected willUpdate() {
|
protected willUpdate(changedProps: PropertyValues) {
|
||||||
if (!this.hasUpdated) {
|
if (!this.hasUpdated) {
|
||||||
loadVirtualizer();
|
loadVirtualizer();
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
[...changedProps.keys()].some(
|
||||||
|
(prop) =>
|
||||||
|
!(
|
||||||
|
["_maxYWidth", "_childYWidths", "_chartCount"] as PropertyKey[]
|
||||||
|
).includes(prop)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Don't recompute times when we just want to update layout
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
this._computedEndTime =
|
||||||
|
this.upToNow || !this.endTime || this.endTime > now
|
||||||
|
? now
|
||||||
|
: this.endTime;
|
||||||
|
|
||||||
|
if (this.startTime) {
|
||||||
|
this._computedStartTime = this.startTime;
|
||||||
|
} else if (this.hoursToShow) {
|
||||||
|
this._computedStartTime = new Date(
|
||||||
|
new Date().getTime() - 60 * 60 * this.hoursToShow * 1000
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this._computedStartTime = new Date(
|
||||||
|
this.historyData.timeline.reduce(
|
||||||
|
(minTime, stateInfo) =>
|
||||||
|
Math.min(
|
||||||
|
minTime,
|
||||||
|
new Date(stateInfo.data[0].last_changed).getTime()
|
||||||
|
),
|
||||||
|
new Date().getTime()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
@ -19,6 +19,7 @@ import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
|||||||
import {
|
import {
|
||||||
formatNumber,
|
formatNumber,
|
||||||
numberFormatToLocale,
|
numberFormatToLocale,
|
||||||
|
getNumberFormatOptions,
|
||||||
} from "../../common/number/format_number";
|
} from "../../common/number/format_number";
|
||||||
import {
|
import {
|
||||||
getDisplayUnit,
|
getDisplayUnit,
|
||||||
@ -74,6 +75,8 @@ export class StatisticsChart extends LitElement {
|
|||||||
|
|
||||||
@state() private _chartData: ChartData = { datasets: [] };
|
@state() private _chartData: ChartData = { datasets: [] };
|
||||||
|
|
||||||
|
@state() private _statisticIds: string[] = [];
|
||||||
|
|
||||||
@state() private _chartOptions?: ChartOptions;
|
@state() private _chartOptions?: ChartOptions;
|
||||||
|
|
||||||
@query("ha-chart-base") private _chart?: HaChartBase;
|
@query("ha-chart-base") private _chart?: HaChartBase;
|
||||||
@ -189,7 +192,11 @@ export class StatisticsChart extends LitElement {
|
|||||||
label: (context) =>
|
label: (context) =>
|
||||||
`${context.dataset.label}: ${formatNumber(
|
`${context.dataset.label}: ${formatNumber(
|
||||||
context.parsed.y,
|
context.parsed.y,
|
||||||
this.hass.locale
|
this.hass.locale,
|
||||||
|
getNumberFormatOptions(
|
||||||
|
undefined,
|
||||||
|
this.hass.entities[this._statisticIds[context.datasetIndex]]
|
||||||
|
)
|
||||||
)} ${
|
)} ${
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
context.dataset.unit || ""
|
context.dataset.unit || ""
|
||||||
@ -248,6 +255,7 @@ export class StatisticsChart extends LitElement {
|
|||||||
let colorIndex = 0;
|
let colorIndex = 0;
|
||||||
const statisticsData = Object.entries(this.statisticsData);
|
const statisticsData = Object.entries(this.statisticsData);
|
||||||
const totalDataSets: ChartDataset<"line">[] = [];
|
const totalDataSets: ChartDataset<"line">[] = [];
|
||||||
|
const statisticIds: string[] = [];
|
||||||
let endTime: Date;
|
let endTime: Date;
|
||||||
|
|
||||||
if (statisticsData.length === 0) {
|
if (statisticsData.length === 0) {
|
||||||
@ -386,6 +394,7 @@ export class StatisticsChart extends LitElement {
|
|||||||
unit: meta?.unit_of_measurement,
|
unit: meta?.unit_of_measurement,
|
||||||
band,
|
band,
|
||||||
});
|
});
|
||||||
|
statisticIds.push(statistic_id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -411,11 +420,7 @@ export class StatisticsChart extends LitElement {
|
|||||||
} else {
|
} else {
|
||||||
val = stat[type];
|
val = stat[type];
|
||||||
}
|
}
|
||||||
dataValues.push(
|
dataValues.push(val ?? null);
|
||||||
val !== null && val !== undefined
|
|
||||||
? Math.round(val * 100) / 100
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
pushData(startDate, new Date(stat.end), dataValues);
|
pushData(startDate, new Date(stat.end), dataValues);
|
||||||
});
|
});
|
||||||
@ -431,6 +436,7 @@ export class StatisticsChart extends LitElement {
|
|||||||
this._chartData = {
|
this._chartData = {
|
||||||
datasets: totalDataSets,
|
datasets: totalDataSets,
|
||||||
};
|
};
|
||||||
|
this._statisticIds = statisticIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
@ -31,6 +31,10 @@ const Component = Vue.extend({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
openingDirection: {
|
||||||
|
type: String,
|
||||||
|
default: "right",
|
||||||
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@ -66,7 +70,7 @@ const Component = Vue.extend({
|
|||||||
props: {
|
props: {
|
||||||
"time-picker": this.timePicker,
|
"time-picker": this.timePicker,
|
||||||
"auto-apply": this.autoApply,
|
"auto-apply": this.autoApply,
|
||||||
opens: "right",
|
opens: this.openingDirection,
|
||||||
"show-dropdowns": false,
|
"show-dropdowns": false,
|
||||||
"time-picker24-hour": this.twentyfourHours,
|
"time-picker24-hour": this.twentyfourHours,
|
||||||
disabled: this.disabled,
|
disabled: this.disabled,
|
||||||
@ -126,9 +130,9 @@ class DateRangePickerElement extends WrappedElement {
|
|||||||
${dateRangePickerStyles}
|
${dateRangePickerStyles}
|
||||||
.calendars {
|
.calendars {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
}
|
}
|
||||||
.daterangepicker {
|
.daterangepicker {
|
||||||
left: 0px !important;
|
|
||||||
top: auto;
|
top: auto;
|
||||||
box-shadow: var(--ha-card-box-shadow, none);
|
box-shadow: var(--ha-card-box-shadow, none);
|
||||||
background-color: var(--card-background-color);
|
background-color: var(--card-background-color);
|
||||||
@ -252,6 +256,10 @@ class DateRangePickerElement extends WrappedElement {
|
|||||||
direction: ltr;
|
direction: ltr;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
.vue-daterange-picker{
|
||||||
|
min-width: unset !important;
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
const shadowRoot = this.shadowRoot!;
|
const shadowRoot = this.shadowRoot!;
|
||||||
shadowRoot.appendChild(style);
|
shadowRoot.appendChild(style);
|
||||||
|
@ -87,6 +87,8 @@ export class HaStatisticPicker extends LitElement {
|
|||||||
@property({ type: Array, attribute: "exclude-statistics" })
|
@property({ type: Array, attribute: "exclude-statistics" })
|
||||||
public excludeStatistics?: string[];
|
public excludeStatistics?: string[];
|
||||||
|
|
||||||
|
@property() public helpMissingEntityUrl = "/more-info/statistics/";
|
||||||
|
|
||||||
@state() private _opened?: boolean;
|
@state() private _opened?: boolean;
|
||||||
|
|
||||||
@query("ha-combo-box", true) public comboBox!: HaComboBox;
|
@query("ha-combo-box", true) public comboBox!: HaComboBox;
|
||||||
@ -111,7 +113,7 @@ export class HaStatisticPicker extends LitElement {
|
|||||||
? html`<a
|
? html`<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
href=${documentationUrl(this.hass, "/more-info/statistics/")}
|
href=${documentationUrl(this.hass, this.helpMissingEntityUrl)}
|
||||||
>${this.hass.localize(
|
>${this.hass.localize(
|
||||||
"ui.components.statistic-picker.learn_more"
|
"ui.components.statistic-picker.learn_more"
|
||||||
)}</a
|
)}</a
|
||||||
|
@ -112,9 +112,7 @@ export class StateBadge extends LitElement {
|
|||||||
const stateObj = this.stateObj;
|
const stateObj = this.stateObj;
|
||||||
|
|
||||||
const iconStyle: { [name: string]: string } = {};
|
const iconStyle: { [name: string]: string } = {};
|
||||||
const hostStyle: Partial<CSSStyleDeclaration> = {
|
let backgroundImage = "";
|
||||||
backgroundImage: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
this._showIcon = true;
|
this._showIcon = true;
|
||||||
|
|
||||||
@ -135,10 +133,12 @@ export class StateBadge extends LitElement {
|
|||||||
if (domain === "camera") {
|
if (domain === "camera") {
|
||||||
imageUrl = cameraUrlWithWidthHeight(imageUrl, 80, 80);
|
imageUrl = cameraUrlWithWidthHeight(imageUrl, 80, 80);
|
||||||
}
|
}
|
||||||
hostStyle.backgroundImage = `url(${imageUrl})`;
|
backgroundImage = `url(${imageUrl})`;
|
||||||
this._showIcon = false;
|
this._showIcon = false;
|
||||||
if (domain === "update") {
|
if (domain === "update") {
|
||||||
hostStyle.borderRadius = "0";
|
this.style.borderRadius = "0";
|
||||||
|
} else if (domain === "media_player") {
|
||||||
|
this.style.borderRadius = "8%";
|
||||||
}
|
}
|
||||||
} else if (this.color) {
|
} else if (this.color) {
|
||||||
// Externally provided overriding color wins over state color
|
// Externally provided overriding color wins over state color
|
||||||
@ -179,12 +179,12 @@ export class StateBadge extends LitElement {
|
|||||||
if (this.hass) {
|
if (this.hass) {
|
||||||
imageUrl = this.hass.hassUrl(imageUrl);
|
imageUrl = this.hass.hassUrl(imageUrl);
|
||||||
}
|
}
|
||||||
hostStyle.backgroundImage = `url(${imageUrl})`;
|
backgroundImage = `url(${imageUrl})`;
|
||||||
this._showIcon = false;
|
this._showIcon = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._iconStyle = iconStyle;
|
this._iconStyle = iconStyle;
|
||||||
Object.assign(this.style, hostStyle);
|
this.style.backgroundImage = backgroundImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
@ -95,25 +95,25 @@ class StateInfo extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
min-width: 120px;
|
min-width: 120px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
state-badge {
|
state-badge {
|
||||||
float: left;
|
flex: none;
|
||||||
}
|
|
||||||
:host([rtl]) state-badge {
|
|
||||||
float: right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
margin-left: 56px;
|
margin-left: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([rtl]) .info {
|
:host([rtl]) .info {
|
||||||
margin-right: 56px;
|
margin-right: 8px;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { haStyle } from "../resources/styles";
|
import { haStyle } from "../resources/styles";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./ha-area-picker";
|
|
||||||
import "./ha-textfield";
|
import "./ha-textfield";
|
||||||
import type { HaTextField } from "./ha-textfield";
|
import type { HaTextField } from "./ha-textfield";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { until } from "lit/directives/until";
|
import { until } from "lit/directives/until";
|
||||||
import { HomeAssistant } from "../types";
|
|
||||||
import { formatNumber } from "../common/number/format_number";
|
import { formatNumber } from "../common/number/format_number";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
let jsYamlPromise: Promise<typeof import("../resources/js-yaml-dump")>;
|
|
||||||
|
|
||||||
@customElement("ha-attribute-value")
|
@customElement("ha-attribute-value")
|
||||||
class HaAttributeValue extends LitElement {
|
class HaAttributeValue extends LitElement {
|
||||||
@ -44,7 +42,7 @@ class HaAttributeValue extends LitElement {
|
|||||||
${attributeValue}
|
${attributeValue}
|
||||||
</a>
|
</a>
|
||||||
`;
|
`;
|
||||||
} catch (_) {
|
} catch {
|
||||||
// Nothing to do here
|
// Nothing to do here
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,15 +53,19 @@ class HaAttributeValue extends LitElement {
|
|||||||
attributeValue.some((val) => val instanceof Object)) ||
|
attributeValue.some((val) => val instanceof Object)) ||
|
||||||
(!Array.isArray(attributeValue) && attributeValue instanceof Object)
|
(!Array.isArray(attributeValue) && attributeValue instanceof Object)
|
||||||
) {
|
) {
|
||||||
if (!jsYamlPromise) {
|
const yaml = import("js-yaml").then(({ dump }) => dump(attributeValue));
|
||||||
jsYamlPromise = import("../resources/js-yaml-dump");
|
|
||||||
}
|
|
||||||
const yaml = jsYamlPromise.then((jsYaml) => jsYaml.dump(attributeValue));
|
|
||||||
return html`<pre>${until(yaml, "")}</pre>`;
|
return html`<pre>${until(yaml, "")}</pre>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.hass.formatEntityAttributeValue(this.stateObj!, this.attribute);
|
return this.hass.formatEntityAttributeValue(this.stateObj!, this.attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -26,6 +26,8 @@ export class HaButtonMenu extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public fixed = false;
|
@property({ type: Boolean }) public fixed = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, attribute: "no-anchor" }) public noAnchor = false;
|
||||||
|
|
||||||
@query("mwc-menu", true) private _menu?: Menu;
|
@query("mwc-menu", true) private _menu?: Menu;
|
||||||
|
|
||||||
public get items() {
|
public get items() {
|
||||||
@ -82,7 +84,7 @@ export class HaButtonMenu extends LitElement {
|
|||||||
if (this.disabled) {
|
if (this.disabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._menu!.anchor = this;
|
this._menu!.anchor = this.noAnchor ? null : this;
|
||||||
this._menu!.show();
|
this._menu!.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,9 @@ export class HaButton extends Button {
|
|||||||
.mdc-button {
|
.mdc-button {
|
||||||
height: var(--button-height, 36px);
|
height: var(--button-height, 36px);
|
||||||
}
|
}
|
||||||
|
.trailing-icon {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import { customElement, property, state } from "lit/decorators";
|
|||||||
import memoizeOne from "memoize-one";
|
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 { CodeMirror, loadCodeMirror } from "../resources/codemirror.ondemand";
|
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./ha-icon";
|
import "./ha-icon";
|
||||||
|
|
||||||
@ -58,7 +57,7 @@ export class HaCodeEditor extends ReactiveElement {
|
|||||||
|
|
||||||
@state() private _value = "";
|
@state() private _value = "";
|
||||||
|
|
||||||
private _loadedCodeMirror?: CodeMirror;
|
private _loadedCodeMirror?: typeof import("../resources/codemirror");
|
||||||
|
|
||||||
private _iconList?: Completion[];
|
private _iconList?: Completion[];
|
||||||
|
|
||||||
@ -110,7 +109,7 @@ export class HaCodeEditor extends ReactiveElement {
|
|||||||
|
|
||||||
// Ensure CodeMirror module is loaded before any update
|
// Ensure CodeMirror module is loaded before any update
|
||||||
protected override async scheduleUpdate() {
|
protected override async scheduleUpdate() {
|
||||||
this._loadedCodeMirror ??= await loadCodeMirror();
|
this._loadedCodeMirror ??= await import("../resources/codemirror");
|
||||||
super.scheduleUpdate();
|
super.scheduleUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +64,7 @@ class HaConfigEntryPicker extends LitElement {
|
|||||||
type: "icon",
|
type: "icon",
|
||||||
darkOptimized: this.hass.themes?.darkMode,
|
darkOptimized: this.hass.themes?.darkMode,
|
||||||
})}
|
})}
|
||||||
|
crossorigin="anonymous"
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
@error=${this._onImageError}
|
@error=${this._onImageError}
|
||||||
@load=${this._onImageLoad}
|
@load=${this._onImageLoad}
|
||||||
|
@ -269,37 +269,61 @@ export class HaCountryPicker extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property() public countries?: string[];
|
||||||
|
|
||||||
|
@property() public helper?: string;
|
||||||
|
|
||||||
@property({ type: Boolean }) public required = false;
|
@property({ type: Boolean }) public required = false;
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
private _getOptions = memoizeOne((language?: string) => {
|
@property({ type: Boolean }) public noSort = false;
|
||||||
const countryDisplayNames =
|
|
||||||
Intl && "DisplayNames" in Intl
|
|
||||||
? new Intl.DisplayNames(language, {
|
|
||||||
type: "region",
|
|
||||||
fallback: "code",
|
|
||||||
})
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const options = COUNTRIES.map((country) => ({
|
private _getOptions = memoizeOne(
|
||||||
value: country,
|
(language?: string, countries?: string[]) => {
|
||||||
label: countryDisplayNames ? countryDisplayNames.of(country)! : country,
|
let options: { label: string; value: string }[] = [];
|
||||||
}));
|
const countryDisplayNames =
|
||||||
options.sort((a, b) =>
|
Intl && "DisplayNames" in Intl
|
||||||
caseInsensitiveStringCompare(a.label, b.label, language)
|
? new Intl.DisplayNames(language, {
|
||||||
);
|
type: "region",
|
||||||
return options;
|
fallback: "code",
|
||||||
});
|
})
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (countries) {
|
||||||
|
options = countries.map((country) => ({
|
||||||
|
value: country,
|
||||||
|
label: countryDisplayNames
|
||||||
|
? countryDisplayNames.of(country)!
|
||||||
|
: country,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
options = COUNTRIES.map((country) => ({
|
||||||
|
value: country,
|
||||||
|
label: countryDisplayNames
|
||||||
|
? countryDisplayNames.of(country)!
|
||||||
|
: country,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.noSort) {
|
||||||
|
options.sort((a, b) =>
|
||||||
|
caseInsensitiveStringCompare(a.label, b.label, language)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const options = this._getOptions(this.language);
|
const options = this._getOptions(this.language, this.countries);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-select
|
<ha-select
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
|
.helper=${this.helper}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@selected=${this._changed}
|
@selected=${this._changed}
|
||||||
@closed=${stopPropagation}
|
@closed=${stopPropagation}
|
||||||
|
@ -15,10 +15,11 @@ import {
|
|||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
|
nothing,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { calcDate } from "../common/datetime/calc_date";
|
import { calcDate } from "../common/datetime/calc_date";
|
||||||
import { firstWeekdayIndex } from "../common/datetime/first_weekday";
|
import { firstWeekdayIndex } from "../common/datetime/first_weekday";
|
||||||
import { formatDate } from "../common/datetime/format_date";
|
import { formatDate } from "../common/datetime/format_date";
|
||||||
@ -29,6 +30,7 @@ import { HomeAssistant } from "../types";
|
|||||||
import "./date-range-picker";
|
import "./date-range-picker";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
import "./ha-textfield";
|
import "./ha-textfield";
|
||||||
|
import "./ha-icon-button";
|
||||||
|
|
||||||
export interface DateRangePickerRanges {
|
export interface DateRangePickerRanges {
|
||||||
[key: string]: [Date, Date];
|
[key: string]: [Date, Date];
|
||||||
@ -54,8 +56,22 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
|
|
||||||
@property({ type: String }) private _rtlDirection = "ltr";
|
@property({ type: String }) private _rtlDirection = "ltr";
|
||||||
|
|
||||||
protected willUpdate() {
|
@property({ type: Boolean }) private minimal = false;
|
||||||
if (!this.hasUpdated && this.ranges === undefined) {
|
|
||||||
|
@property() public openingDirection?: "right" | "left" | "center" | "inline";
|
||||||
|
|
||||||
|
@state() private _calcedOpeningDirection?:
|
||||||
|
| "right"
|
||||||
|
| "left"
|
||||||
|
| "center"
|
||||||
|
| "inline";
|
||||||
|
|
||||||
|
protected willUpdate(changedProps: PropertyValues) {
|
||||||
|
if (
|
||||||
|
(!this.hasUpdated && this.ranges === undefined) ||
|
||||||
|
(changedProps.has("hass") &&
|
||||||
|
this.hass?.localize !== changedProps.get("hass")?.localize)
|
||||||
|
) {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const weekStartsOn = firstWeekdayIndex(this.hass.locale);
|
const weekStartsOn = firstWeekdayIndex(this.hass.locale);
|
||||||
const weekStart = calcDate(
|
const weekStart = calcDate(
|
||||||
@ -133,41 +149,62 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
<date-range-picker
|
<date-range-picker
|
||||||
?disabled=${this.disabled}
|
?disabled=${this.disabled}
|
||||||
?auto-apply=${this.autoApply}
|
?auto-apply=${this.autoApply}
|
||||||
?time-picker=${this.timePicker}
|
time-picker=${this.timePicker}
|
||||||
twentyfour-hours=${this._hour24format}
|
twentyfour-hours=${this._hour24format}
|
||||||
start-date=${this.startDate}
|
start-date=${this.startDate}
|
||||||
end-date=${this.endDate}
|
end-date=${this.endDate}
|
||||||
?ranges=${this.ranges !== false}
|
?ranges=${this.ranges !== false}
|
||||||
|
opening-direction=${this.openingDirection ||
|
||||||
|
this._calcedOpeningDirection}
|
||||||
first-day=${firstWeekdayIndex(this.hass.locale)}
|
first-day=${firstWeekdayIndex(this.hass.locale)}
|
||||||
>
|
>
|
||||||
<div slot="input" class="date-range-inputs">
|
<div slot="input" class="date-range-inputs" @click=${this._handleClick}>
|
||||||
<ha-svg-icon .path=${mdiCalendar}></ha-svg-icon>
|
${!this.minimal
|
||||||
<ha-textfield
|
? html`<ha-svg-icon .path=${mdiCalendar}></ha-svg-icon>
|
||||||
.value=${this.timePicker
|
<ha-textfield
|
||||||
? formatDateTime(
|
.value=${this.timePicker
|
||||||
this.startDate,
|
? formatDateTime(
|
||||||
this.hass.locale,
|
this.startDate,
|
||||||
this.hass.config
|
this.hass.locale,
|
||||||
)
|
this.hass.config
|
||||||
: formatDate(this.startDate, this.hass.locale, this.hass.config)}
|
)
|
||||||
.label=${this.hass.localize(
|
: formatDate(
|
||||||
"ui.components.date-range-picker.start_date"
|
this.startDate,
|
||||||
)}
|
this.hass.locale,
|
||||||
.disabled=${this.disabled}
|
this.hass.config
|
||||||
@click=${this._handleInputClick}
|
)}
|
||||||
readonly
|
.label=${this.hass.localize(
|
||||||
></ha-textfield>
|
"ui.components.date-range-picker.start_date"
|
||||||
<ha-textfield
|
)}
|
||||||
.value=${this.timePicker
|
.disabled=${this.disabled}
|
||||||
? formatDateTime(this.endDate, this.hass.locale, this.hass.config)
|
@click=${this._handleInputClick}
|
||||||
: formatDate(this.endDate, this.hass.locale, this.hass.config)}
|
readonly
|
||||||
.label=${this.hass.localize(
|
></ha-textfield>
|
||||||
"ui.components.date-range-picker.end_date"
|
<ha-textfield
|
||||||
)}
|
.value=${this.timePicker
|
||||||
.disabled=${this.disabled}
|
? formatDateTime(
|
||||||
@click=${this._handleInputClick}
|
this.endDate,
|
||||||
readonly
|
this.hass.locale,
|
||||||
></ha-textfield>
|
this.hass.config
|
||||||
|
)
|
||||||
|
: formatDate(
|
||||||
|
this.endDate,
|
||||||
|
this.hass.locale,
|
||||||
|
this.hass.config
|
||||||
|
)}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.components.date-range-picker.end_date"
|
||||||
|
)}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@click=${this._handleInputClick}
|
||||||
|
readonly
|
||||||
|
></ha-textfield>`
|
||||||
|
: html`<ha-icon-button
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.components.date-range-picker.select_date_range"
|
||||||
|
)}
|
||||||
|
.path=${mdiCalendar}
|
||||||
|
></ha-icon-button>`}
|
||||||
</div>
|
</div>
|
||||||
${this.ranges
|
${this.ranges
|
||||||
? html`<div
|
? html`<div
|
||||||
@ -181,7 +218,7 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
</div>`
|
</div>`
|
||||||
: ""}
|
: nothing}
|
||||||
<div slot="footer" class="date-range-footer">
|
<div slot="footer" class="date-range-footer">
|
||||||
<mwc-button @click=${this._cancelDateRange}
|
<mwc-button @click=${this._cancelDateRange}
|
||||||
>${this.hass.localize("ui.common.cancel")}</mwc-button
|
>${this.hass.localize("ui.common.cancel")}</mwc-button
|
||||||
@ -225,6 +262,22 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleClick() {
|
||||||
|
// calculate opening direction if not set
|
||||||
|
if (!this._dateRangePicker.open && !this.openingDirection) {
|
||||||
|
const datePickerPosition = this.getBoundingClientRect().x;
|
||||||
|
let opens: "right" | "left" | "center" | "inline";
|
||||||
|
if (datePickerPosition > (2 * window.innerWidth) / 3) {
|
||||||
|
opens = "left";
|
||||||
|
} else if (datePickerPosition < window.innerWidth / 3) {
|
||||||
|
opens = "right";
|
||||||
|
} else {
|
||||||
|
opens = "center";
|
||||||
|
}
|
||||||
|
this._calcedOpeningDirection = opens;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-svg-icon {
|
ha-svg-icon {
|
||||||
@ -234,6 +287,10 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ha-icon-button {
|
||||||
|
direction: var(--direction);
|
||||||
|
}
|
||||||
|
|
||||||
.date-range-inputs {
|
.date-range-inputs {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -163,10 +163,7 @@ export class HaExpansionPanel extends LitElement {
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: var(
|
border-color: var(--outline-color);
|
||||||
--ha-card-border-color,
|
|
||||||
var(--divider-color, #e0e0e0)
|
|
||||||
);
|
|
||||||
border-radius: var(--ha-card-border-radius, 12px);
|
border-radius: var(--ha-card-border-radius, 12px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +66,10 @@ export const computeInitialHaFormData = (
|
|||||||
typeof firstOption === "string" ? firstOption : firstOption.value;
|
typeof firstOption === "string" ? firstOption : firstOption.value;
|
||||||
data[field.name] = selector.select.multiple ? [val] : val;
|
data[field.name] = selector.select.multiple ? [val] : val;
|
||||||
}
|
}
|
||||||
|
} else if ("country" in selector) {
|
||||||
|
if (selector.country?.countries?.length) {
|
||||||
|
data[field.name] = selector.country.countries[0];
|
||||||
|
}
|
||||||
} else if ("duration" in selector) {
|
} else if ("duration" in selector) {
|
||||||
data[field.name] = {
|
data[field.name] = {
|
||||||
hours: 0,
|
hours: 0,
|
||||||
|
@ -57,8 +57,7 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
|||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<ha-slider
|
<ha-slider
|
||||||
pin
|
labeled
|
||||||
ignore-bar-touch
|
|
||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
.min=${this.schema.valueMin}
|
.min=${this.schema.valueMin}
|
||||||
.max=${this.schema.valueMax}
|
.max=${this.schema.valueMax}
|
||||||
|
@ -138,15 +138,13 @@ export class HaFormString extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
ha-icon-button {
|
ha-icon-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1em;
|
top: 8px;
|
||||||
right: 12px;
|
right: 8px;
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-icon-button {
|
|
||||||
inset-inline-start: initial;
|
inset-inline-start: initial;
|
||||||
inset-inline-end: 12px;
|
inset-inline-end: 8px;
|
||||||
|
--mdc-icon-button-size: 40px;
|
||||||
|
--mdc-icon-size: 20px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
nothing,
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
@ -11,12 +11,12 @@ import { fireEvent } from "../common/dom/fire_event";
|
|||||||
import { debounce } from "../common/util/debounce";
|
import { debounce } from "../common/util/debounce";
|
||||||
import { CustomIcon, customIcons } from "../data/custom_icons";
|
import { CustomIcon, customIcons } from "../data/custom_icons";
|
||||||
import {
|
import {
|
||||||
checkCacheVersion,
|
|
||||||
Chunks,
|
Chunks,
|
||||||
findIconChunk,
|
|
||||||
getIcon,
|
|
||||||
Icons,
|
Icons,
|
||||||
MDI_PREFIXES,
|
MDI_PREFIXES,
|
||||||
|
checkCacheVersion,
|
||||||
|
findIconChunk,
|
||||||
|
getIcon,
|
||||||
writeCache,
|
writeCache,
|
||||||
} from "../data/iconsets";
|
} from "../data/iconsets";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
@ -47,6 +47,8 @@ export class HaIcon extends LitElement {
|
|||||||
|
|
||||||
@state() private _path?: string;
|
@state() private _path?: string;
|
||||||
|
|
||||||
|
@state() private _secondaryPath?: string;
|
||||||
|
|
||||||
@state() private _viewBox?: string;
|
@state() private _viewBox?: string;
|
||||||
|
|
||||||
@state() private _legacy = false;
|
@state() private _legacy = false;
|
||||||
@ -55,6 +57,7 @@ export class HaIcon extends LitElement {
|
|||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
if (changedProps.has("icon")) {
|
if (changedProps.has("icon")) {
|
||||||
this._path = undefined;
|
this._path = undefined;
|
||||||
|
this._secondaryPath = undefined;
|
||||||
this._viewBox = undefined;
|
this._viewBox = undefined;
|
||||||
this._loadIcon();
|
this._loadIcon();
|
||||||
}
|
}
|
||||||
@ -70,6 +73,7 @@ export class HaIcon extends LitElement {
|
|||||||
}
|
}
|
||||||
return html`<ha-svg-icon
|
return html`<ha-svg-icon
|
||||||
.path=${this._path}
|
.path=${this._path}
|
||||||
|
.secondaryPath=${this._secondaryPath}
|
||||||
.viewBox=${this._viewBox}
|
.viewBox=${this._viewBox}
|
||||||
></ha-svg-icon>`;
|
></ha-svg-icon>`;
|
||||||
}
|
}
|
||||||
@ -175,6 +179,7 @@ export class HaIcon extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._path = icon.path;
|
this._path = icon.path;
|
||||||
|
this._secondaryPath = icon.secondaryPath;
|
||||||
this._viewBox = icon.viewBox;
|
this._viewBox = icon.viewBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import "./ha-icon";
|
|
||||||
import "./ha-slider";
|
|
||||||
|
|
||||||
class HaLabeledSlider extends PolymerElement {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin: 5px 0 8px;
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-container {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-icon {
|
|
||||||
margin-top: 4px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-slider {
|
|
||||||
flex-grow: 1;
|
|
||||||
background-image: var(--ha-slider-background);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="title">[[_getTitle()]]</div>
|
|
||||||
<div class="extra-container"><slot name="extra"></slot></div>
|
|
||||||
<div class="slider-container">
|
|
||||||
<ha-icon icon="[[icon]]" hidden$="[[!icon]]"></ha-icon>
|
|
||||||
<ha-slider
|
|
||||||
min="[[min]]"
|
|
||||||
max="[[max]]"
|
|
||||||
step="[[step]]"
|
|
||||||
pin="[[pin]]"
|
|
||||||
disabled="[[disabled]]"
|
|
||||||
value="{{value}}"
|
|
||||||
></ha-slider>
|
|
||||||
</div>
|
|
||||||
<template is="dom-if" if="[[helper]]">
|
|
||||||
<ha-input-helper-text>[[helper]]</ha-input-helper-text>
|
|
||||||
</template>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
_getTitle() {
|
|
||||||
return `${this.caption}${this.caption && this.required ? " *" : ""}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
caption: String,
|
|
||||||
disabled: Boolean,
|
|
||||||
required: Boolean,
|
|
||||||
min: Number,
|
|
||||||
max: Number,
|
|
||||||
pin: Boolean,
|
|
||||||
step: Number,
|
|
||||||
helper: String,
|
|
||||||
|
|
||||||
extra: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
ignoreBarTouch: {
|
|
||||||
type: Boolean,
|
|
||||||
value: true,
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: Number,
|
|
||||||
notify: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("ha-labeled-slider", HaLabeledSlider);
|
|
96
src/components/ha-labeled-slider.ts
Normal file
96
src/components/ha-labeled-slider.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import "./ha-icon";
|
||||||
|
import "./ha-slider";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
|
||||||
|
@customElement("ha-labeled-slider")
|
||||||
|
class HaLabeledSlider extends LitElement {
|
||||||
|
@property() public labeled? = false;
|
||||||
|
|
||||||
|
@property() public caption?: string;
|
||||||
|
|
||||||
|
@property() public disabled?: boolean;
|
||||||
|
|
||||||
|
@property() public required?: boolean;
|
||||||
|
|
||||||
|
@property() public min: number = 0;
|
||||||
|
|
||||||
|
@property() public max: number = 100;
|
||||||
|
|
||||||
|
@property() public step: number = 1;
|
||||||
|
|
||||||
|
@property() public helper?: string;
|
||||||
|
|
||||||
|
@property() public extra = false;
|
||||||
|
|
||||||
|
@property() public icon?: string;
|
||||||
|
|
||||||
|
@property() public value?: number;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<div class="title">${this._getTitle()}</div>
|
||||||
|
<div class="extra-container"><slot name="extra"></slot></div>
|
||||||
|
<div class="slider-container">
|
||||||
|
${this.icon ? html`<ha-icon icon=${this.icon}></ha-icon>` : nothing}
|
||||||
|
<ha-slider
|
||||||
|
.min=${this.min}
|
||||||
|
.max=${this.max}
|
||||||
|
.step=${this.step}
|
||||||
|
labeled=${this.labeled}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.value=${this.value}
|
||||||
|
@change=${this._inputChanged}
|
||||||
|
></ha-slider>
|
||||||
|
</div>
|
||||||
|
${this.helper
|
||||||
|
? html`<ha-input-helper-text> ${this.helper} </ha-input-helper-text>`
|
||||||
|
: nothing}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getTitle(): string {
|
||||||
|
return `${this.caption}${this.caption && this.required ? " *" : ""}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _inputChanged(ev) {
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: Number((ev.target as any).value),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 5px 0 8px;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-icon {
|
||||||
|
margin-top: 8px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-slider {
|
||||||
|
flex-grow: 1;
|
||||||
|
background-image: var(--ha-slider-background);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-labeled-slider": HaLabeledSlider;
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +1,16 @@
|
|||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import { OutlinedButton } from "@material/web/button/internal/outlined-button";
|
import { MdOutlinedButton } from "@material/web/button/outlined-button";
|
||||||
import { styles as outlinedStyles } from "@material/web/button/internal/outlined-styles.css";
|
|
||||||
import { styles as sharedStyles } from "@material/web/button/internal/shared-styles.css";
|
|
||||||
|
|
||||||
@customElement("ha-outlined-button")
|
@customElement("ha-outlined-button")
|
||||||
export class HaOutlinedButton extends OutlinedButton {
|
export class HaOutlinedButton extends MdOutlinedButton {
|
||||||
static override styles = [
|
static override styles = [
|
||||||
sharedStyles,
|
...super.styles,
|
||||||
outlinedStyles,
|
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
--ha-icon-display: block;
|
--ha-icon-display: block;
|
||||||
--md-sys-color-primary: var(--primary-text-color);
|
--md-sys-color-primary: var(--primary-text-color);
|
||||||
--md-sys-color-outline: var(--divider-color);
|
--md-sys-color-outline: var(--outline-color);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -1,21 +1,11 @@
|
|||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import { IconButton } from "@material/web/iconbutton/internal/icon-button";
|
import { MdOutlinedIconButton } from "@material/web/iconbutton/outlined-icon-button";
|
||||||
import { styles as outlinedStyles } from "@material/web/iconbutton/internal/outlined-styles.css";
|
|
||||||
import { styles as sharedStyles } from "@material/web/iconbutton/internal/shared-styles.css";
|
|
||||||
|
|
||||||
@customElement("ha-outlined-icon-button")
|
@customElement("ha-outlined-icon-button")
|
||||||
export class HaOutlinedIconButton extends IconButton {
|
export class HaOutlinedIconButton extends MdOutlinedIconButton {
|
||||||
protected override getRenderClasses() {
|
|
||||||
return {
|
|
||||||
...super.getRenderClasses(),
|
|
||||||
outlined: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static override styles = [
|
static override styles = [
|
||||||
sharedStyles,
|
...super.styles,
|
||||||
outlinedStyles,
|
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
--ha-icon-display: block;
|
--ha-icon-display: block;
|
||||||
|
@ -154,6 +154,8 @@ export class HaRelatedItems extends LitElement {
|
|||||||
useFallback: true,
|
useFallback: true,
|
||||||
darkOptimized: this.hass.themes?.darkMode,
|
darkOptimized: this.hass.themes?.darkMode,
|
||||||
})}
|
})}
|
||||||
|
crossorigin="anonymous"
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
alt=${entry.domain}
|
alt=${entry.domain}
|
||||||
slot="graphic"
|
slot="graphic"
|
||||||
/>
|
/>
|
||||||
|
@ -1,15 +1,32 @@
|
|||||||
import { SelectBase } from "@material/mwc-select/mwc-select-base";
|
import { SelectBase } from "@material/mwc-select/mwc-select-base";
|
||||||
import { styles } from "@material/mwc-select/mwc-select.css";
|
import { styles } from "@material/mwc-select/mwc-select.css";
|
||||||
|
import { mdiClose } from "@mdi/js";
|
||||||
import { css, html, nothing } from "lit";
|
import { css, html, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
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-button";
|
||||||
|
|
||||||
@customElement("ha-select")
|
@customElement("ha-select")
|
||||||
export class HaSelect extends SelectBase {
|
export class HaSelect extends SelectBase {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@property({ type: Boolean }) public icon?: boolean;
|
@property({ type: Boolean }) public icon?: boolean;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) public clearable?: boolean;
|
||||||
|
|
||||||
|
protected override render() {
|
||||||
|
return html`
|
||||||
|
${super.render()}
|
||||||
|
${this.clearable && !this.required && !this.disabled && this.value
|
||||||
|
? html`<ha-icon-button
|
||||||
|
label="clear"
|
||||||
|
@click=${this._clearValue}
|
||||||
|
.path=${mdiClose}
|
||||||
|
></ha-icon-button>`
|
||||||
|
: nothing}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
protected override renderLeadingIcon() {
|
protected override renderLeadingIcon() {
|
||||||
if (!this.icon) {
|
if (!this.icon) {
|
||||||
return nothing;
|
return nothing;
|
||||||
@ -33,6 +50,15 @@ export class HaSelect extends SelectBase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _clearValue(): void {
|
||||||
|
if (this.disabled || !this.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.valueSetDirectly = true;
|
||||||
|
this.select(-1);
|
||||||
|
this.mdcFoundation.handleChange();
|
||||||
|
}
|
||||||
|
|
||||||
private _translationsUpdated = debounce(async () => {
|
private _translationsUpdated = debounce(async () => {
|
||||||
await nextRender();
|
await nextRender();
|
||||||
this.layoutOptions();
|
this.layoutOptions();
|
||||||
@ -41,6 +67,9 @@ export class HaSelect extends SelectBase {
|
|||||||
static override styles = [
|
static override styles = [
|
||||||
styles,
|
styles,
|
||||||
css`
|
css`
|
||||||
|
:host([clearable]) {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
.mdc-select:not(.mdc-select--disabled) .mdc-select__icon {
|
.mdc-select:not(.mdc-select--disabled) .mdc-select__icon {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
@ -68,6 +97,23 @@ export class HaSelect extends SelectBase {
|
|||||||
.mdc-select__anchor .mdc-floating-label--float-above {
|
.mdc-select__anchor .mdc-floating-label--float-above {
|
||||||
transform-origin: var(--float-start);
|
transform-origin: var(--float-start);
|
||||||
}
|
}
|
||||||
|
.mdc-select__selected-text-container {
|
||||||
|
padding-inline-end: var(--select-selected-text-padding-end, 0px);
|
||||||
|
}
|
||||||
|
:host([clearable]) .mdc-select__selected-text-container {
|
||||||
|
padding-inline-end: var(--select-selected-text-padding-end, 12px);
|
||||||
|
}
|
||||||
|
ha-icon-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 28px;
|
||||||
|
--mdc-icon-button-size: 36px;
|
||||||
|
--mdc-icon-size: 20px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
inset-inline-start: initial;
|
||||||
|
inset-inline-end: 28px;
|
||||||
|
direction: var(--direction);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ export class HaColorTempSelector extends LitElement {
|
|||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<ha-labeled-slider
|
<ha-labeled-slider
|
||||||
pin
|
labeled
|
||||||
icon="hass:thermometer"
|
icon="hass:thermometer"
|
||||||
.caption=${this.label || ""}
|
.caption=${this.label || ""}
|
||||||
.min=${this.selector.color_temp?.min_mireds ?? 153}
|
.min=${this.selector.color_temp?.min_mireds ?? 153}
|
||||||
@ -33,27 +33,25 @@ export class HaColorTempSelector extends LitElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.helper=${this.helper}
|
.helper=${this.helper}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
@change=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
></ha-labeled-slider>
|
></ha-labeled-slider>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent) {
|
private _valueChanged(ev: CustomEvent) {
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: Number((ev.target as any).value),
|
value: Number((ev.detail as any).value),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
ha-labeled-slider {
|
ha-labeled-slider {
|
||||||
--ha-slider-background: -webkit-linear-gradient(
|
--ha-slider-background: linear-gradient(
|
||||||
var(--float-end),
|
to var(--float-end),
|
||||||
rgb(255, 160, 0) 0%,
|
rgb(255, 160, 0) 0%,
|
||||||
white 50%,
|
white 50%,
|
||||||
rgb(166, 209, 255) 100%
|
rgb(166, 209, 255) 100%
|
||||||
);
|
);
|
||||||
/* The color temp minimum value shouldn't be rendered differently. It's not "off". */
|
|
||||||
--paper-slider-knob-start-border-color: var(--primary-color);
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
49
src/components/ha-selector/ha-selector-country.ts
Normal file
49
src/components/ha-selector/ha-selector-country.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { css, html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { CountrySelector } from "../../data/selector";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-country-picker";
|
||||||
|
|
||||||
|
@customElement("ha-selector-country")
|
||||||
|
export class HaCountrySelector extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public selector!: CountrySelector;
|
||||||
|
|
||||||
|
@property() public value?: any;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property() public helper?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public required = true;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<ha-country-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.value}
|
||||||
|
.label=${this.label}
|
||||||
|
.helper=${this.helper}
|
||||||
|
.countries=${this.selector.country?.countries}
|
||||||
|
.noSort=${this.selector.country?.no_sort}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.required=${this.required}
|
||||||
|
></ha-country-picker>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
ha-country-picker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-selector-country": HaCountrySelector;
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,7 @@ export class HaDateSelector extends LitElement {
|
|||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.value=${this.value}
|
.value=${typeof this.value === "string" ? this.value : undefined}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.helper=${this.helper}
|
.helper=${this.helper}
|
||||||
>
|
>
|
||||||
|
@ -30,7 +30,8 @@ export class HaDateTimeSelector extends LitElement {
|
|||||||
@query("ha-time-input") private _timeInput!: HaTimeInput;
|
@query("ha-time-input") private _timeInput!: HaTimeInput;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const values = this.value?.split(" ");
|
const values =
|
||||||
|
typeof this.value === "string" ? this.value.split(" ") : undefined;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="input">
|
<div class="input">
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
@ -26,8 +26,22 @@ export class HaNumberSelector extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
private _valueStr = "";
|
||||||
|
|
||||||
|
protected willUpdate(changedProps: PropertyValues) {
|
||||||
|
if (changedProps.has("value")) {
|
||||||
|
if (this.value !== Number(this._valueStr)) {
|
||||||
|
this._valueStr =
|
||||||
|
!this.value || isNaN(this.value) ? "" : this.value.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const isBox = this.selector.number?.mode === "box";
|
const isBox =
|
||||||
|
this.selector.number?.mode === "box" ||
|
||||||
|
this.selector.number?.min === undefined ||
|
||||||
|
this.selector.number?.max === undefined;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="input">
|
<div class="input">
|
||||||
@ -37,6 +51,7 @@ export class HaNumberSelector extends LitElement {
|
|||||||
? html`${this.label}${this.required ? "*" : ""}`
|
? html`${this.label}${this.required ? "*" : ""}`
|
||||||
: ""}
|
: ""}
|
||||||
<ha-slider
|
<ha-slider
|
||||||
|
labeled
|
||||||
.min=${this.selector.number?.min}
|
.min=${this.selector.number?.min}
|
||||||
.max=${this.selector.number?.max}
|
.max=${this.selector.number?.max}
|
||||||
.value=${this.value ?? ""}
|
.value=${this.value ?? ""}
|
||||||
@ -45,8 +60,6 @@ export class HaNumberSelector extends LitElement {
|
|||||||
: this.selector.number?.step ?? 1}
|
: this.selector.number?.step ?? 1}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
pin
|
|
||||||
ignore-bar-touch
|
|
||||||
@change=${this._handleSliderChange}
|
@change=${this._handleSliderChange}
|
||||||
>
|
>
|
||||||
</ha-slider>
|
</ha-slider>
|
||||||
@ -57,14 +70,12 @@ export class HaNumberSelector extends LitElement {
|
|||||||
(this.selector.number?.step ?? 1) % 1 !== 0
|
(this.selector.number?.step ?? 1) % 1 !== 0
|
||||||
? "decimal"
|
? "decimal"
|
||||||
: "numeric"}
|
: "numeric"}
|
||||||
.label=${this.selector.number?.mode !== "box"
|
.label=${!isBox ? undefined : this.label}
|
||||||
? undefined
|
|
||||||
: this.label}
|
|
||||||
.placeholder=${this.placeholder}
|
.placeholder=${this.placeholder}
|
||||||
class=${classMap({ single: this.selector.number?.mode === "box" })}
|
class=${classMap({ single: isBox })}
|
||||||
.min=${this.selector.number?.min}
|
.min=${this.selector.number?.min}
|
||||||
.max=${this.selector.number?.max}
|
.max=${this.selector.number?.max}
|
||||||
.value=${this.value ?? ""}
|
.value=${this._valueStr ?? ""}
|
||||||
.step=${this.selector.number?.step ?? 1}
|
.step=${this.selector.number?.step ?? 1}
|
||||||
helperPersistent
|
helperPersistent
|
||||||
.helper=${isBox ? this.helper : undefined}
|
.helper=${isBox ? this.helper : undefined}
|
||||||
@ -73,7 +84,7 @@ export class HaNumberSelector extends LitElement {
|
|||||||
.suffix=${this.selector.number?.unit_of_measurement}
|
.suffix=${this.selector.number?.unit_of_measurement}
|
||||||
type="number"
|
type="number"
|
||||||
autoValidate
|
autoValidate
|
||||||
?no-spinner=${this.selector.number?.mode !== "box"}
|
?no-spinner=${!isBox}
|
||||||
@input=${this._handleInputChange}
|
@input=${this._handleInputChange}
|
||||||
>
|
>
|
||||||
</ha-textfield>
|
</ha-textfield>
|
||||||
@ -86,6 +97,7 @@ export class HaNumberSelector extends LitElement {
|
|||||||
|
|
||||||
private _handleInputChange(ev) {
|
private _handleInputChange(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
this._valueStr = ev.target.value;
|
||||||
const value =
|
const value =
|
||||||
ev.target.value === "" || isNaN(ev.target.value)
|
ev.target.value === "" || isNaN(ev.target.value)
|
||||||
? undefined
|
? undefined
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { mdiClose } from "@mdi/js";
|
import { mdiClose, mdiDrag } from "@mdi/js";
|
||||||
import { css, html, LitElement } from "lit";
|
import { LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
|
import { repeat } from "lit/directives/repeat";
|
||||||
|
import { SortableEvent } from "sortablejs";
|
||||||
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
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 { ensureArray } from "../../common/array/ensure-array";
|
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||||
import type { SelectOption, SelectSelector } from "../../data/selector";
|
import type { SelectOption, SelectSelector } from "../../data/selector";
|
||||||
|
import { sortableStyles } from "../../resources/ha-sortable-style";
|
||||||
|
import { SortableInstance } from "../../resources/sortable";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import "../ha-checkbox";
|
import "../ha-checkbox";
|
||||||
import "../ha-chip";
|
import "../ha-chip";
|
||||||
@ -13,10 +18,9 @@ import "../ha-chip-set";
|
|||||||
import "../ha-combo-box";
|
import "../ha-combo-box";
|
||||||
import type { HaComboBox } from "../ha-combo-box";
|
import type { HaComboBox } from "../ha-combo-box";
|
||||||
import "../ha-formfield";
|
import "../ha-formfield";
|
||||||
|
import "../ha-input-helper-text";
|
||||||
import "../ha-radio";
|
import "../ha-radio";
|
||||||
import "../ha-select";
|
import "../ha-select";
|
||||||
import "../ha-input-helper-text";
|
|
||||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
|
||||||
|
|
||||||
@customElement("ha-selector-select")
|
@customElement("ha-selector-select")
|
||||||
export class HaSelectSelector extends LitElement {
|
export class HaSelectSelector extends LitElement {
|
||||||
@ -38,6 +42,68 @@ export class HaSelectSelector extends LitElement {
|
|||||||
|
|
||||||
@query("ha-combo-box", true) private comboBox!: HaComboBox;
|
@query("ha-combo-box", true) private comboBox!: HaComboBox;
|
||||||
|
|
||||||
|
private _sortable?: SortableInstance;
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues): void {
|
||||||
|
if (changedProps.has("value") || changedProps.has("selector")) {
|
||||||
|
const sortableNeeded =
|
||||||
|
this.selector.select?.multiple &&
|
||||||
|
this.selector.select.reorder &&
|
||||||
|
this.value?.length;
|
||||||
|
if (!this._sortable && sortableNeeded) {
|
||||||
|
this._createSortable();
|
||||||
|
} else if (this._sortable && !sortableNeeded) {
|
||||||
|
this._destroySortable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _createSortable() {
|
||||||
|
const Sortable = (await import("../../resources/sortable")).default;
|
||||||
|
this._sortable = new Sortable(
|
||||||
|
this.shadowRoot!.querySelector("ha-chip-set")!,
|
||||||
|
{
|
||||||
|
animation: 150,
|
||||||
|
fallbackClass: "sortable-fallback",
|
||||||
|
draggable: "ha-chip",
|
||||||
|
onChoose: (evt: SortableEvent) => {
|
||||||
|
(evt.item as any).placeholder =
|
||||||
|
document.createComment("sort-placeholder");
|
||||||
|
evt.item.after((evt.item as any).placeholder);
|
||||||
|
},
|
||||||
|
onEnd: (evt: SortableEvent) => {
|
||||||
|
// put back in original location
|
||||||
|
if ((evt.item as any).placeholder) {
|
||||||
|
(evt.item as any).placeholder.replaceWith(evt.item);
|
||||||
|
delete (evt.item as any).placeholder;
|
||||||
|
}
|
||||||
|
this._dragged(evt);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _dragged(ev: SortableEvent): void {
|
||||||
|
if (ev.oldIndex === ev.newIndex) return;
|
||||||
|
this._move(ev.oldIndex!, ev.newIndex!);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _move(index: number, newIndex: number) {
|
||||||
|
const value = this.value as string[];
|
||||||
|
const newValue = value.concat();
|
||||||
|
const element = newValue.splice(index, 1)[0];
|
||||||
|
newValue.splice(newIndex, 0, element);
|
||||||
|
this.value = newValue;
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: newValue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _destroySortable() {
|
||||||
|
this._sortable?.destroy();
|
||||||
|
this._sortable = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
private _filter = "";
|
private _filter = "";
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@ -71,7 +137,11 @@ export class HaSelectSelector extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.selector.select?.custom_value && this._mode === "list") {
|
if (
|
||||||
|
!this.selector.select?.custom_value &&
|
||||||
|
!this.selector.select?.reorder &&
|
||||||
|
this._mode === "list"
|
||||||
|
) {
|
||||||
if (!this.selector.select?.multiple) {
|
if (!this.selector.select?.multiple) {
|
||||||
return html`
|
return html`
|
||||||
<div>
|
<div>
|
||||||
@ -124,23 +194,39 @@ export class HaSelectSelector extends LitElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
${value?.length
|
${value?.length
|
||||||
? html`<ha-chip-set>
|
? html`
|
||||||
${value.map(
|
<ha-chip-set>
|
||||||
(item, idx) => html`
|
${repeat(
|
||||||
<ha-chip hasTrailingIcon>
|
value,
|
||||||
${options.find((option) => option.value === item)?.label ||
|
(item) => item,
|
||||||
item}
|
(item, idx) => html`
|
||||||
<ha-svg-icon
|
<ha-chip
|
||||||
slot="trailing-icon"
|
hasTrailingIcon
|
||||||
.path=${mdiClose}
|
.hasIcon=${this.selector.select?.reorder}
|
||||||
.idx=${idx}
|
>
|
||||||
@click=${this._removeItem}
|
${this.selector.select?.reorder
|
||||||
></ha-svg-icon>
|
? html`
|
||||||
</ha-chip>
|
<ha-svg-icon
|
||||||
`
|
slot="icon"
|
||||||
)}
|
.path=${mdiDrag}
|
||||||
</ha-chip-set>`
|
data-handle
|
||||||
: ""}
|
></ha-svg-icon>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
${options.find((option) => option.value === item)
|
||||||
|
?.label || item}
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="trailing-icon"
|
||||||
|
.path=${mdiClose}
|
||||||
|
.idx=${idx}
|
||||||
|
@click=${this._removeItem}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</ha-chip>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</ha-chip-set>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
<ha-combo-box
|
<ha-combo-box
|
||||||
item-value-path="value"
|
item-value-path="value"
|
||||||
@ -198,6 +284,7 @@ export class HaSelectSelector extends LitElement {
|
|||||||
.helper=${this.helper ?? ""}
|
.helper=${this.helper ?? ""}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
|
clearable
|
||||||
@closed=${stopPropagation}
|
@closed=${stopPropagation}
|
||||||
@selected=${this._valueChanged}
|
@selected=${this._valueChanged}
|
||||||
>
|
>
|
||||||
@ -228,7 +315,7 @@ export class HaSelectSelector extends LitElement {
|
|||||||
private _valueChanged(ev) {
|
private _valueChanged(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
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;
|
||||||
}
|
}
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
@ -330,16 +417,22 @@ export class HaSelectSelector extends LitElement {
|
|||||||
this.comboBox.filteredItems = filteredItems;
|
this.comboBox.filteredItems = filteredItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = [
|
||||||
ha-select,
|
sortableStyles,
|
||||||
mwc-formfield,
|
css`
|
||||||
ha-formfield {
|
:host {
|
||||||
display: block;
|
position: relative;
|
||||||
}
|
}
|
||||||
mwc-list-item[disabled] {
|
ha-select,
|
||||||
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
mwc-formfield,
|
||||||
}
|
ha-formfield {
|
||||||
`;
|
display: block;
|
||||||
|
}
|
||||||
|
mwc-list-item[disabled] {
|
||||||
|
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -111,13 +111,13 @@ export class HaTextSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
ha-icon-button {
|
ha-icon-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 8px;
|
||||||
right: 10px;
|
right: 8px;
|
||||||
--mdc-icon-button-size: 36px;
|
inset-inline-start: initial;
|
||||||
|
inset-inline-end: 8px;
|
||||||
|
--mdc-icon-button-size: 40px;
|
||||||
--mdc-icon-size: 20px;
|
--mdc-icon-size: 20px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
inset-inline-start: initial;
|
|
||||||
inset-inline-end: 10px;
|
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -23,7 +23,7 @@ export class HaTimeSelector extends LitElement {
|
|||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<ha-time-input
|
<ha-time-input
|
||||||
.value=${this.value}
|
.value=${typeof this.value === "string" ? this.value : undefined}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
|
@ -21,6 +21,7 @@ const LOAD_ELEMENTS = {
|
|||||||
config_entry: () => import("./ha-selector-config-entry"),
|
config_entry: () => import("./ha-selector-config-entry"),
|
||||||
conversation_agent: () => import("./ha-selector-conversation-agent"),
|
conversation_agent: () => import("./ha-selector-conversation-agent"),
|
||||||
constant: () => import("./ha-selector-constant"),
|
constant: () => import("./ha-selector-constant"),
|
||||||
|
country: () => import("./ha-selector-country"),
|
||||||
date: () => import("./ha-selector-date"),
|
date: () => import("./ha-selector-date"),
|
||||||
datetime: () => import("./ha-selector-datetime"),
|
datetime: () => import("./ha-selector-datetime"),
|
||||||
device: () => import("./ha-selector-device"),
|
device: () => import("./ha-selector-device"),
|
||||||
|
@ -2,9 +2,9 @@ import "@material/mwc-button/mwc-button";
|
|||||||
import {
|
import {
|
||||||
mdiBell,
|
mdiBell,
|
||||||
mdiCalendar,
|
mdiCalendar,
|
||||||
mdiCart,
|
|
||||||
mdiCellphoneCog,
|
mdiCellphoneCog,
|
||||||
mdiChartBox,
|
mdiChartBox,
|
||||||
|
mdiClipboardList,
|
||||||
mdiClose,
|
mdiClose,
|
||||||
mdiCog,
|
mdiCog,
|
||||||
mdiFormatListBulletedType,
|
mdiFormatListBulletedType,
|
||||||
@ -50,7 +50,7 @@ import { subscribeRepairsIssueRegistry } from "../data/repairs";
|
|||||||
import { UpdateEntity, updateCanInstall } from "../data/update";
|
import { UpdateEntity, updateCanInstall } from "../data/update";
|
||||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||||
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
||||||
import { SortableInstance, loadSortable } from "../resources/sortable.ondemand";
|
import type { SortableInstance } from "../resources/sortable";
|
||||||
import { haStyleScrollbar } from "../resources/styles";
|
import { haStyleScrollbar } from "../resources/styles";
|
||||||
import type { HomeAssistant, PanelInfo, Route } from "../types";
|
import type { HomeAssistant, PanelInfo, Route } from "../types";
|
||||||
import "./ha-icon";
|
import "./ha-icon";
|
||||||
@ -81,7 +81,7 @@ const PANEL_ICONS = {
|
|||||||
lovelace: mdiViewDashboard,
|
lovelace: mdiViewDashboard,
|
||||||
map: mdiTooltipAccount,
|
map: mdiTooltipAccount,
|
||||||
"media-browser": mdiPlayBoxMultiple,
|
"media-browser": mdiPlayBoxMultiple,
|
||||||
"shopping-list": mdiCart,
|
todo: mdiClipboardList,
|
||||||
};
|
};
|
||||||
|
|
||||||
const panelSorter = (
|
const panelSorter = (
|
||||||
@ -689,7 +689,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _createSortable() {
|
private async _createSortable() {
|
||||||
const Sortable = await loadSortable();
|
const Sortable = (await import("../resources/sortable")).default;
|
||||||
this._sortable = new Sortable(
|
this._sortable = new Sortable(
|
||||||
this.shadowRoot!.getElementById("sortable")!,
|
this.shadowRoot!.getElementById("sortable")!,
|
||||||
{
|
{
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
import "@polymer/paper-slider";
|
|
||||||
|
|
||||||
const PaperSliderClass = customElements.get("paper-slider");
|
|
||||||
let subTemplate;
|
|
||||||
|
|
||||||
export class HaSlider extends PaperSliderClass {
|
|
||||||
static get template() {
|
|
||||||
if (!subTemplate) {
|
|
||||||
subTemplate = PaperSliderClass.template.cloneNode(true);
|
|
||||||
|
|
||||||
const superStyle = subTemplate.content.querySelector("style");
|
|
||||||
|
|
||||||
// append style to add mirroring of pin in RTL
|
|
||||||
superStyle.appendChild(
|
|
||||||
document.createTextNode(`
|
|
||||||
:host([dir="rtl"]) #sliderContainer.pin.expand > .slider-knob > .slider-knob-inner::after {
|
|
||||||
-webkit-transform: scale(1) translate(0, -17px) scaleX(-1) !important;
|
|
||||||
transform: scale(1) translate(0, -17px) scaleX(-1) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pin > .slider-knob > .slider-knob-inner {
|
|
||||||
font-size: var(--ha-slider-pin-font-size, 15px);
|
|
||||||
line-height: normal;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.disabled.ring > .slider-knob > .slider-knob-inner {
|
|
||||||
background-color: var(--paper-slider-disabled-knob-color, var(--disabled-text-color));
|
|
||||||
border: 2px solid var(--paper-slider-disabled-knob-color, var(--disabled-text-color));
|
|
||||||
}
|
|
||||||
|
|
||||||
.pin > .slider-knob > .slider-knob-inner::before {
|
|
||||||
top: unset;
|
|
||||||
margin-left: unset;
|
|
||||||
|
|
||||||
bottom: calc(15px + var(--calculated-paper-slider-height)/2);
|
|
||||||
left: 50%;
|
|
||||||
width: 2.6em;
|
|
||||||
height: 2.6em;
|
|
||||||
|
|
||||||
-webkit-transform-origin: left bottom;
|
|
||||||
transform-origin: left bottom;
|
|
||||||
-webkit-transform: rotate(-45deg) scale(0) translate(0);
|
|
||||||
transform: rotate(-45deg) scale(0) translate(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pin.expand > .slider-knob > .slider-knob-inner::before {
|
|
||||||
-webkit-transform: rotate(-45deg) scale(1) translate(7px, -7px);
|
|
||||||
transform: rotate(-45deg) scale(1) translate(7px, -7px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pin > .slider-knob > .slider-knob-inner::after {
|
|
||||||
top: unset;
|
|
||||||
font-size: unset;
|
|
||||||
|
|
||||||
bottom: calc(15px + var(--calculated-paper-slider-height)/2);
|
|
||||||
left: 50%;
|
|
||||||
margin-left: -1.3em;
|
|
||||||
width: 2.6em;
|
|
||||||
height: 2.5em;
|
|
||||||
|
|
||||||
-webkit-transform-origin: center bottom;
|
|
||||||
transform-origin: center bottom;
|
|
||||||
-webkit-transform: scale(0) translate(0);
|
|
||||||
transform: scale(0) translate(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pin.expand > .slider-knob > .slider-knob-inner::after {
|
|
||||||
-webkit-transform: scale(1) translate(0, -10px);
|
|
||||||
transform: scale(1) translate(0, -10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-input {
|
|
||||||
width: 54px;
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return subTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
_setImmediateValue(newImmediateValue) {
|
|
||||||
super._setImmediateValue(
|
|
||||||
this.step >= 1
|
|
||||||
? Math.round(newImmediateValue)
|
|
||||||
: Math.round(newImmediateValue * 100) / 100
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_calcStep(value) {
|
|
||||||
if (!this.step) {
|
|
||||||
return parseFloat(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const numSteps = Math.round((value - this.min) / this.step);
|
|
||||||
const stepStr = this.step.toString();
|
|
||||||
const stepPointAt = stepStr.indexOf(".");
|
|
||||||
if (stepPointAt !== -1) {
|
|
||||||
/**
|
|
||||||
* For small values of this.step, if we calculate the step using
|
|
||||||
* For non-integer values of this.step, if we calculate the step using
|
|
||||||
* `Math.round(value / step) * step` we may hit a precision point issue
|
|
||||||
* eg. 0.1 * 0.2 = 0.020000000000000004
|
|
||||||
* http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
|
|
||||||
*
|
|
||||||
* as a work around we can round with the decimal precision of `step`
|
|
||||||
*/
|
|
||||||
const precision = 10 ** (stepStr.length - stepPointAt - 1);
|
|
||||||
return (
|
|
||||||
Math.round((numSteps * this.step + this.min) * precision) / precision
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return numSteps * this.step + this.min;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
customElements.define("ha-slider", HaSlider);
|
|
26
src/components/ha-slider.ts
Normal file
26
src/components/ha-slider.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { customElement } from "lit/decorators";
|
||||||
|
import { MdSlider } from "@material/web/slider/slider";
|
||||||
|
import { CSSResult, css } from "lit";
|
||||||
|
|
||||||
|
@customElement("ha-slider")
|
||||||
|
export class HaSlider extends MdSlider {
|
||||||
|
static override styles: CSSResult[] = [
|
||||||
|
...MdSlider.styles,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
--md-sys-color-primary: var(--primary-color);
|
||||||
|
--md-sys-color-outline: var(--outline-color);
|
||||||
|
|
||||||
|
min-width: 100px;
|
||||||
|
min-inline-size: 100px;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-slider": HaSlider;
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,19 @@
|
|||||||
import { css, CSSResultGroup, LitElement, svg, SVGTemplateResult } from "lit";
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
LitElement,
|
||||||
|
nothing,
|
||||||
|
svg,
|
||||||
|
SVGTemplateResult,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
|
||||||
@customElement("ha-svg-icon")
|
@customElement("ha-svg-icon")
|
||||||
export class HaSvgIcon extends LitElement {
|
export class HaSvgIcon extends LitElement {
|
||||||
@property() public path?: string;
|
@property() public path?: string;
|
||||||
|
|
||||||
|
@property() public secondaryPath?: string;
|
||||||
|
|
||||||
@property() public viewBox?: string;
|
@property() public viewBox?: string;
|
||||||
|
|
||||||
protected render(): SVGTemplateResult {
|
protected render(): SVGTemplateResult {
|
||||||
@ -13,11 +22,20 @@ export class HaSvgIcon extends LitElement {
|
|||||||
viewBox=${this.viewBox || "0 0 24 24"}
|
viewBox=${this.viewBox || "0 0 24 24"}
|
||||||
preserveAspectRatio="xMidYMid meet"
|
preserveAspectRatio="xMidYMid meet"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
role="img"
|
role="img"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<g>
|
<g>
|
||||||
${this.path ? svg`<path d=${this.path}></path>` : ""}
|
${
|
||||||
|
this.path
|
||||||
|
? svg`<path class="primary-path" d=${this.path}></path>`
|
||||||
|
: nothing
|
||||||
|
}
|
||||||
|
${
|
||||||
|
this.secondaryPath
|
||||||
|
? svg`<path class="secondary-path" d=${this.secondaryPath}></path>`
|
||||||
|
: nothing
|
||||||
|
}
|
||||||
</g>
|
</g>
|
||||||
</svg>`;
|
</svg>`;
|
||||||
}
|
}
|
||||||
@ -30,7 +48,7 @@ export class HaSvgIcon extends LitElement {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
fill: currentcolor;
|
fill: var(--icon-primary-color, currentcolor);
|
||||||
width: var(--mdc-icon-size, 24px);
|
width: var(--mdc-icon-size, 24px);
|
||||||
height: var(--mdc-icon-size, 24px);
|
height: var(--mdc-icon-size, 24px);
|
||||||
}
|
}
|
||||||
@ -40,6 +58,13 @@ export class HaSvgIcon extends LitElement {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
path.primary-path {
|
||||||
|
opacity: var(--icon-primary-opactity, 1);
|
||||||
|
}
|
||||||
|
path.secondary-path {
|
||||||
|
fill: var(--icon-secondary-color, currentcolor);
|
||||||
|
opacity: var(--icon-secondary-opactity, 0.5);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
320
src/components/ha-two-pane-top-app-bar-fixed.ts
Normal file
320
src/components/ha-two-pane-top-app-bar-fixed.ts
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
import {
|
||||||
|
addHasRemoveClass,
|
||||||
|
BaseElement,
|
||||||
|
} from "@material/mwc-base/base-element";
|
||||||
|
import { supportsPassiveEventListener } from "@material/mwc-base/utils";
|
||||||
|
import { MDCTopAppBarAdapter } from "@material/top-app-bar/adapter";
|
||||||
|
import { strings } from "@material/top-app-bar/constants";
|
||||||
|
import MDCFixedTopAppBarFoundation from "@material/top-app-bar/fixed/foundation";
|
||||||
|
import { html, css, nothing } from "lit";
|
||||||
|
import { property, query, customElement } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import { styles } from "@material/mwc-top-app-bar/mwc-top-app-bar.css";
|
||||||
|
import { haStyleScrollbar } from "../resources/styles";
|
||||||
|
|
||||||
|
export const passiveEventOptionsIfSupported = supportsPassiveEventListener
|
||||||
|
? { passive: true }
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
@customElement("ha-two-pane-top-app-bar-fixed")
|
||||||
|
export abstract class TopAppBarBaseBase extends BaseElement {
|
||||||
|
protected override mdcFoundation!: MDCFixedTopAppBarFoundation;
|
||||||
|
|
||||||
|
protected override mdcFoundationClass = MDCFixedTopAppBarFoundation;
|
||||||
|
|
||||||
|
@query(".mdc-top-app-bar") protected mdcRoot!: HTMLElement;
|
||||||
|
|
||||||
|
// _actionItemsSlot should have type HTMLSlotElement, but when TypeScript's
|
||||||
|
// emitDecoratorMetadata is enabled, the HTMLSlotElement constructor will
|
||||||
|
// be emitted into the runtime, which will cause an "HTMLSlotElement is
|
||||||
|
// undefined" error in browsers that don't define it (e.g. IE11).
|
||||||
|
@query('slot[name="actionItems"]') protected _actionItemsSlot!: HTMLElement;
|
||||||
|
|
||||||
|
protected _scrollTarget!: HTMLElement | Window;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) centerTitle = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) prominent = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) dense = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) pane = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) footer = false;
|
||||||
|
|
||||||
|
@query(".content") private _contentElement!: HTMLElement;
|
||||||
|
|
||||||
|
@query(".pane .ha-scrollbar") private _paneElement?: HTMLElement;
|
||||||
|
|
||||||
|
@property({ type: Object })
|
||||||
|
get scrollTarget() {
|
||||||
|
return this._scrollTarget || window;
|
||||||
|
}
|
||||||
|
|
||||||
|
set scrollTarget(value) {
|
||||||
|
this.unregisterListeners();
|
||||||
|
const old = this.scrollTarget;
|
||||||
|
this._scrollTarget = value;
|
||||||
|
this.updateRootPosition();
|
||||||
|
this.requestUpdate("scrollTarget", old);
|
||||||
|
this.registerListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updateRootPosition() {
|
||||||
|
if (this.mdcRoot) {
|
||||||
|
const windowScroller = this.scrollTarget === window;
|
||||||
|
// we add support for top-app-bar's tied to an element scroller.
|
||||||
|
this.mdcRoot.style.position = windowScroller ? "" : "absolute";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected barClasses() {
|
||||||
|
return {
|
||||||
|
"mdc-top-app-bar--dense": this.dense,
|
||||||
|
"mdc-top-app-bar--prominent": this.prominent,
|
||||||
|
"center-title": this.centerTitle,
|
||||||
|
"mdc-top-app-bar--fixed": true,
|
||||||
|
"mdc-top-app-bar--pane": this.pane,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected contentClasses() {
|
||||||
|
return {
|
||||||
|
"mdc-top-app-bar--fixed-adjust": !this.dense && !this.prominent,
|
||||||
|
"mdc-top-app-bar--dense-fixed-adjust": this.dense && !this.prominent,
|
||||||
|
"mdc-top-app-bar--prominent-fixed-adjust": !this.dense && this.prominent,
|
||||||
|
"mdc-top-app-bar--dense-prominent-fixed-adjust":
|
||||||
|
this.dense && this.prominent,
|
||||||
|
"mdc-top-app-bar--pane": this.pane,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override render() {
|
||||||
|
const title = html`<span class="mdc-top-app-bar__title"
|
||||||
|
><slot name="title"></slot
|
||||||
|
></span>`;
|
||||||
|
return html`
|
||||||
|
<header class="mdc-top-app-bar ${classMap(this.barClasses())}">
|
||||||
|
<div class="mdc-top-app-bar__row">
|
||||||
|
${this.pane
|
||||||
|
? html`<section
|
||||||
|
class="mdc-top-app-bar__section mdc-top-app-bar__section--align-start"
|
||||||
|
id="title"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
name="navigationIcon"
|
||||||
|
@click=${this.handleNavigationClick}
|
||||||
|
></slot>
|
||||||
|
${title}
|
||||||
|
</section>`
|
||||||
|
: nothing}
|
||||||
|
<section class="mdc-top-app-bar__section" id="navigation">
|
||||||
|
${this.pane
|
||||||
|
? nothing
|
||||||
|
: html`<slot
|
||||||
|
name="navigationIcon"
|
||||||
|
@click=${this.handleNavigationClick}
|
||||||
|
></slot
|
||||||
|
>${title}`}
|
||||||
|
</section>
|
||||||
|
<section
|
||||||
|
class="mdc-top-app-bar__section mdc-top-app-bar__section--align-end"
|
||||||
|
id="actions"
|
||||||
|
role="toolbar"
|
||||||
|
>
|
||||||
|
<slot name="actionItems"></slot>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class=${classMap(this.contentClasses())}>
|
||||||
|
${this.pane
|
||||||
|
? html`<div class="pane">
|
||||||
|
<div class="shadow-container"></div>
|
||||||
|
<div class="ha-scrollbar">
|
||||||
|
<slot name="pane"></slot>
|
||||||
|
</div>
|
||||||
|
${this.footer
|
||||||
|
? html`<div class="footer">
|
||||||
|
<slot name="pane-footer"></slot>
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
<div class="main">
|
||||||
|
${this.pane ? html`<div class="shadow-container"></div>` : nothing}
|
||||||
|
<div class="content">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProperties) {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
if (
|
||||||
|
changedProperties.has("pane") &&
|
||||||
|
changedProperties.get("pane") !== undefined
|
||||||
|
) {
|
||||||
|
this.unregisterListeners();
|
||||||
|
this.registerListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createAdapter(): MDCTopAppBarAdapter {
|
||||||
|
return {
|
||||||
|
...addHasRemoveClass(this.mdcRoot),
|
||||||
|
setStyle: (prprty: string, value: string) =>
|
||||||
|
this.mdcRoot.style.setProperty(prprty, value),
|
||||||
|
getTopAppBarHeight: () => this.mdcRoot.clientHeight,
|
||||||
|
notifyNavigationIconClicked: () => {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new Event(strings.NAVIGATION_EVENT, {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
getViewportScrollY: () =>
|
||||||
|
this.scrollTarget instanceof Window
|
||||||
|
? this.scrollTarget.pageYOffset
|
||||||
|
: this.scrollTarget.scrollTop,
|
||||||
|
getTotalActionItems: () =>
|
||||||
|
(this._actionItemsSlot as HTMLSlotElement).assignedNodes({
|
||||||
|
flatten: true,
|
||||||
|
}).length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleTargetScroll = () => {
|
||||||
|
this.mdcFoundation.handleTargetScroll();
|
||||||
|
};
|
||||||
|
|
||||||
|
protected handlePaneScroll = (ev) => {
|
||||||
|
if (ev.target.scrollTop > 0) {
|
||||||
|
ev.target.parentElement.classList.add("scrolled");
|
||||||
|
} else {
|
||||||
|
ev.target.parentElement.classList.remove("scrolled");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected handleNavigationClick = () => {
|
||||||
|
this.mdcFoundation.handleNavigationClick();
|
||||||
|
};
|
||||||
|
|
||||||
|
protected registerListeners() {
|
||||||
|
if (this.pane) {
|
||||||
|
this._paneElement!.addEventListener(
|
||||||
|
"scroll",
|
||||||
|
this.handlePaneScroll,
|
||||||
|
passiveEventOptionsIfSupported
|
||||||
|
);
|
||||||
|
this._contentElement.addEventListener(
|
||||||
|
"scroll",
|
||||||
|
this.handlePaneScroll,
|
||||||
|
passiveEventOptionsIfSupported
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.scrollTarget.addEventListener(
|
||||||
|
"scroll",
|
||||||
|
this.handleTargetScroll,
|
||||||
|
passiveEventOptionsIfSupported
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected unregisterListeners() {
|
||||||
|
this._paneElement?.removeEventListener("scroll", this.handlePaneScroll);
|
||||||
|
this._contentElement.removeEventListener("scroll", this.handlePaneScroll);
|
||||||
|
this.scrollTarget.removeEventListener("scroll", this.handleTargetScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override firstUpdated() {
|
||||||
|
super.firstUpdated();
|
||||||
|
this.updateRootPosition();
|
||||||
|
this.registerListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
override disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this.unregisterListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
static override styles = [
|
||||||
|
styles,
|
||||||
|
haStyleScrollbar,
|
||||||
|
css`
|
||||||
|
.mdc-top-app-bar__row {
|
||||||
|
height: var(--header-height);
|
||||||
|
border-bottom: var(--app-header-border-bottom);
|
||||||
|
}
|
||||||
|
.mdc-top-app-bar--fixed-adjust {
|
||||||
|
padding-top: var(--header-height);
|
||||||
|
}
|
||||||
|
.shadow-container {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(-1 * var(--header-height));
|
||||||
|
width: 100%;
|
||||||
|
height: var(--header-height);
|
||||||
|
z-index: 1;
|
||||||
|
transition: box-shadow 200ms linear;
|
||||||
|
}
|
||||||
|
.scrolled .shadow-container {
|
||||||
|
box-shadow: var(
|
||||||
|
--mdc-top-app-bar-fixed-box-shadow,
|
||||||
|
0px 2px 4px -1px rgba(0, 0, 0, 0.2),
|
||||||
|
0px 4px 5px 0px rgba(0, 0, 0, 0.14),
|
||||||
|
0px 1px 10px 0px rgba(0, 0, 0, 0.12)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
.mdc-top-app-bar {
|
||||||
|
--mdc-typography-headline6-font-weight: 400;
|
||||||
|
color: var(--app-header-text-color, var(--mdc-theme-on-primary, #fff));
|
||||||
|
background-color: var(
|
||||||
|
--app-header-background-color,
|
||||||
|
var(--mdc-theme-primary)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
.mdc-top-app-bar--pane.mdc-top-app-bar--fixed-scrolled {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
#title {
|
||||||
|
border-right: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex: 0 0 var(--sidepane-width, 250px);
|
||||||
|
width: var(--sidepane-width, 250px);
|
||||||
|
}
|
||||||
|
div.mdc-top-app-bar--pane {
|
||||||
|
display: flex;
|
||||||
|
height: calc(100vh - var(--header-height));
|
||||||
|
}
|
||||||
|
.pane {
|
||||||
|
border-right: 1px solid var(--divider-color);
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 var(--sidepane-width, 250px);
|
||||||
|
width: var(--sidepane-width, 250px);
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.pane .ha-scrollbar {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.pane .footer {
|
||||||
|
border-top: 1px solid var(--divider-color);
|
||||||
|
}
|
||||||
|
.main {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
.mdc-top-app-bar--pane .main {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.mdc-top-app-bar--pane .content {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
@ -298,7 +298,7 @@ export class HatScriptGraph extends LitElement {
|
|||||||
.notEnabled=${disabled || config.enabled === false}
|
.notEnabled=${disabled || config.enabled === false}
|
||||||
nofocus
|
nofocus
|
||||||
></hat-graph-node>
|
></hat-graph-node>
|
||||||
${ensureArray(config.then).map((action, j) =>
|
${ensureArray(config.then ?? []).map((action, j) =>
|
||||||
this.render_action_node(
|
this.render_action_node(
|
||||||
action,
|
action,
|
||||||
`${path}/then/${j}`,
|
`${path}/then/${j}`,
|
||||||
|
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