Compare commits
367 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b065799bf | |||
| ca94267c44 | |||
| df1f26cee7 | |||
| 24a4e075e6 | |||
| 43fcc6238e | |||
| b40b96248b | |||
| c7ac4c7490 | |||
| 6cd8471b91 | |||
| 940eaa26e0 | |||
| 79e68ce125 | |||
| e581d35432 | |||
| d3d578e0f4 | |||
| 79c71cbe48 | |||
| 82b50a1c5d | |||
| 6cfda78aa1 | |||
| f9ff938775 | |||
| 778fcab90d | |||
| 07e5aa30c6 | |||
| 3f0ec03a14 | |||
| 1bb871b9ac | |||
| 0e8783fb01 | |||
| 1d88c4465b | |||
| af2d575bf0 | |||
| 92165d776a | |||
| a8bbd8ab90 | |||
| 43ac9dbea7 | |||
| bba9eca4e9 | |||
| 40f65b1980 | |||
| 23a33b10a1 | |||
| 67a93013c7 | |||
| 1f838d7529 | |||
| ffc0435144 | |||
| 5877d69c87 | |||
| 99035cea8f | |||
| 1b441a7eec | |||
| ad49e9f7b0 | |||
| e32b15ede2 | |||
| a35b4376ea | |||
| 619f9f76ee | |||
| f771bc10db | |||
| b8889a1183 | |||
| eb6b45eaed | |||
| 31a748ed93 | |||
| 0110bdd24a | |||
| 365b712976 | |||
| 7d97dbe15b | |||
| 8bc0ea5a0b | |||
| 44948a3474 | |||
| bc51b53b4a | |||
| 67217b9dd0 | |||
| 487795b7c4 | |||
| a30e0d33f9 | |||
| 0c1b8abe03 | |||
| ce9c5149d5 | |||
| adbcdc62eb | |||
| faf872bfb8 | |||
| fe0fb2382a | |||
| cdd29295e5 | |||
| f7532f3476 | |||
| c8930cec87 | |||
| f9c336890d | |||
| c721de109f | |||
| 1c95e8d6ec | |||
| 57cf2c1341 | |||
| f7d5c5f850 | |||
| 470f5127f4 | |||
| 34e361601a | |||
| 70d6cce8f8 | |||
| f9814f35d1 | |||
| f30603753e | |||
| ce9993fd36 | |||
| 4c2044e70a | |||
| 7f96c1fbe1 | |||
| 75e24780c1 | |||
| 95580bc4c0 | |||
| 175f68e0cf | |||
| b6efedfc8d | |||
| 23c21a35d8 | |||
| 9c7324298b | |||
| e92be566a0 | |||
| 4e96ad5f28 | |||
| f64a1500af | |||
| c9e8619c04 | |||
| 7ab1133b45 | |||
| 77abfd3e61 | |||
| d7aaa41aa4 | |||
| 8223f6b155 | |||
| 435eae77fa | |||
| ead54e445f | |||
| 7ee5db2be5 | |||
| fef6f0ac94 | |||
| 7a60763786 | |||
| 94e321a364 | |||
| 1c12c2b714 | |||
| 442a8f11a7 | |||
| 4e8b58cd6c | |||
| a92dab46c2 | |||
| 468660d235 | |||
| c721afa137 | |||
| ac9654c1de | |||
| 570ad38bac | |||
| e778a9aa1d | |||
| 49576189af | |||
| 5d71d4c0a1 | |||
| d334b1ca7b | |||
| 5551e98388 | |||
| 59945cb2f8 | |||
| 500bc959f0 | |||
| deece20206 | |||
| fc8945be60 | |||
| 3fbd5f07a9 | |||
| ff9af2f980 | |||
| 62cba99491 | |||
| 5a5005c09c | |||
| dd179e1f4e | |||
| 27bdf80168 | |||
| f70ce7491a | |||
| 9f17f6a8cf | |||
| 4e51c7cf96 | |||
| 291c026da0 | |||
| dd88d8633f | |||
| 254ee8568b | |||
| cd631e8693 | |||
| 765812331b | |||
| 7462f8fbe3 | |||
| dc940f248c | |||
| 2793ca65cd | |||
| e687ddab21 | |||
| 4bd27e5055 | |||
| c6e2e07286 | |||
| e77508b8a8 | |||
| a5db44a167 | |||
| 265bbfc95d | |||
| 305cecb213 | |||
| 813feff12e | |||
| cbce6f633f | |||
| 1bbf45d35e | |||
| 76e53e9738 | |||
| c30e4a6935 | |||
| c4a700a55c | |||
| a759767d79 | |||
| 7f868c8140 | |||
| f7f37c24e2 | |||
| be02a8869f | |||
| 3a9f09cb47 | |||
| 0c2a9d85e0 | |||
| e72356033c | |||
| 3c48559df6 | |||
| f36d68c677 | |||
| af46b8221e | |||
| d25f72524b | |||
| 0840d8a10e | |||
| 597bf5def0 | |||
| 3478bd309b | |||
| 64b8b7658d | |||
| a1af8718a0 | |||
| fd9e2b647d | |||
| caee4ba7bc | |||
| 915036006d | |||
| 48887f2066 | |||
| 68d9ce7923 | |||
| a36f3c8fb1 | |||
| 4dfadea9e9 | |||
| 71dc26edab | |||
| f260c95add | |||
| dc6f1efffb | |||
| b7763882f4 | |||
| 7de5c46f14 | |||
| 5920efa2b2 | |||
| d2194d55f9 | |||
| c0043af4c9 | |||
| dcf763438b | |||
| 858a00e28c | |||
| ab407e8274 | |||
| 14f96a6262 | |||
| 2b33c70e04 | |||
| 717443e2d6 | |||
| 2aba9099a0 | |||
| 3079f126a8 | |||
| 1cdfb746bf | |||
| 39a1844991 | |||
| 9e4dc0d39e | |||
| ab91a4b814 | |||
| ca66c02fb3 | |||
| 97bb052d71 | |||
| 137bb473c0 | |||
| 326b57f91b | |||
| 32feab6a70 | |||
| 68a0d04f04 | |||
| 9078ab4026 | |||
| 8605684906 | |||
| 9f17d17d6e | |||
| ba5f176d52 | |||
| 7115d14699 | |||
| 23e37daff3 | |||
| ed6c2dfe39 | |||
| b48a28f2a6 | |||
| 3166fec7db | |||
| 1a67bd0414 | |||
| d34c43e292 | |||
| c7cfbb5b6c | |||
| bde2fd8202 | |||
| e5327c0903 | |||
| 1a0ca1b78f | |||
| ed141b1d12 | |||
| 5a7a71c551 | |||
| f09e0d187b | |||
| 7f6325fa5e | |||
| de292a8143 | |||
| 84b2005844 | |||
| 0d93432a2c | |||
| 8bc9927ee2 | |||
| 484bed4dab | |||
| 3d7e243707 | |||
| f8a432c89e | |||
| d484b2f63d | |||
| 30d9186031 | |||
| cd74367acc | |||
| 618cd9d9e5 | |||
| 0ff2f1bf75 | |||
| d28f1f07e7 | |||
| 6aa5bc2d8b | |||
| 76fc0c7ab1 | |||
| 7aa7019386 | |||
| b69f0964c9 | |||
| 2f9b6d000b | |||
| 94f186c436 | |||
| 449f858ac8 | |||
| 91a2f2cf24 | |||
| 2c975d4f41 | |||
| ab534933fc | |||
| e353aaa339 | |||
| 020904f8f6 | |||
| fa8b3f006d | |||
| d9ce20992c | |||
| a09f44dcd2 | |||
| c709059c00 | |||
| 5613df1d01 | |||
| d8013a4db9 | |||
| 216dbc4d41 | |||
| c40751dadd | |||
| f58d3ad670 | |||
| 682f5345cc | |||
| a69771c1f8 | |||
| 22de449dda | |||
| 05a27b9399 | |||
| 32083ea13d | |||
| 18210f35b5 | |||
| 362a6f46fe | |||
| 1c9d411d3a | |||
| 6d84523456 | |||
| 2a18706a13 | |||
| 87b58b0bbd | |||
| 3ebb268b57 | |||
| 2df097cd1b | |||
| 8349e47c17 | |||
| 4913932c97 | |||
| 19f057a51b | |||
| c556742ff4 | |||
| 8b5f731d0c | |||
| 9568677926 | |||
| 93ee5de1b4 | |||
| 2f68ee0efc | |||
| 5a229e3c88 | |||
| 7c5f947865 | |||
| e9cbd54979 | |||
| cf55824899 | |||
| 395586ddeb | |||
| 9c48dbf232 | |||
| 00eb820e36 | |||
| 6b99cda982 | |||
| 9bde0e876d | |||
| 9482fcb04b | |||
| 883ad58f52 | |||
| 11ace6002a | |||
| c416daeb92 | |||
| 061521a979 | |||
| d3f73baa36 | |||
| 3037bf494c | |||
| b4dd953128 | |||
| 430a28f350 | |||
| 0eacf3fdac | |||
| 7f9bf69a08 | |||
| 32cca9e30c | |||
| d7d62307b8 | |||
| 12bfa5dab2 | |||
| c0a728bc66 | |||
| 19e8667349 | |||
| f3688b95d4 | |||
| 01f692f05c | |||
| d652f6382d | |||
| c0f96d9473 | |||
| f1a2af24b3 | |||
| 8501098bd1 | |||
| a235f76985 | |||
| 968c0de141 | |||
| 77d8aff1f4 | |||
| 5e486d9cf0 | |||
| f730761b3f | |||
| 46fc9c1a33 | |||
| 8ed68bf295 | |||
| 5622180d42 | |||
| ef1f9b371d | |||
| 0b79684cf1 | |||
| bc68d8df11 | |||
| ebbade2fb7 | |||
| 2b5f778f2e | |||
| 91fc2383cb | |||
| 1080a8c961 | |||
| 6144049f8c | |||
| 4ad3ad6e93 | |||
| 16e84296da | |||
| 3e1ea8d236 | |||
| 8c9996fc81 | |||
| 336b5fb547 | |||
| 3f0f3affb6 | |||
| 6754b8893b | |||
| 6ec4323c76 | |||
| 20408392d2 | |||
| b030a5d1f0 | |||
| a9f8eb5ab1 | |||
| e26e6d7c2d | |||
| e0c98e4524 | |||
| 2832f501d1 | |||
| d2f2d682a9 | |||
| 54b2121273 | |||
| c5ca731e05 | |||
| fd9c6c5449 | |||
| 39f4355c1a | |||
| 35fed0b0e2 | |||
| f0f0aefca1 | |||
| b60864086f | |||
| 6629f372fc | |||
| ec9a95dde0 | |||
| 3cd0c49c78 | |||
| e1ae95dd9f | |||
| ab38dad156 | |||
| 1ddeca3eeb | |||
| 9c6aef033d | |||
| b23aacef84 | |||
| 7d218c89ae | |||
| ffa96d789f | |||
| f08f455698 | |||
| 5eae163796 | |||
| 9e7f01a009 | |||
| eeffa3561c | |||
| f096eb2031 | |||
| 3aad7431da | |||
| a4f167559c | |||
| 44a0582e83 | |||
| 735560c552 | |||
| 94c28ea534 | |||
| 5b7c20af33 | |||
| 4719775926 | |||
| 5c30c1647c | |||
| 3ba572ee37 | |||
| 0adee7d189 | |||
| b5c2b555bc | |||
| 51b6d7758d | |||
| 0f21dfadf1 | |||
| 54e8a34f21 | |||
| dfbf4abd5d | |||
| 62d8434596 | |||
| d6df228e17 | |||
| 00faa16349 | |||
| 3c92fe4170 | |||
| 02a9d67c7d |
@@ -21,12 +21,12 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -57,12 +57,12 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
||||
@@ -24,9 +24,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
- name: Build resources
|
||||
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
|
||||
- name: Setup lint cache
|
||||
uses: actions/cache@v4.0.2
|
||||
uses: actions/cache@v4.1.1
|
||||
with:
|
||||
path: |
|
||||
node_modules/.cache/prettier
|
||||
@@ -58,9 +58,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -76,9 +76,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
env:
|
||||
IS_TEST: "true"
|
||||
- name: Upload bundle stats
|
||||
uses: actions/upload-artifact@v4.3.4
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: frontend-bundle-stats
|
||||
path: build/stats/*.json
|
||||
@@ -100,9 +100,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
env:
|
||||
IS_TEST: "true"
|
||||
- name: Upload bundle stats
|
||||
uses: actions/upload-artifact@v4.3.4
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: supervisor-bundle-stats
|
||||
path: build/stats/*.json
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
|
||||
@@ -22,12 +22,12 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -58,12 +58,12 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
||||
@@ -16,10 +16,10 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
||||
@@ -21,10 +21,10 @@ jobs:
|
||||
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v5
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -57,14 +57,14 @@ jobs:
|
||||
run: tar -czvf translations.tar.gz translations
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4.3.4
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist/home_assistant_frontend*.whl
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload translations
|
||||
uses: actions/upload-artifact@v4.3.4
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: translations
|
||||
path: translations.tar.gz
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send bundle stats and build information to RelativeCI
|
||||
uses: relative-ci/agent-action@v2.1.11
|
||||
uses: relative-ci/agent-action@v2.1.12
|
||||
with:
|
||||
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
||||
token: ${{ github.token }}
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
|
||||
- name: Verify version
|
||||
uses: home-assistant/actions/helpers/verify-version@master
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.1
|
||||
|
||||
- name: Upload Translations
|
||||
run: |
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
diff --git a/modular/sortable.core.esm.js b/modular/sortable.core.esm.js
|
||||
index 93ba17509e2e8583ab241fea6845fbe714c584a2..de0651ddb5dced30d36f7d764da0dd0b441f523f 100644
|
||||
index 8b5e49b011713c8859c669069fbe85ce53974e1d..6a0afc92787157b8a31c38cc5f67dfa526090a00 100644
|
||||
--- a/modular/sortable.core.esm.js
|
||||
+++ b/modular/sortable.core.esm.js
|
||||
@@ -1461,7 +1461,7 @@ Sortable.prototype = /** @lends Sortable.prototype */{
|
||||
}
|
||||
target = parent; // store last element
|
||||
}
|
||||
- /* jshint boss:true */ while (parent = parent.parentNode);
|
||||
+ /* jshint boss:true */ while (parent = parent.parentNode || parent.getRootNode().host);
|
||||
}
|
||||
_unhideGhostForTarget();
|
||||
}
|
||||
@@ -1781,11 +1781,16 @@ Sortable.prototype = /** @lends Sortable.prototype */{
|
||||
}
|
||||
if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, !!target) !== false) {
|
||||
@@ -33,7 +24,7 @@ index 93ba17509e2e8583ab241fea6845fbe714c584a2..de0651ddb5dced30d36f7d764da0dd0b
|
||||
}
|
||||
parentEl = el; // actualization
|
||||
|
||||
@@ -1802,7 +1807,13 @@ Sortable.prototype = /** @lends Sortable.prototype */{
|
||||
@@ -1802,7 +1807,12 @@ Sortable.prototype = /** @lends Sortable.prototype */{
|
||||
targetRect = getRect(target);
|
||||
if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, false) !== false) {
|
||||
capture();
|
||||
@@ -44,11 +35,10 @@ index 93ba17509e2e8583ab241fea6845fbe714c584a2..de0651ddb5dced30d36f7d764da0dd0b
|
||||
+ catch(err) {
|
||||
+ return completed(false);
|
||||
+ }
|
||||
+
|
||||
parentEl = el; // actualization
|
||||
|
||||
changed();
|
||||
@@ -1849,12 +1860,17 @@ Sortable.prototype = /** @lends Sortable.prototype */{
|
||||
@@ -1849,10 +1859,15 @@ Sortable.prototype = /** @lends Sortable.prototype */{
|
||||
_silent = true;
|
||||
setTimeout(_unsilent, 30);
|
||||
capture();
|
||||
@@ -56,8 +46,6 @@ index 93ba17509e2e8583ab241fea6845fbe714c584a2..de0651ddb5dced30d36f7d764da0dd0b
|
||||
- el.appendChild(dragEl);
|
||||
- } else {
|
||||
- target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
|
||||
- }
|
||||
|
||||
+ try {
|
||||
+ if (after && !nextSibling) {
|
||||
+ el.appendChild(dragEl);
|
||||
@@ -67,7 +55,6 @@ index 93ba17509e2e8583ab241fea6845fbe714c584a2..de0651ddb5dced30d36f7d764da0dd0b
|
||||
+ }
|
||||
+ catch(err) {
|
||||
+ return completed(false);
|
||||
+ }
|
||||
}
|
||||
|
||||
// Undo chrome's scroll adjustment (has no effect on other browsers)
|
||||
if (scrolledPastTop) {
|
||||
scrollBy(scrolledPastTop, 0, scrollBefore - scrolledPastTop.scrollTop);
|
||||
@@ -6,4 +6,4 @@ enableGlobalCache: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.3.1.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.5.0.cjs
|
||||
|
||||
@@ -27,3 +27,5 @@ A complete guide can be found at the following [link](https://www.home-assistant
|
||||
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.
|
||||
|
||||
We use [BrowserStack](https://www.browserstack.com) to test Home Assistant on a large variety of devices.
|
||||
|
||||
[](https://www.openhomefoundation.org/)
|
||||
|
||||
@@ -15,23 +15,29 @@ const brotliOptions = {
|
||||
};
|
||||
const zopfliOptions = { threshold: 150 };
|
||||
|
||||
const compressDistBrotli = (rootDir, modernDir) =>
|
||||
const compressDistBrotli = (rootDir, modernDir, compressServiceWorker = true) =>
|
||||
gulp
|
||||
.src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], {
|
||||
base: rootDir,
|
||||
})
|
||||
.src(
|
||||
[
|
||||
`${modernDir}/**/${filesGlob}`,
|
||||
compressServiceWorker ? `${rootDir}/sw-modern.js` : undefined,
|
||||
].filter(Boolean),
|
||||
{
|
||||
base: rootDir,
|
||||
}
|
||||
)
|
||||
.pipe(brotli(brotliOptions))
|
||||
.pipe(gulp.dest(rootDir));
|
||||
|
||||
const compressDistZopfli = (rootDir, modernDir) =>
|
||||
const compressDistZopfli = (rootDir, modernDir, compressModern = false) =>
|
||||
gulp
|
||||
.src(
|
||||
[
|
||||
`${rootDir}/**/${filesGlob}`,
|
||||
`!${modernDir}/**/${filesGlob}`,
|
||||
`!${rootDir}/sw-modern.js`,
|
||||
compressModern ? undefined : `!${modernDir}/**/${filesGlob}`,
|
||||
`!${rootDir}/{sw-modern,service_worker}.js`,
|
||||
`${rootDir}/{authorize,onboarding}.html`,
|
||||
],
|
||||
].filter(Boolean),
|
||||
{ base: rootDir }
|
||||
)
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
@@ -40,12 +46,20 @@ const compressDistZopfli = (rootDir, modernDir) =>
|
||||
const compressAppBrotli = () =>
|
||||
compressDistBrotli(paths.app_output_root, paths.app_output_latest);
|
||||
const compressHassioBrotli = () =>
|
||||
compressDistBrotli(paths.hassio_output_root, paths.hassio_output_latest);
|
||||
compressDistBrotli(
|
||||
paths.hassio_output_root,
|
||||
paths.hassio_output_latest,
|
||||
false
|
||||
);
|
||||
|
||||
const compressAppZopfli = () =>
|
||||
compressDistZopfli(paths.app_output_root, paths.app_output_latest);
|
||||
const compressHassioZopfli = () =>
|
||||
compressDistZopfli(paths.hassio_output_root, paths.hassio_output_latest);
|
||||
compressDistZopfli(
|
||||
paths.hassio_output_root,
|
||||
paths.hassio_output_latest,
|
||||
true
|
||||
);
|
||||
|
||||
gulp.task("compress-app", gulp.parallel(compressAppBrotli, compressAppZopfli));
|
||||
gulp.task(
|
||||
|
||||
@@ -1,35 +1,76 @@
|
||||
// Tasks to generate entry HTML
|
||||
|
||||
import { getUserAgentRegex } from "browserslist-useragent-regexp";
|
||||
import {
|
||||
applyVersionsToRegexes,
|
||||
compileRegex,
|
||||
getPreUserAgentRegexes,
|
||||
} from "browserslist-useragent-regexp";
|
||||
import fs from "fs-extra";
|
||||
import gulp from "gulp";
|
||||
import { minify } from "html-minifier-terser";
|
||||
import template from "lodash.template";
|
||||
import path from "path";
|
||||
import { dirname, extname, resolve } from "node:path";
|
||||
import { htmlMinifierOptions, terserOptions } from "../bundle.cjs";
|
||||
import env from "../env.cjs";
|
||||
import paths from "../paths.cjs";
|
||||
|
||||
// macOS companion app has no way to obtain the Safari version used by WKWebView,
|
||||
// and it is not in the default user agent string. So we add an additional regex
|
||||
// to serve modern based on a minimum macOS version. We take the minimum Safari
|
||||
// major version from browserslist and manually map that to a supported macOS
|
||||
// version. Note this assumes the user has kept Safari updated.
|
||||
const HA_MACOS_REGEX =
|
||||
/Home Assistant\/[\d.]+ \(.+; macOS (\d+)\.(\d+)(?:\.(\d+))?\)/;
|
||||
const SAFARI_TO_MACOS = {
|
||||
15: [10, 15, 0],
|
||||
16: [11, 0, 0],
|
||||
17: [12, 0, 0],
|
||||
18: [13, 0, 0],
|
||||
};
|
||||
|
||||
const getCommonTemplateVars = () => {
|
||||
const browserRegexes = getPreUserAgentRegexes({
|
||||
env: "modern",
|
||||
allowHigherVersions: true,
|
||||
mobileToDesktop: true,
|
||||
throwOnMissing: true,
|
||||
});
|
||||
const minSafariVersion = browserRegexes.find(
|
||||
(regex) => regex.family === "safari"
|
||||
)?.matchedVersions[0][0];
|
||||
const minMacOSVersion = SAFARI_TO_MACOS[minSafariVersion];
|
||||
if (!minMacOSVersion) {
|
||||
throw Error(
|
||||
`Could not find minimum MacOS version for Safari ${minSafariVersion}.`
|
||||
);
|
||||
}
|
||||
const haMacOSRegex = applyVersionsToRegexes(
|
||||
[
|
||||
{
|
||||
family: "ha_macos",
|
||||
regex: HA_MACOS_REGEX,
|
||||
matchedVersions: [minMacOSVersion],
|
||||
requestVersions: [minMacOSVersion],
|
||||
},
|
||||
],
|
||||
{ ignorePatch: true, allowHigherVersions: true }
|
||||
);
|
||||
return {
|
||||
useRollup: env.useRollup(),
|
||||
useWDS: env.useWDS(),
|
||||
modernRegex: compileRegex(browserRegexes.concat(haMacOSRegex)).toString(),
|
||||
};
|
||||
};
|
||||
|
||||
const renderTemplate = (templateFile, data = {}) => {
|
||||
const compiled = template(
|
||||
fs.readFileSync(templateFile, { encoding: "utf-8" })
|
||||
);
|
||||
return compiled({
|
||||
...data,
|
||||
useRollup: env.useRollup(),
|
||||
useWDS: env.useWDS(),
|
||||
modernRegex: getUserAgentRegex({
|
||||
env: "modern",
|
||||
allowHigherVersions: true,
|
||||
mobileToDesktop: true,
|
||||
throwOnMissing: true,
|
||||
}).toString(),
|
||||
// Resolve any child/nested templates relative to the parent and pass the same data
|
||||
renderTemplate: (childTemplate) =>
|
||||
renderTemplate(
|
||||
path.resolve(path.dirname(templateFile), childTemplate),
|
||||
data
|
||||
),
|
||||
renderTemplate(resolve(dirname(templateFile), childTemplate), data),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -63,10 +104,12 @@ const genPagesDevTask =
|
||||
publicRoot = ""
|
||||
) =>
|
||||
async () => {
|
||||
const commonVars = getCommonTemplateVars();
|
||||
for (const [page, entries] of Object.entries(pageEntries)) {
|
||||
const content = renderTemplate(
|
||||
path.resolve(inputRoot, inputSub, `${page}.template`),
|
||||
resolve(inputRoot, inputSub, `${page}.template`),
|
||||
{
|
||||
...commonVars,
|
||||
latestEntryJS: entries.map((entry) =>
|
||||
useWDS
|
||||
? `http://localhost:8000/src/entrypoints/${entry}.ts`
|
||||
@@ -81,7 +124,7 @@ const genPagesDevTask =
|
||||
es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`,
|
||||
}
|
||||
);
|
||||
fs.outputFileSync(path.resolve(outputRoot, page), content);
|
||||
fs.outputFileSync(resolve(outputRoot, page), content);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -98,16 +141,18 @@ const genPagesProdTask =
|
||||
) =>
|
||||
async () => {
|
||||
const latestManifest = fs.readJsonSync(
|
||||
path.resolve(outputLatest, "manifest.json")
|
||||
resolve(outputLatest, "manifest.json")
|
||||
);
|
||||
const es5Manifest = outputES5
|
||||
? fs.readJsonSync(path.resolve(outputES5, "manifest.json"))
|
||||
? fs.readJsonSync(resolve(outputES5, "manifest.json"))
|
||||
: {};
|
||||
const commonVars = getCommonTemplateVars();
|
||||
const minifiedHTML = [];
|
||||
for (const [page, entries] of Object.entries(pageEntries)) {
|
||||
const content = renderTemplate(
|
||||
path.resolve(inputRoot, inputSub, `${page}.template`),
|
||||
resolve(inputRoot, inputSub, `${page}.template`),
|
||||
{
|
||||
...commonVars,
|
||||
latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]),
|
||||
es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]),
|
||||
latestCustomPanelJS: latestManifest["custom-panel.js"],
|
||||
@@ -115,8 +160,8 @@ const genPagesProdTask =
|
||||
}
|
||||
);
|
||||
minifiedHTML.push(
|
||||
minifyHtml(content, path.extname(page)).then((minified) =>
|
||||
fs.outputFileSync(path.resolve(outputRoot, page), minified)
|
||||
minifyHtml(content, extname(page)).then((minified) =>
|
||||
fs.outputFileSync(resolve(outputRoot, page), minified)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,6 +60,12 @@ function copyPolyfills(staticDir) {
|
||||
npmPath("@webcomponents/webcomponentsjs/webcomponents-bundle.js.map"),
|
||||
staticPath("polyfills/")
|
||||
);
|
||||
|
||||
// dialog-polyfill css
|
||||
copyFileDir(
|
||||
npmPath("dialog-polyfill/dialog-polyfill.css"),
|
||||
staticPath("polyfills/")
|
||||
);
|
||||
}
|
||||
|
||||
function copyLoaderJS(staticDir) {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import { deleteAsync } from "del";
|
||||
import gulp from "gulp";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { join, relative } from "node:path";
|
||||
import { mkdir, readFile, symlink, writeFile } from "node:fs/promises";
|
||||
import { basename, join, relative } from "node:path";
|
||||
import { injectManifest } from "workbox-build";
|
||||
import paths from "../paths.cjs";
|
||||
|
||||
@@ -41,10 +41,11 @@ gulp.task("gen-service-worker-app-prod", () =>
|
||||
await readFile(join(outPath, "manifest.json"), "utf-8")
|
||||
);
|
||||
const swSrc = join(paths.app_output_root, manifest["service-worker.js"]);
|
||||
const swDest = join(paths.app_output_root, `sw-${build}.js`);
|
||||
const buildDir = relative(paths.app_output_root, outPath);
|
||||
const { warnings } = await injectManifest({
|
||||
swSrc,
|
||||
swDest: join(paths.app_output_root, `sw-${build}.js`),
|
||||
swDest,
|
||||
injectionPoint: "__WB_MANIFEST__",
|
||||
// Files that mach this pattern will be considered unique and skip revision check
|
||||
// ignore JS files + translation files
|
||||
@@ -76,6 +77,11 @@ gulp.task("gen-service-worker-app-prod", () =>
|
||||
);
|
||||
}
|
||||
await deleteAsync(`${swSrc}?(.map)`);
|
||||
// Needed to install new SW from a cached HTML
|
||||
if (build === "modern") {
|
||||
const swOld = join(paths.app_output_root, "service_worker.js");
|
||||
await symlink(basename(swDest), swOld);
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="section-header">Wat does Home Assistant Cast do?</div>
|
||||
<div class="section-header">What does Home Assistant Cast do?</div>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
Home Assistant Cast is a receiver application for the Chromecast. When
|
||||
|
||||
@@ -36,6 +36,7 @@ import { HassElement } from "../../../../src/state/hass-element";
|
||||
import { castContext } from "../cast_context";
|
||||
import "./hc-launch-screen";
|
||||
import { getPanelTitleFromUrlPath } from "../../../../src/data/panel";
|
||||
import { checkLovelaceConfig } from "../../../../src/panels/lovelace/common/check-lovelace-config";
|
||||
|
||||
const DEFAULT_CONFIG: LovelaceDashboardStrategyConfig = {
|
||||
strategy: {
|
||||
@@ -365,7 +366,9 @@ export class HcMain extends HassElement {
|
||||
this._urlPath || "lovelace"
|
||||
);
|
||||
castContext.setApplicationState(title || "");
|
||||
this._lovelaceConfig = lovelaceConfig;
|
||||
this._lovelaceConfig = checkLovelaceConfig(
|
||||
lovelaceConfig
|
||||
) as LovelaceConfig;
|
||||
}
|
||||
|
||||
private _handleShowDemo(_msg: ShowDemoMessage) {
|
||||
|
||||
@@ -111,9 +111,47 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
|
||||
friendly_name: "Living room Temperature",
|
||||
},
|
||||
},
|
||||
"sensor.living_room_humidity": {
|
||||
entity_id: "sensor.living_room_humidity",
|
||||
state: "57",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
unit_of_measurement: "%",
|
||||
device_class: "humidity",
|
||||
friendly_name: "Living room Humidity",
|
||||
},
|
||||
},
|
||||
"sensor.outdoor_temperature": {
|
||||
entity_id: "sensor.outdoor_temperature",
|
||||
state: "10.5",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
unit_of_measurement: "°C",
|
||||
device_class: "temperature",
|
||||
friendly_name: "Outdoor temperature",
|
||||
},
|
||||
},
|
||||
"sensor.outdoor_humidity": {
|
||||
entity_id: "sensor.outdoor_humidity",
|
||||
state: "70.4",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
unit_of_measurement: "%",
|
||||
device_class: "humidity",
|
||||
friendly_name: "Outdoor humidity",
|
||||
},
|
||||
},
|
||||
"device_tracker.car": {
|
||||
entity_id: "sensor.outdoor_humidity",
|
||||
state: "not_home",
|
||||
attributes: {
|
||||
friendly_name: "Car",
|
||||
icon: "mdi:car",
|
||||
},
|
||||
},
|
||||
"media_player.living_room_nest_mini": {
|
||||
entity_id: "media_player.living_room_nest_mini",
|
||||
state: "on",
|
||||
state: "playing",
|
||||
attributes: {
|
||||
device_class: "speaker",
|
||||
volume_level: 0.18,
|
||||
@@ -161,6 +199,14 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
|
||||
supported_features: 32,
|
||||
},
|
||||
},
|
||||
"binary_sensor.kitchen_motion": {
|
||||
entity_id: "light.kitchen_motion",
|
||||
state: "on",
|
||||
attributes: {
|
||||
device_class: "motion",
|
||||
friendly_name: "Kitchen motion",
|
||||
},
|
||||
},
|
||||
"light.worktop_spotlights": {
|
||||
entity_id: "light.worktop_spotlights",
|
||||
state: "off",
|
||||
@@ -395,6 +441,14 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
|
||||
supported_features: 64063,
|
||||
},
|
||||
},
|
||||
"switch.in_meeting": {
|
||||
entity_id: "switch.in_meeting",
|
||||
state: "on",
|
||||
attributes: {
|
||||
icon: "mdi:laptop-account",
|
||||
friendly_name: "In a meeting",
|
||||
},
|
||||
},
|
||||
"sensor.standing_desk_height": {
|
||||
entity_id: "sensor.standing_desk_height",
|
||||
state: "72",
|
||||
|
||||
@@ -9,17 +9,57 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
title: isFrontpageEmbed ? "Home Assistant" : "Demo",
|
||||
path: "home",
|
||||
icon: "mdi:home-assistant",
|
||||
badges: [
|
||||
{
|
||||
type: "entity",
|
||||
entity: "sensor.outdoor_temperature",
|
||||
color: "red",
|
||||
},
|
||||
{
|
||||
type: "entity",
|
||||
entity: "sensor.outdoor_humidity",
|
||||
color: "indigo",
|
||||
},
|
||||
{
|
||||
type: "entity",
|
||||
entity: "device_tracker.car",
|
||||
},
|
||||
],
|
||||
sections: [
|
||||
...(isFrontpageEmbed
|
||||
? []
|
||||
: [
|
||||
{
|
||||
title: `${localize("ui.panel.page-demo.config.sections.titles.welcome")} 👋`,
|
||||
cards: [{ type: "custom:ha-demo-card" }],
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: `${localize("ui.panel.page-demo.config.sections.titles.welcome")} 👋`,
|
||||
},
|
||||
{ type: "custom:ha-demo-card" },
|
||||
],
|
||||
},
|
||||
]),
|
||||
{
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: localize(
|
||||
"ui.panel.page-demo.config.sections.titles.living_room"
|
||||
),
|
||||
icon: "mdi:sofa",
|
||||
badges: [
|
||||
{
|
||||
type: "entity",
|
||||
entity: "sensor.living_room_temperature",
|
||||
color: "red",
|
||||
},
|
||||
{
|
||||
type: "entity",
|
||||
entity: "sensor.living_room_humidity",
|
||||
color: "indigo",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "light.floor_lamp",
|
||||
@@ -38,13 +78,6 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
type: "tile",
|
||||
entity: "light.bar_lamp",
|
||||
},
|
||||
{
|
||||
graph: "line",
|
||||
type: "sensor",
|
||||
entity: "sensor.living_room_temperature",
|
||||
detail: 1,
|
||||
name: "Temperature",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "cover.living_room_garden_shutter",
|
||||
@@ -55,11 +88,25 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
entity: "media_player.living_room_nest_mini",
|
||||
},
|
||||
],
|
||||
title: `🛋️ ${localize("ui.panel.page-demo.config.sections.titles.living_room")} `,
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: localize(
|
||||
"ui.panel.page-demo.config.sections.titles.kitchen"
|
||||
),
|
||||
icon: "mdi:fridge",
|
||||
badges: [
|
||||
{
|
||||
type: "entity",
|
||||
entity: "binary_sensor.kitchen_motion",
|
||||
show_state: false,
|
||||
color: "blue",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "cover.kitchen_shutter",
|
||||
@@ -90,11 +137,17 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
entity: "media_player.kitchen_nest_audio",
|
||||
},
|
||||
],
|
||||
title: `👩🍳 ${localize("ui.panel.page-demo.config.sections.titles.kitchen")}`,
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: localize(
|
||||
"ui.panel.page-demo.config.sections.titles.energy"
|
||||
),
|
||||
icon: "mdi:transmission-tower",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "binary_sensor.tesla_wall_connector_vehicle_connected",
|
||||
@@ -132,11 +185,17 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
color: "dark-grey",
|
||||
},
|
||||
],
|
||||
title: `⚡️ ${localize("ui.panel.page-demo.config.sections.titles.energy")}`,
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: localize(
|
||||
"ui.panel.page-demo.config.sections.titles.climate"
|
||||
),
|
||||
icon: "mdi:thermometer",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "sun.sun",
|
||||
@@ -169,16 +228,38 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
state_content: ["preset_mode", "current_temperature"],
|
||||
},
|
||||
],
|
||||
title: `🌤️ ${localize("ui.panel.page-demo.config.sections.titles.climate")}`,
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: localize(
|
||||
"ui.panel.page-demo.config.sections.titles.study"
|
||||
),
|
||||
icon: "mdi:desk-lamp",
|
||||
badges: [
|
||||
{
|
||||
type: "entity",
|
||||
entity: "switch.in_meeting",
|
||||
state: "on",
|
||||
state_content: "name",
|
||||
visibility: [
|
||||
{
|
||||
condition: "state",
|
||||
state: "on",
|
||||
entity: "switch.in_meeting",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "cover.study_shutter",
|
||||
name: "Shutter",
|
||||
},
|
||||
|
||||
{
|
||||
type: "tile",
|
||||
entity: "light.study_spotlights",
|
||||
@@ -195,12 +276,23 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
color: "brown",
|
||||
icon: "mdi:desk",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "switch.in_meeting",
|
||||
name: "Meeting mode",
|
||||
},
|
||||
],
|
||||
title: `🧑💻 ${localize("ui.panel.page-demo.config.sections.titles.study")}`,
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: localize(
|
||||
"ui.panel.page-demo.config.sections.titles.outdoor"
|
||||
),
|
||||
icon: "mdi:tree",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "light.outdoor_light",
|
||||
@@ -230,11 +322,17 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
name: "Illuminance",
|
||||
},
|
||||
],
|
||||
title: `🌳 ${localize("ui.panel.page-demo.config.sections.titles.outdoor")}`,
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
cards: [
|
||||
{
|
||||
type: "heading",
|
||||
heading: localize(
|
||||
"ui.panel.page-demo.config.sections.titles.updates"
|
||||
),
|
||||
icon: "mdi:update",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "automation.home_assistant_auto_update",
|
||||
@@ -260,7 +358,6 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
icon: "mdi:home-assistant",
|
||||
},
|
||||
],
|
||||
title: `🎉 ${localize("ui.panel.page-demo.config.sections.titles.updates")}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -63,33 +63,42 @@
|
||||
align-items: center;
|
||||
}
|
||||
#ha-launch-screen svg {
|
||||
width: 170px;
|
||||
width: 112px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
#ha-launch-screen .ha-launch-screen-spacer {
|
||||
#ha-launch-screen .ha-launch-screen-spacer-top {
|
||||
flex: 1;
|
||||
margin-top: calc( 2 * max(env(safe-area-inset-bottom), 48px) + 46px );
|
||||
padding-top: 48px;
|
||||
}
|
||||
#ha-launch-screen .ha-launch-screen-spacer-bottom {
|
||||
flex: 1;
|
||||
padding-top: 48px;
|
||||
}
|
||||
.ohf-logo {
|
||||
color: grey;
|
||||
font-size: 12px;
|
||||
margin-bottom: 16px;
|
||||
margin: max(env(safe-area-inset-bottom), 48px) 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
opacity: .66;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.ohf-logo {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="ha-launch-screen">
|
||||
<div class="ha-launch-screen-spacer"></div>
|
||||
<div class="ha-launch-screen-spacer-top"></div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
|
||||
<path fill="#18BCF2" d="M240 224.762a15 15 0 0 1-15 15H15a15 15 0 0 1-15-15v-90c0-8.25 4.77-19.769 10.61-25.609l98.78-98.7805c5.83-5.83 15.38-5.83 21.21 0l98.79 98.7895c5.83 5.83 10.61 17.36 10.61 25.61v90-.01Z"/>
|
||||
<path fill="#F2F4F9" d="m107.27 239.762-40.63-40.63c-2.09.72-4.32 1.13-6.64 1.13-11.3 0-20.5-9.2-20.5-20.5s9.2-20.5 20.5-20.5 20.5 9.2 20.5 20.5c0 2.33-.41 4.56-1.13 6.65l31.63 31.63v-115.88c-6.8-3.3395-11.5-10.3195-11.5-18.3895 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5c0 8.07-4.7 15.05-11.5 18.3895v81.27l31.46-31.46c-.62-1.96-.96-4.04-.96-6.2 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5-9.2 20.5-20.5 20.5c-2.5 0-4.88-.47-7.09-1.29L129 208.892v30.88z"/>
|
||||
</svg>
|
||||
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div>
|
||||
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer-bottom"></div>
|
||||
<div class="ohf-logo">
|
||||
a project from
|
||||
<img src="/static/icons/ohf.svg" alt="Open Home Foundation" height="32">
|
||||
<img src="/static/images/ohf-badge.svg" alt="Home Assistant is a project by the Open Home Foundation" height="46">
|
||||
</div>
|
||||
</div>
|
||||
<ha-demo></ha-demo>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockConfig = (hass: MockHomeAssistant) => {
|
||||
hass.mockWS("validate_config", () => ({
|
||||
actions: { valid: true },
|
||||
conditions: { valid: true },
|
||||
triggers: { valid: true },
|
||||
}));
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Tag } from "../../../src/data/tag";
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockTags = (hass: MockHomeAssistant) => {
|
||||
hass.mockWS("tag/list", () => [{ id: "my-tag", name: "My Tag" }] as Tag[]);
|
||||
};
|
||||
@@ -532,15 +532,6 @@ export default {
|
||||
last_changed: "2018-07-19T10:44:46.200946+00:00",
|
||||
last_updated: "2018-07-19T10:44:46.200946+00:00",
|
||||
},
|
||||
"mailbox.demomailbox": {
|
||||
entity_id: "mailbox.demomailbox",
|
||||
state: "10",
|
||||
attributes: {
|
||||
friendly_name: "DemoMailbox",
|
||||
},
|
||||
last_changed: "2018-07-19T10:45:16.555210+00:00",
|
||||
last_updated: "2018-07-19T10:45:16.555210+00:00",
|
||||
},
|
||||
"input_select.living_room_preset": {
|
||||
entity_id: "input_select.living_room_preset",
|
||||
state: "Visitors",
|
||||
|
||||
@@ -217,22 +217,22 @@ export const basicTrace: DemoTrace = {
|
||||
id: "1615419646544",
|
||||
alias: "Ensure Party mode",
|
||||
description: "",
|
||||
trigger: [
|
||||
triggers: [
|
||||
{
|
||||
platform: "state",
|
||||
trigger: "state",
|
||||
entity_id: "input_boolean.toggle_1",
|
||||
},
|
||||
],
|
||||
condition: [
|
||||
conditions: [
|
||||
{
|
||||
condition: "template",
|
||||
alias: "Test if Paulus is home",
|
||||
value_template: "{{ true }}",
|
||||
},
|
||||
],
|
||||
action: [
|
||||
actions: [
|
||||
{
|
||||
service: "input_boolean.toggle",
|
||||
action: "input_boolean.toggle",
|
||||
target: {
|
||||
entity_id: "input_boolean.toggle_4",
|
||||
},
|
||||
@@ -268,7 +268,7 @@ export const basicTrace: DemoTrace = {
|
||||
],
|
||||
default: [
|
||||
{
|
||||
service: "input_boolean.toggle",
|
||||
action: "input_boolean.toggle",
|
||||
alias: "Toggle 2",
|
||||
target: {
|
||||
entity_id: "input_boolean.toggle_2",
|
||||
@@ -277,7 +277,7 @@ export const basicTrace: DemoTrace = {
|
||||
],
|
||||
},
|
||||
{
|
||||
service: "input_boolean.toggle",
|
||||
action: "input_boolean.toggle",
|
||||
target: {
|
||||
entity_id: "input_boolean.toggle_4",
|
||||
},
|
||||
|
||||
@@ -31,8 +31,8 @@ export const mockDemoTrace = (
|
||||
],
|
||||
},
|
||||
config: {
|
||||
trigger: [],
|
||||
action: [],
|
||||
triggers: [],
|
||||
actions: [],
|
||||
},
|
||||
context: {
|
||||
id: "abcd",
|
||||
|
||||
@@ -133,17 +133,17 @@ export const motionLightTrace: DemoTrace = {
|
||||
config: {
|
||||
mode: "restart",
|
||||
max_exceeded: "silent",
|
||||
trigger: [
|
||||
triggers: [
|
||||
{
|
||||
platform: "state",
|
||||
trigger: "state",
|
||||
entity_id: "binary_sensor.pauluss_macbook_pro_camera_in_use",
|
||||
from: "off",
|
||||
to: "on",
|
||||
},
|
||||
],
|
||||
action: [
|
||||
actions: [
|
||||
{
|
||||
service: "light.turn_on",
|
||||
action: "light.turn_on",
|
||||
target: {
|
||||
entity_id: "light.elgato_key_light_air",
|
||||
},
|
||||
@@ -162,7 +162,7 @@ export const motionLightTrace: DemoTrace = {
|
||||
delay: 0,
|
||||
},
|
||||
{
|
||||
service: "light.turn_off",
|
||||
action: "light.turn_off",
|
||||
target: {
|
||||
entity_id: "light.elgato_key_light_air",
|
||||
},
|
||||
|
||||
@@ -48,7 +48,7 @@ const ACTIONS = [
|
||||
{
|
||||
wait_for_trigger: [
|
||||
{
|
||||
platform: "state",
|
||||
trigger: "state",
|
||||
entity_id: "input_boolean.toggle_1",
|
||||
},
|
||||
],
|
||||
@@ -121,7 +121,7 @@ const ACTIONS = [
|
||||
];
|
||||
|
||||
const initialAction: Action = {
|
||||
service: "light.turn_on",
|
||||
action: "light.turn_on",
|
||||
target: {
|
||||
entity_id: "light.kitchen",
|
||||
},
|
||||
@@ -142,7 +142,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
||||
<div class="action">
|
||||
<span>
|
||||
${this._action
|
||||
? describeAction(this.hass, [], [], [], this._action)
|
||||
? describeAction(this.hass, [], [], this._action)
|
||||
: "<invalid YAML>"}
|
||||
</span>
|
||||
<ha-yaml-editor
|
||||
@@ -155,7 +155,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
||||
${ACTIONS.map(
|
||||
(conf) => html`
|
||||
<div class="action">
|
||||
<span>${describeAction(this.hass, [], [], [], conf as any)}</span>
|
||||
<span>${describeAction(this.hass, [], [], conf as any)}</span>
|
||||
<pre>${dump(conf)}</pre>
|
||||
</div>
|
||||
`
|
||||
|
||||
@@ -22,46 +22,52 @@ const ENTITIES = [
|
||||
];
|
||||
|
||||
const triggers = [
|
||||
{ platform: "state", entity_id: "light.kitchen", from: "off", to: "on" },
|
||||
{ platform: "mqtt" },
|
||||
{ trigger: "state", entity_id: "light.kitchen", from: "off", to: "on" },
|
||||
{ trigger: "mqtt" },
|
||||
{
|
||||
platform: "geo_location",
|
||||
trigger: "geo_location",
|
||||
source: "test_source",
|
||||
zone: "zone.home",
|
||||
event: "enter",
|
||||
},
|
||||
{ platform: "homeassistant", event: "start" },
|
||||
{ trigger: "homeassistant", event: "start" },
|
||||
{
|
||||
platform: "numeric_state",
|
||||
trigger: "numeric_state",
|
||||
entity_id: "light.kitchen",
|
||||
attribute: "brightness",
|
||||
below: 80,
|
||||
above: 20,
|
||||
},
|
||||
{ platform: "sun", event: "sunset" },
|
||||
{ platform: "time_pattern" },
|
||||
{ platform: "time_pattern", hours: "*", minutes: "/5", seconds: "10" },
|
||||
{ platform: "webhook" },
|
||||
{ platform: "persistent_notification" },
|
||||
{ trigger: "sun", event: "sunset" },
|
||||
{ trigger: "time_pattern" },
|
||||
{ trigger: "time_pattern", hours: "*", minutes: "/5", seconds: "10" },
|
||||
{ trigger: "webhook" },
|
||||
{ trigger: "persistent_notification" },
|
||||
{
|
||||
platform: "zone",
|
||||
trigger: "zone",
|
||||
entity_id: "person.person",
|
||||
zone: "zone.home",
|
||||
event: "enter",
|
||||
},
|
||||
{ platform: "tag" },
|
||||
{ platform: "time", at: "15:32" },
|
||||
{ platform: "template" },
|
||||
{ platform: "conversation", command: "Turn on the lights" },
|
||||
{ trigger: "tag" },
|
||||
{ trigger: "time", at: "15:32" },
|
||||
{ trigger: "template" },
|
||||
{ trigger: "conversation", command: "Turn on the lights" },
|
||||
{
|
||||
platform: "conversation",
|
||||
trigger: "conversation",
|
||||
command: ["Turn on the lights", "Turn the lights on"],
|
||||
},
|
||||
{ platform: "event", event_type: "homeassistant_started" },
|
||||
{ trigger: "event", event_type: "homeassistant_started" },
|
||||
{
|
||||
triggers: [
|
||||
{ trigger: "state", entity_id: "light.kitchen", to: "on" },
|
||||
{ trigger: "state", entity_id: "light.kitchen", to: "off" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const initialTrigger: Trigger = {
|
||||
platform: "state",
|
||||
trigger: "state",
|
||||
entity_id: "light.kitchen",
|
||||
};
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervis
|
||||
import type { ConditionWithShorthand } from "../../../../src/data/automation";
|
||||
import "../../../../src/panels/config/automation/condition/ha-automation-condition";
|
||||
import { HaDeviceCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-device";
|
||||
import { HaLogicalCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-logical";
|
||||
import HaNumericStateCondition from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-numeric_state";
|
||||
import { HaStateCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-state";
|
||||
import { HaSunCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-sun";
|
||||
@@ -19,62 +18,67 @@ import { HaTemplateCondition } from "../../../../src/panels/config/automation/co
|
||||
import { HaTimeCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-time";
|
||||
import { HaTriggerCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-trigger";
|
||||
import { HaZoneCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-zone";
|
||||
import { HaAndCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-and";
|
||||
import { HaOrCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-or";
|
||||
import { HaNotCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-not";
|
||||
|
||||
const SCHEMAS: { name: string; conditions: ConditionWithShorthand[] }[] = [
|
||||
{
|
||||
name: "State",
|
||||
conditions: [{ condition: "state", ...HaStateCondition.defaultConfig }],
|
||||
conditions: [{ ...HaStateCondition.defaultConfig }],
|
||||
},
|
||||
{
|
||||
name: "Numeric State",
|
||||
conditions: [
|
||||
{ condition: "numeric_state", ...HaNumericStateCondition.defaultConfig },
|
||||
],
|
||||
conditions: [{ ...HaNumericStateCondition.defaultConfig }],
|
||||
},
|
||||
{
|
||||
name: "Sun",
|
||||
conditions: [{ condition: "sun", ...HaSunCondition.defaultConfig }],
|
||||
conditions: [{ ...HaSunCondition.defaultConfig }],
|
||||
},
|
||||
{
|
||||
name: "Zone",
|
||||
conditions: [{ condition: "zone", ...HaZoneCondition.defaultConfig }],
|
||||
conditions: [{ ...HaZoneCondition.defaultConfig }],
|
||||
},
|
||||
{
|
||||
name: "Time",
|
||||
conditions: [{ condition: "time", ...HaTimeCondition.defaultConfig }],
|
||||
conditions: [{ ...HaTimeCondition.defaultConfig }],
|
||||
},
|
||||
{
|
||||
name: "Template",
|
||||
conditions: [
|
||||
{ condition: "template", ...HaTemplateCondition.defaultConfig },
|
||||
],
|
||||
conditions: [{ ...HaTemplateCondition.defaultConfig }],
|
||||
},
|
||||
{
|
||||
name: "Device",
|
||||
conditions: [{ condition: "device", ...HaDeviceCondition.defaultConfig }],
|
||||
conditions: [{ ...HaDeviceCondition.defaultConfig }],
|
||||
},
|
||||
{
|
||||
name: "And",
|
||||
conditions: [{ condition: "and", ...HaLogicalCondition.defaultConfig }],
|
||||
conditions: [{ ...HaAndCondition.defaultConfig }],
|
||||
},
|
||||
{
|
||||
name: "Or",
|
||||
conditions: [{ condition: "or", ...HaLogicalCondition.defaultConfig }],
|
||||
conditions: [{ ...HaOrCondition.defaultConfig }],
|
||||
},
|
||||
{
|
||||
name: "Not",
|
||||
conditions: [{ condition: "not", ...HaLogicalCondition.defaultConfig }],
|
||||
conditions: [{ ...HaNotCondition.defaultConfig }],
|
||||
},
|
||||
{
|
||||
name: "Trigger",
|
||||
conditions: [{ condition: "trigger", ...HaTriggerCondition.defaultConfig }],
|
||||
conditions: [{ ...HaTriggerCondition.defaultConfig }],
|
||||
},
|
||||
{
|
||||
name: "Shorthand",
|
||||
conditions: [
|
||||
{ and: HaLogicalCondition.defaultConfig.conditions },
|
||||
{ or: HaLogicalCondition.defaultConfig.conditions },
|
||||
{ not: HaLogicalCondition.defaultConfig.conditions },
|
||||
{
|
||||
...HaAndCondition.defaultConfig,
|
||||
},
|
||||
{
|
||||
...HaOrCondition.defaultConfig,
|
||||
},
|
||||
{
|
||||
...HaNotCondition.defaultConfig,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -8,6 +8,9 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
import { mockConfig } from "../../../../demo/src/stubs/config";
|
||||
import { mockTags } from "../../../../demo/src/stubs/tags";
|
||||
import { mockAuth } from "../../../../demo/src/stubs/auth";
|
||||
import type { Trigger } from "../../../../src/data/automation";
|
||||
import { HaGeolocationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location";
|
||||
import { HaEventTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-event";
|
||||
@@ -26,59 +29,53 @@ import { HaStateTrigger } from "../../../../src/panels/config/automation/trigger
|
||||
import { HaMQTTTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt";
|
||||
import "../../../../src/panels/config/automation/trigger/ha-automation-trigger";
|
||||
import { HaConversationTrigger } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-conversation";
|
||||
import { HaTriggerList } from "../../../../src/panels/config/automation/trigger/types/ha-automation-trigger-list";
|
||||
|
||||
const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
|
||||
{
|
||||
name: "State",
|
||||
triggers: [{ platform: "state", ...HaStateTrigger.defaultConfig }],
|
||||
triggers: [{ ...HaStateTrigger.defaultConfig }],
|
||||
},
|
||||
|
||||
{
|
||||
name: "MQTT",
|
||||
triggers: [{ platform: "mqtt", ...HaMQTTTrigger.defaultConfig }],
|
||||
triggers: [{ ...HaMQTTTrigger.defaultConfig }],
|
||||
},
|
||||
|
||||
{
|
||||
name: "GeoLocation",
|
||||
triggers: [
|
||||
{ platform: "geo_location", ...HaGeolocationTrigger.defaultConfig },
|
||||
],
|
||||
triggers: [{ ...HaGeolocationTrigger.defaultConfig }],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Home Assistant",
|
||||
triggers: [{ platform: "homeassistant", ...HaHassTrigger.defaultConfig }],
|
||||
triggers: [{ ...HaHassTrigger.defaultConfig }],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Numeric State",
|
||||
triggers: [
|
||||
{ platform: "numeric_state", ...HaNumericStateTrigger.defaultConfig },
|
||||
],
|
||||
triggers: [{ ...HaNumericStateTrigger.defaultConfig }],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Sun",
|
||||
triggers: [{ platform: "sun", ...HaSunTrigger.defaultConfig }],
|
||||
triggers: [{ ...HaSunTrigger.defaultConfig }],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Time Pattern",
|
||||
triggers: [
|
||||
{ platform: "time_pattern", ...HaTimePatternTrigger.defaultConfig },
|
||||
],
|
||||
triggers: [{ ...HaTimePatternTrigger.defaultConfig }],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Webhook",
|
||||
triggers: [{ platform: "webhook", ...HaWebhookTrigger.defaultConfig }],
|
||||
triggers: [{ ...HaWebhookTrigger.defaultConfig }],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Persistent Notification",
|
||||
triggers: [
|
||||
{
|
||||
platform: "persistent_notification",
|
||||
...HaPersistentNotificationTrigger.defaultConfig,
|
||||
},
|
||||
],
|
||||
@@ -86,43 +83,47 @@ const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
|
||||
|
||||
{
|
||||
name: "Zone",
|
||||
triggers: [{ platform: "zone", ...HaZoneTrigger.defaultConfig }],
|
||||
triggers: [{ ...HaZoneTrigger.defaultConfig }],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Tag",
|
||||
triggers: [{ platform: "tag", ...HaTagTrigger.defaultConfig }],
|
||||
triggers: [{ ...HaTagTrigger.defaultConfig }],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Time",
|
||||
triggers: [{ platform: "time", ...HaTimeTrigger.defaultConfig }],
|
||||
triggers: [{ ...HaTimeTrigger.defaultConfig }],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Template",
|
||||
triggers: [{ platform: "template", ...HaTemplateTrigger.defaultConfig }],
|
||||
triggers: [{ ...HaTemplateTrigger.defaultConfig }],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Event",
|
||||
triggers: [{ platform: "event", ...HaEventTrigger.defaultConfig }],
|
||||
triggers: [{ ...HaEventTrigger.defaultConfig }],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Device Trigger",
|
||||
triggers: [{ platform: "device", ...HaDeviceTrigger.defaultConfig }],
|
||||
triggers: [{ ...HaDeviceTrigger.defaultConfig }],
|
||||
},
|
||||
{
|
||||
name: "Sentence",
|
||||
triggers: [
|
||||
{ platform: "conversation", ...HaConversationTrigger.defaultConfig },
|
||||
{ ...HaConversationTrigger.defaultConfig },
|
||||
{
|
||||
platform: "conversation",
|
||||
trigger: "conversation",
|
||||
command: ["Turn on the lights", "Turn the lights on"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Trigger list",
|
||||
triggers: [{ ...HaTriggerList.defaultConfig }],
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-automation-editor-trigger")
|
||||
@@ -142,6 +143,9 @@ export class DemoAutomationEditorTrigger extends LitElement {
|
||||
mockDeviceRegistry(hass);
|
||||
mockAreaRegistry(hass);
|
||||
mockHassioSupervisor(hass);
|
||||
mockConfig(hass);
|
||||
mockTags(hass);
|
||||
mockAuth(hass);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
|
||||
@@ -64,6 +64,7 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
primary_config_entry: null,
|
||||
},
|
||||
{
|
||||
area_id: "backyard",
|
||||
@@ -86,6 +87,7 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
primary_config_entry: null,
|
||||
},
|
||||
{
|
||||
area_id: null,
|
||||
@@ -108,6 +110,7 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
primary_config_entry: null,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
primary_config_entry: null,
|
||||
},
|
||||
{
|
||||
area_id: "backyard",
|
||||
@@ -86,6 +87,7 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
primary_config_entry: null,
|
||||
},
|
||||
{
|
||||
area_id: null,
|
||||
@@ -108,6 +110,7 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
primary_config_entry: null,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
import { ClimateEntityFeature } from "../../../../src/data/climate";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("switch", "tv_outlet", "on", {
|
||||
@@ -60,6 +61,36 @@ const ENTITIES = [
|
||||
CoverEntityFeature.OPEN_TILT +
|
||||
CoverEntityFeature.STOP_TILT,
|
||||
}),
|
||||
getEntity("input_number", "counter", "1.0", {
|
||||
friendly_name: "Counter",
|
||||
initial: 0,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
mode: "slider",
|
||||
}),
|
||||
getEntity("climate", "dual_thermostat", "heat/cool", {
|
||||
friendly_name: "Dual thermostat",
|
||||
hvac_modes: ["off", "cool", "heat_cool", "auto", "dry", "fan_only"],
|
||||
min_temp: 7,
|
||||
max_temp: 35,
|
||||
fan_modes: ["on_low", "on_high", "auto_low", "auto_high", "off"],
|
||||
preset_modes: ["home", "eco", "away"],
|
||||
swing_modes: ["auto", "1", "2", "3", "off"],
|
||||
current_temperature: 23,
|
||||
target_temp_high: 24,
|
||||
target_temp_low: 21,
|
||||
fan_mode: "auto_low",
|
||||
preset_mode: "home",
|
||||
swing_mode: "auto",
|
||||
supported_features:
|
||||
ClimateEntityFeature.TURN_ON +
|
||||
ClimateEntityFeature.TURN_OFF +
|
||||
ClimateEntityFeature.SWING_MODE +
|
||||
ClimateEntityFeature.PRESET_MODE +
|
||||
ClimateEntityFeature.FAN_MODE +
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
@@ -193,6 +224,25 @@ const CONFIGS = [
|
||||
- type: "cover-tilt"
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Number buttons feature",
|
||||
config: `
|
||||
- type: tile
|
||||
entity: input_number.counter
|
||||
features:
|
||||
- type: numeric-input
|
||||
style: buttons
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Dual thermostat feature",
|
||||
config: `
|
||||
- type: tile
|
||||
entity: climate.dual_thermostat
|
||||
features:
|
||||
- type: target-temperature
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-lovelace-tile-card")
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Markdown
|
||||
---
|
||||
@@ -0,0 +1,93 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
|
||||
import { customElement } from "lit/decorators";
|
||||
|
||||
interface MarkdownContent {
|
||||
content: string;
|
||||
breaks: boolean;
|
||||
allowSvg: boolean;
|
||||
lazyImages: boolean;
|
||||
}
|
||||
|
||||
const mdContentwithDefaults = (md: Partial<MarkdownContent>) =>
|
||||
({
|
||||
breaks: false,
|
||||
allowSvg: false,
|
||||
lazyImages: false,
|
||||
...md,
|
||||
}) as MarkdownContent;
|
||||
|
||||
const generateContent = (md) => `
|
||||
\`\`\`json
|
||||
${JSON.stringify({ ...md, content: undefined })}
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
|
||||
${md.content}
|
||||
`;
|
||||
|
||||
const markdownContents: MarkdownContent[] = [
|
||||
mdContentwithDefaults({
|
||||
content: "_Hello_ **there** 👋, ~~nice~~ of you ||to|| show up.",
|
||||
}),
|
||||
...[true, false].map((breaks) =>
|
||||
mdContentwithDefaults({
|
||||
breaks,
|
||||
content: `
|
||||

|
||||

|
||||
|
||||
> [!TIP]
|
||||
> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer dictum quis ante eu eleifend. Integer sed [consectetur est, nec elementum magna](#). Fusce lobortis lectus ac rutrum tincidunt. Quisque suscipit gravida ante, in convallis risus vulputate non.
|
||||
|
||||
key | description
|
||||
-- | --
|
||||
lorem | ipsum
|
||||
|
||||
- list item 1
|
||||
- list item 2
|
||||
|
||||
|
||||
`,
|
||||
})
|
||||
),
|
||||
];
|
||||
|
||||
@customElement("demo-misc-ha-markdown")
|
||||
export class DemoMiscMarkdown extends LitElement {
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="container">
|
||||
${markdownContents.map(
|
||||
(md) =>
|
||||
html`<ha-card>
|
||||
<ha-markdown
|
||||
.content=${generateContent(md)}
|
||||
.breaks=${md.breaks}
|
||||
.allowSvg=${md.allowSvg}
|
||||
.lazyImages=${md.lazyImages}
|
||||
></ha-markdown>
|
||||
</ha-card>`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
margin: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-misc-ha-markdown": DemoMiscMarkdown;
|
||||
}
|
||||
}
|
||||
@@ -232,6 +232,7 @@ const createDeviceRegistryEntries = (
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
primary_config_entry: null,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { LocalizeFunc } from "../../../src/common/translations/localize";
|
||||
import "../../../src/components/ha-checkbox";
|
||||
import "../../../src/components/ha-formfield";
|
||||
import "../../../src/components/ha-textfield";
|
||||
import "../../../src/components/ha-password-field";
|
||||
import "../../../src/components/ha-radio";
|
||||
import type { HaRadio } from "../../../src/components/ha-radio";
|
||||
import {
|
||||
@@ -261,23 +262,21 @@ export class SupervisorBackupContent extends LitElement {
|
||||
: ""}
|
||||
${this.backupHasPassword
|
||||
? html`
|
||||
<ha-textfield
|
||||
<ha-password-field
|
||||
.label=${this._localize("password")}
|
||||
type="password"
|
||||
name="backupPassword"
|
||||
.value=${this.backupPassword}
|
||||
@change=${this._handleTextValueChanged}
|
||||
>
|
||||
</ha-textfield>
|
||||
</ha-password-field>
|
||||
${!this.backup
|
||||
? html`<ha-textfield
|
||||
? html`<ha-password-field
|
||||
.label=${this._localize("confirm_password")}
|
||||
type="password"
|
||||
name="confirmBackupPassword"
|
||||
.value=${this.confirmBackupPassword}
|
||||
@change=${this._handleTextValueChanged}
|
||||
>
|
||||
</ha-textfield>`
|
||||
</ha-password-field>`
|
||||
: ""}
|
||||
`
|
||||
: ""}
|
||||
|
||||
@@ -13,10 +13,12 @@ import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-expansion-panel";
|
||||
import "../../../../src/components/ha-formfield";
|
||||
import "../../../../src/components/ha-textfield";
|
||||
import "../../../../src/components/ha-header-bar";
|
||||
import "../../../../src/components/ha-icon-button";
|
||||
import "../../../../src/components/ha-password-field";
|
||||
import "../../../../src/components/ha-radio";
|
||||
import "../../../../src/components/ha-textfield";
|
||||
import type { HaTextField } from "../../../../src/components/ha-textfield";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
AccessPoints,
|
||||
@@ -34,7 +36,6 @@ import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import { haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { HassioNetworkDialogParams } from "./show-dialog-network";
|
||||
import type { HaTextField } from "../../../../src/components/ha-textfield";
|
||||
|
||||
const IP_VERSIONS = ["ipv4", "ipv6"];
|
||||
|
||||
@@ -246,9 +247,8 @@ export class DialogHassioNetwork
|
||||
${this._wifiConfiguration.auth === "wpa-psk" ||
|
||||
this._wifiConfiguration.auth === "wep"
|
||||
? html`
|
||||
<ha-textfield
|
||||
<ha-password-field
|
||||
class="flex-auto"
|
||||
type="password"
|
||||
id="psk"
|
||||
.label=${this.supervisor.localize(
|
||||
"dialog.network.wifi_password"
|
||||
@@ -256,7 +256,7 @@ export class DialogHassioNetwork
|
||||
version="wifi"
|
||||
@change=${this._handleInputValueChangedWifi}
|
||||
>
|
||||
</ha-textfield>
|
||||
</ha-password-field>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
|
||||
@@ -25,8 +25,8 @@ import type { HomeAssistant } from "../../../../src/types";
|
||||
import { HassioRepositoryDialogParams } from "./show-dialog-repositories";
|
||||
import type { HaTextField } from "../../../../src/components/ha-textfield";
|
||||
import "../../../../src/components/ha-textfield";
|
||||
import "../../../../src/components/ha-list-new";
|
||||
import "../../../../src/components/ha-list-item-new";
|
||||
import "../../../../src/components/ha-md-list";
|
||||
import "../../../../src/components/ha-md-list-item";
|
||||
|
||||
@customElement("dialog-hassio-repositories")
|
||||
class HassioRepositoriesDialog extends LitElement {
|
||||
@@ -107,11 +107,11 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
<div class="form">
|
||||
<ha-list-new>
|
||||
<ha-md-list>
|
||||
${repositories.length
|
||||
? repositories.map(
|
||||
(repo) => html`
|
||||
<ha-list-item-new class="option">
|
||||
<ha-md-list-item class="option">
|
||||
${repo.name}
|
||||
<div slot="supporting-text">
|
||||
<div>${repo.maintainer}</div>
|
||||
@@ -142,11 +142,11 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
)}
|
||||
</simple-tooltip>
|
||||
</div>
|
||||
</ha-list-item-new>
|
||||
</ha-md-list-item>
|
||||
`
|
||||
)
|
||||
: html`<ha-list-item-new> No repositories </ha-list-item-new>`}
|
||||
</ha-list-new>
|
||||
: html`<ha-md-list-item> No repositories </ha-md-list-item>`}
|
||||
</ha-md-list>
|
||||
<div class="layout horizontal bottom">
|
||||
<ha-textfield
|
||||
class="flex-auto"
|
||||
@@ -209,7 +209,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
div.delete ha-icon-button {
|
||||
color: var(--error-color);
|
||||
}
|
||||
ha-list-item-new {
|
||||
ha-md-list-item {
|
||||
position: relative;
|
||||
}
|
||||
`,
|
||||
|
||||
@@ -13,10 +13,11 @@
|
||||
<% for (const entry of es5EntryJS) { %>
|
||||
loadES5("<%= entry %>");
|
||||
<% } %>
|
||||
}
|
||||
} else {
|
||||
<% for (const entry of es5EntryJS) { %>
|
||||
loadES5("<%= entry %>");
|
||||
<% } %>
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -25,35 +25,35 @@
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.25.0",
|
||||
"@babel/runtime": "7.25.7",
|
||||
"@braintree/sanitize-url": "7.1.0",
|
||||
"@codemirror/autocomplete": "6.17.0",
|
||||
"@codemirror/commands": "6.6.0",
|
||||
"@codemirror/language": "6.10.2",
|
||||
"@codemirror/legacy-modes": "6.4.0",
|
||||
"@codemirror/autocomplete": "6.18.1",
|
||||
"@codemirror/commands": "6.7.0",
|
||||
"@codemirror/language": "6.10.3",
|
||||
"@codemirror/legacy-modes": "6.4.1",
|
||||
"@codemirror/search": "6.5.6",
|
||||
"@codemirror/state": "6.4.1",
|
||||
"@codemirror/view": "6.29.1",
|
||||
"@codemirror/view": "6.34.1",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.12.5",
|
||||
"@formatjs/intl-displaynames": "6.6.8",
|
||||
"@formatjs/intl-datetimeformat": "6.13.0",
|
||||
"@formatjs/intl-displaynames": "6.6.9",
|
||||
"@formatjs/intl-getcanonicallocales": "2.3.0",
|
||||
"@formatjs/intl-listformat": "7.5.7",
|
||||
"@formatjs/intl-locale": "4.0.0",
|
||||
"@formatjs/intl-numberformat": "8.10.3",
|
||||
"@formatjs/intl-pluralrules": "5.2.14",
|
||||
"@formatjs/intl-relativetimeformat": "11.2.14",
|
||||
"@formatjs/intl-listformat": "7.5.8",
|
||||
"@formatjs/intl-locale": "4.0.1",
|
||||
"@formatjs/intl-numberformat": "8.11.0",
|
||||
"@formatjs/intl-pluralrules": "5.2.15",
|
||||
"@formatjs/intl-relativetimeformat": "11.2.15",
|
||||
"@fullcalendar/core": "6.1.15",
|
||||
"@fullcalendar/daygrid": "6.1.15",
|
||||
"@fullcalendar/interaction": "6.1.15",
|
||||
"@fullcalendar/list": "6.1.15",
|
||||
"@fullcalendar/luxon3": "6.1.15",
|
||||
"@fullcalendar/timegrid": "6.1.15",
|
||||
"@lezer/highlight": "1.2.0",
|
||||
"@lezer/highlight": "1.2.1",
|
||||
"@lit-labs/context": "0.4.1",
|
||||
"@lit-labs/motion": "1.0.7",
|
||||
"@lit-labs/observers": "2.0.2",
|
||||
"@lit-labs/virtualizer": "2.0.13",
|
||||
"@lit-labs/virtualizer": "2.0.14",
|
||||
"@lrnwebcomponents/simple-tooltip": "8.0.2",
|
||||
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||
@@ -80,16 +80,17 @@
|
||||
"@material/mwc-top-app-bar": "0.27.0",
|
||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/web": "2.0.0",
|
||||
"@material/web": "2.2.0",
|
||||
"@mdi/js": "7.4.47",
|
||||
"@mdi/svg": "7.4.47",
|
||||
"@polymer/paper-item": "3.0.1",
|
||||
"@polymer/paper-listbox": "3.0.1",
|
||||
"@polymer/paper-tabs": "3.1.0",
|
||||
"@polymer/polymer": "3.5.1",
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@vaadin/combo-box": "24.4.4",
|
||||
"@vaadin/vaadin-themable-mixin": "24.4.4",
|
||||
"@vaadin/combo-box": "24.4.11",
|
||||
"@vaadin/vaadin-themable-mixin": "24.4.11",
|
||||
"@vibrant/color": "3.2.1-alpha.1",
|
||||
"@vibrant/core": "3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||
@@ -97,43 +98,44 @@
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.9",
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
"app-datepicker": "5.1.1",
|
||||
"chart.js": "4.4.3",
|
||||
"chart.js": "4.4.4",
|
||||
"color-name": "2.0.0",
|
||||
"comlink": "4.4.1",
|
||||
"core-js": "3.37.1",
|
||||
"core-js": "3.38.1",
|
||||
"cropperjs": "1.6.2",
|
||||
"date-fns": "3.6.0",
|
||||
"date-fns-tz": "3.1.3",
|
||||
"date-fns": "4.1.0",
|
||||
"date-fns-tz": "3.2.0",
|
||||
"deep-clone-simple": "1.1.1",
|
||||
"deep-freeze": "0.0.1",
|
||||
"dialog-polyfill": "0.5.6",
|
||||
"element-internals-polyfill": "1.3.11",
|
||||
"fuse.js": "7.0.0",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
|
||||
"home-assistant-js-websocket": "9.4.0",
|
||||
"idb-keyval": "6.2.1",
|
||||
"intl-messageformat": "10.5.14",
|
||||
"intl-messageformat": "10.6.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-draw": "1.0.4",
|
||||
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
|
||||
"lit": "2.8.0",
|
||||
"luxon": "3.4.4",
|
||||
"marked": "13.0.3",
|
||||
"luxon": "3.5.0",
|
||||
"marked": "14.1.2",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
"proxy-polyfill": "0.3.2",
|
||||
"punycode": "2.3.1",
|
||||
"qr-scanner": "1.4.2",
|
||||
"qrcode": "1.5.3",
|
||||
"qrcode": "1.5.4",
|
||||
"roboto-fontface": "0.10.0",
|
||||
"rrule": "2.8.1",
|
||||
"sortablejs": "1.15.2",
|
||||
"sortablejs": "patch:sortablejs@npm%3A1.15.3#~/.yarn/patches/sortablejs-npm-1.15.3-3235a8f83b.patch",
|
||||
"stacktrace-js": "2.0.2",
|
||||
"superstruct": "2.0.2",
|
||||
"tinykeys": "2.1.0",
|
||||
"tinykeys": "3.0.0",
|
||||
"tsparticles-engine": "2.12.0",
|
||||
"tsparticles-preset-links": "2.12.0",
|
||||
"ua-parser-js": "1.0.38",
|
||||
"ua-parser-js": "1.0.39",
|
||||
"unfetch": "5.0.0",
|
||||
"vis-data": "7.1.9",
|
||||
"vis-network": "9.1.9",
|
||||
@@ -149,28 +151,28 @@
|
||||
"xss": "1.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.24.9",
|
||||
"@babel/core": "7.25.8",
|
||||
"@babel/helper-define-polyfill-provider": "0.6.2",
|
||||
"@babel/plugin-proposal-decorators": "7.24.7",
|
||||
"@babel/plugin-transform-runtime": "7.24.7",
|
||||
"@babel/preset-env": "7.25.0",
|
||||
"@babel/preset-typescript": "7.24.7",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.13.4",
|
||||
"@babel/plugin-proposal-decorators": "7.25.7",
|
||||
"@babel/plugin-transform-runtime": "7.25.7",
|
||||
"@babel/preset-env": "7.25.8",
|
||||
"@babel/preset-typescript": "7.25.7",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.15.1",
|
||||
"@koa/cors": "5.0.0",
|
||||
"@lokalise/node-api": "12.7.0",
|
||||
"@octokit/auth-oauth-device": "7.1.1",
|
||||
"@octokit/plugin-retry": "7.1.1",
|
||||
"@octokit/rest": "21.0.1",
|
||||
"@octokit/plugin-retry": "7.1.2",
|
||||
"@octokit/rest": "21.0.2",
|
||||
"@open-wc/dev-server-hmr": "0.1.4",
|
||||
"@rollup/plugin-babel": "6.0.4",
|
||||
"@rollup/plugin-commonjs": "26.0.1",
|
||||
"@rollup/plugin-json": "6.1.0",
|
||||
"@rollup/plugin-node-resolve": "15.2.3",
|
||||
"@rollup/plugin-node-resolve": "15.2.4",
|
||||
"@rollup/plugin-replace": "5.0.7",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.16",
|
||||
"@types/chromecast-caf-receiver": "6.0.17",
|
||||
"@types/chromecast-caf-sender": "1.0.10",
|
||||
"@types/color-name": "1.1.4",
|
||||
"@types/color-name": "2.0.0",
|
||||
"@types/glob": "8.1.0",
|
||||
"@types/html-minifier-terser": "7.0.2",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
@@ -185,25 +187,25 @@
|
||||
"@types/tar": "6.1.13",
|
||||
"@types/ua-parser-js": "0.7.39",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "7.17.0",
|
||||
"@typescript-eslint/parser": "7.17.0",
|
||||
"@typescript-eslint/eslint-plugin": "7.18.0",
|
||||
"@typescript-eslint/parser": "7.18.0",
|
||||
"@web/dev-server": "0.1.38",
|
||||
"@web/dev-server-rollup": "0.4.1",
|
||||
"babel-loader": "9.1.3",
|
||||
"babel-loader": "9.2.1",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"browserslist-useragent-regexp": "4.1.3",
|
||||
"chai": "5.1.1",
|
||||
"del": "7.1.0",
|
||||
"eslint": "8.57.0",
|
||||
"del": "8.0.0",
|
||||
"eslint": "8.57.1",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-airbnb-typescript": "18.0.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-import-resolver-webpack": "0.13.8",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-lit": "1.14.0",
|
||||
"eslint-import-resolver-webpack": "0.13.9",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-lit": "1.15.0",
|
||||
"eslint-plugin-lit-a11y": "4.1.4",
|
||||
"eslint-plugin-unused-imports": "4.0.1",
|
||||
"eslint-plugin-wc": "2.1.0",
|
||||
"eslint-plugin-unused-imports": "4.1.4",
|
||||
"eslint-plugin-wc": "2.2.0",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.2.0",
|
||||
"glob": "11.0.0",
|
||||
@@ -213,35 +215,35 @@
|
||||
"gulp-rename": "2.0.0",
|
||||
"gulp-zopfli-green": "6.0.2",
|
||||
"html-minifier-terser": "7.2.0",
|
||||
"husky": "9.1.4",
|
||||
"husky": "9.1.6",
|
||||
"instant-mocha": "1.5.2",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "15.2.7",
|
||||
"lint-staged": "15.2.10",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.5.0",
|
||||
"magic-string": "0.30.11",
|
||||
"magic-string": "0.30.12",
|
||||
"map-stream": "0.0.7",
|
||||
"mocha": "10.5.0",
|
||||
"object-hash": "3.0.0",
|
||||
"open": "10.1.0",
|
||||
"pinst": "3.0.0",
|
||||
"prettier": "3.3.3",
|
||||
"rollup": "2.79.1",
|
||||
"rollup": "2.79.2",
|
||||
"rollup-plugin-string": "3.0.0",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"rollup-plugin-visualizer": "5.12.0",
|
||||
"serve-handler": "6.1.5",
|
||||
"sinon": "18.0.0",
|
||||
"sinon": "19.0.2",
|
||||
"systemjs": "6.15.1",
|
||||
"tar": "7.4.3",
|
||||
"terser-webpack-plugin": "5.3.10",
|
||||
"transform-async-modules-webpack-plugin": "1.1.1",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.5.4",
|
||||
"webpack": "5.93.0",
|
||||
"typescript": "5.6.3",
|
||||
"webpack": "5.95.0",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-dev-server": "5.0.4",
|
||||
"webpack-dev-server": "5.1.0",
|
||||
"webpack-manifest-plugin": "5.0.0",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
"webpackbar": "6.0.1",
|
||||
@@ -254,9 +256,7 @@
|
||||
"lit": "2.8.0",
|
||||
"clean-css": "5.3.3",
|
||||
"@lit/reactive-element": "1.6.3",
|
||||
"@fullcalendar/daygrid": "6.1.15",
|
||||
"sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
|
||||
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
"@fullcalendar/daygrid": "6.1.15"
|
||||
},
|
||||
"packageManager": "yarn@4.3.1"
|
||||
"packageManager": "yarn@4.5.0"
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="b" data-name="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760.69 138.69">
|
||||
<g id="c" data-name="Layer_1">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M136.22,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM136.27,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/>
|
||||
<path d="M184.16,80.53c0,3.47-1.06,6.27-3.18,8.41s-4.98,3.21-8.59,3.21h-7.45v12h-6.56v-35.18h14.06c3.64,0,6.5,1.04,8.59,3.11s3.13,4.89,3.13,8.45ZM177.25,80.39c0-1.64-.52-2.98-1.56-4.03s-2.52-1.57-4.44-1.57h-6.3v11.65h6.26c1.95,0,3.45-.55,4.49-1.65s1.56-2.57,1.56-4.39Z"/>
|
||||
<path d="M210.82,98.02v6.14h-22.03v-35.18h21.98v6.19h-15.42v8.3h13.78v5.81h-13.78v8.74h15.47Z"/>
|
||||
<path d="M246.95,68.98v35.18h-6.49l-16.08-24.77v24.77h-6.52v-35.18h6.52l16.08,24.77v-24.77h6.49Z"/>
|
||||
<path d="M266.45,68.98h6.56v14.44l14.7.05v-14.48h6.63v35.18h-6.63v-14.84l-14.7-.09v14.93h-6.56v-35.18Z"/>
|
||||
<path d="M316.41,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM316.46,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/>
|
||||
<path d="M373.66,68.98v35.18h-6.45v-20.55l-8.11,20.55h-6.23l-8.02-20.39v20.39h-6.28v-35.18h6.28l11.13,27.54,11.23-27.54h6.45Z"/>
|
||||
<path d="M402.87,98.02v6.14h-22.03v-35.18h21.98v6.19h-15.42v8.3h13.78v5.81h-13.78v8.74h15.47Z"/>
|
||||
<path d="M427.83,75.12v8.93h13.01l-.05,5.91h-12.96v14.2h-6.52v-35.18h21.98l-.05,6.14h-15.42Z"/>
|
||||
<path d="M463.16,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM463.21,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/>
|
||||
<path d="M485,68.98h6.56v22.12c0,2.31.72,4.12,2.16,5.43s3.3,1.96,5.58,1.96,4.08-.67,5.58-2.02,2.25-3.13,2.25-5.37v-22.12h6.52v22.31c0,2.08-.38,3.98-1.14,5.7s-1.79,3.14-3.09,4.25-2.82,1.98-4.56,2.59-3.59.91-5.55.91c-2.59,0-4.96-.52-7.1-1.55s-3.88-2.58-5.2-4.65-1.99-4.49-1.99-7.25v-22.31Z"/>
|
||||
<path d="M549.63,68.98v35.18h-6.49l-16.08-24.77v24.77h-6.52v-35.18h6.52l16.08,24.77v-24.77h6.49Z"/>
|
||||
<path d="M586.9,86.58c.05,3.34-.71,6.37-2.27,9.08s-3.7,4.82-6.42,6.32-5.73,2.23-9.02,2.18h-12.42v-35.18h12.42c2.45-.03,4.78.39,6.98,1.28s4.1,2.1,5.68,3.66,2.84,3.43,3.75,5.64,1.35,4.55,1.3,7.03ZM579.99,86.58c0-3.39-1-6.16-3.01-8.3s-4.62-3.21-7.84-3.21h-5.81v23.04h5.81c3.27,0,5.89-1.06,7.88-3.19s2.98-4.91,2.98-8.34Z"/>
|
||||
<path d="M609.16,96.19h-12.73l-2.79,7.97h-6.82l12.68-35.18h6.63l12.66,35.18h-6.96l-2.67-7.97ZM607.24,90.73l-4.43-12.87-4.45,12.87h8.88Z"/>
|
||||
<path d="M642.87,75.17h-9.89v28.99h-6.56v-28.99h-9.94v-6.19h26.39v6.19Z"/>
|
||||
<path d="M647.06,104.16v-35.18h6.56v35.18h-6.56Z"/>
|
||||
<path d="M675.71,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM675.76,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/>
|
||||
<path d="M726.96,68.98v35.18h-6.49l-16.08-24.77v24.77h-6.52v-35.18h6.52l16.08,24.77v-24.77h6.49Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M94.34,79.34c0,2.75-2.25,5-5,5h-50c-2.75,0-5-2.25-5-5v-20c0-2.75,1.59-6.59,3.54-8.54l22.93-22.93c1.94-1.94,5.13-1.94,7.07,0l22.93,22.93c1.94,1.94,3.54,5.79,3.54,8.54v20Z"/>
|
||||
<g>
|
||||
<rect x="34.34" y="94.34" width="60" height="10" rx="2.5" ry="2.5"/>
|
||||
<rect x="34.34" y="94.34" width="10" height="20" rx="1.56" ry="1.56"/>
|
||||
<rect x="84.34" y="94.34" width="10" height="20" rx="1.56" ry="1.56"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M735.34,3c12.32,0,22.34,10.02,22.34,22.34v88c0,12.32-10.02,22.34-22.34,22.34H25.34c-12.32,0-22.34-10.02-22.34-22.34V25.34C3,13.02,13.02,3,25.34,3h710M735.34,0H25.34C11.37,0,0,11.37,0,25.34v88c0,13.98,11.37,25.34,25.34,25.34h710c13.97,0,25.34-11.37,25.34-25.34V25.34c0-13.98-11.37-25.34-25.34-25.34h0Z"/>
|
||||
<g>
|
||||
<path d="M120.98,36.79h2.95v7.26l7.66.02v-7.29h2.97v17.37h-2.97v-7.47l-7.66-.02v7.49h-2.95v-17.37Z"/>
|
||||
<path d="M146.97,36.47c1.63,0,3.09.39,4.37,1.16s2.28,1.84,2.99,3.2,1.06,2.9,1.06,4.61c.02,1.7-.32,3.24-1.04,4.62s-1.72,2.47-3.02,3.25-2.75,1.16-4.36,1.14c-1.62.02-3.08-.36-4.37-1.14s-2.29-1.86-3-3.24-1.05-2.91-1.03-4.61c0-1.27.2-2.47.61-3.58s.99-2.08,1.72-2.88,1.63-1.42,2.68-1.88,2.18-.67,3.39-.66ZM146.99,51.57c1.6,0,2.89-.56,3.85-1.67s1.45-2.6,1.45-4.45-.48-3.32-1.45-4.43-2.25-1.66-3.85-1.66-2.89.55-3.86,1.66-1.45,2.58-1.45,4.43.48,3.34,1.44,4.46,2.25,1.67,3.88,1.67Z"/>
|
||||
<path d="M176.51,36.79v17.37h-2.89v-10.78l-4.29,10.78h-2.81l-4.25-10.71v10.71h-2.84v-17.37h2.84l5.66,13.92,5.69-13.92h2.89Z"/>
|
||||
<path d="M192.41,51.37v2.79h-10.78v-17.37h10.78v2.81h-7.83v4.5h7v2.61h-7v4.66h7.83Z"/>
|
||||
<path d="M213.93,50.11h-6.61l-1.43,4.04h-3.04l6.27-17.37h3.04l6.29,17.37h-3.11l-1.41-4.04ZM213.07,47.62l-2.43-6.95-2.45,6.95h4.88Z"/>
|
||||
<path d="M226.96,36.47c1.59,0,2.91.39,3.96,1.16s1.7,1.81,1.94,3.1l-2.78.76c-.16-.74-.52-1.32-1.09-1.72s-1.27-.6-2.11-.6c-.9,0-1.61.21-2.14.64s-.79,1-.79,1.71c0,1.12.7,1.85,2.09,2.18l2.84.71c1.46.38,2.56.98,3.29,1.81s1.09,1.84,1.09,3.05c0,1.55-.56,2.8-1.68,3.76s-2.63,1.44-4.51,1.44c-1.7,0-3.13-.4-4.3-1.2-1.15-.83-1.84-1.92-2.05-3.28l2.78-.72c.1.77.48,1.37,1.14,1.8s1.5.65,2.53.65,1.76-.21,2.32-.62.84-.98.84-1.69c0-1.12-.7-1.85-2.09-2.21l-2.84-.69c-1.46-.33-2.55-.92-3.28-1.77s-1.1-1.88-1.1-3.11c0-1.53.54-2.78,1.63-3.74s2.52-1.44,4.29-1.44Z"/>
|
||||
<path d="M242.38,36.47c1.59,0,2.91.39,3.96,1.16s1.7,1.81,1.94,3.1l-2.78.76c-.16-.74-.52-1.32-1.09-1.72s-1.27-.6-2.11-.6c-.9,0-1.61.21-2.14.64s-.79,1-.79,1.71c0,1.12.7,1.85,2.09,2.18l2.84.71c1.46.38,2.56.98,3.29,1.81s1.09,1.84,1.09,3.05c0,1.55-.56,2.8-1.68,3.76s-2.63,1.44-4.51,1.44c-1.7,0-3.13-.4-4.3-1.2-1.15-.83-1.84-1.92-2.05-3.28l2.78-.72c.1.77.48,1.37,1.14,1.8s1.5.65,2.53.65,1.76-.21,2.32-.62.84-.98.84-1.69c0-1.12-.7-1.85-2.09-2.21l-2.84-.69c-1.46-.33-2.55-.92-3.28-1.77s-1.1-1.88-1.1-3.11c0-1.53.54-2.78,1.63-3.74s2.52-1.44,4.29-1.44Z"/>
|
||||
<path d="M252.68,54.16v-17.37h2.95v17.37h-2.95Z"/>
|
||||
<path d="M265.82,36.47c1.59,0,2.91.39,3.96,1.16s1.7,1.81,1.94,3.1l-2.78.76c-.16-.74-.52-1.32-1.09-1.72s-1.27-.6-2.11-.6c-.9,0-1.61.21-2.14.64s-.79,1-.79,1.71c0,1.12.7,1.85,2.09,2.18l2.84.71c1.46.38,2.56.98,3.29,1.81s1.09,1.84,1.09,3.05c0,1.55-.56,2.8-1.68,3.76s-2.63,1.44-4.51,1.44c-1.7,0-3.13-.4-4.3-1.2-1.15-.83-1.84-1.92-2.05-3.28l2.78-.72c.1.77.48,1.37,1.14,1.8s1.5.65,2.53.65,1.76-.21,2.32-.62.84-.98.84-1.69c0-1.12-.7-1.85-2.09-2.21l-2.84-.69c-1.46-.33-2.55-.92-3.28-1.77s-1.1-1.88-1.1-3.11c0-1.53.54-2.78,1.63-3.74s2.52-1.44,4.29-1.44Z"/>
|
||||
<path d="M287.47,39.57h-4.97v14.58h-2.95v-14.58h-4.97v-2.79h12.9v2.79Z"/>
|
||||
<path d="M298.87,50.11h-6.61l-1.43,4.04h-3.04l6.27-17.37h3.04l6.29,17.37h-3.11l-1.41-4.04ZM298.01,47.62l-2.43-6.95-2.45,6.95h4.88Z"/>
|
||||
<path d="M320.89,36.79v17.37h-2.93l-8.25-12.67v12.67h-2.93v-17.37h2.93l8.25,12.65v-12.65h2.93Z"/>
|
||||
<path d="M337.31,39.57h-4.97v14.58h-2.95v-14.58h-4.97v-2.79h12.9v2.79Z"/>
|
||||
<path d="M348.75,54.16v-17.14h2.05v17.14h-2.05Z"/>
|
||||
<path d="M360.95,36.72c1.55,0,2.82.38,3.81,1.14,1,.74,1.61,1.72,1.82,2.95l-1.95.52c-.16-.87-.56-1.54-1.23-2.02s-1.5-.71-2.5-.71c-1.08,0-1.95.27-2.6.8s-.97,1.24-.97,2.13c0,1.36.84,2.26,2.52,2.71l2.9.73c1.37.34,2.41.9,3.11,1.68s1.05,1.74,1.05,2.9c0,1.46-.53,2.64-1.6,3.54s-2.49,1.36-4.28,1.36c-1.61,0-2.95-.37-4.03-1.12s-1.72-1.76-1.95-3.06l1.98-.55c.13.88.55,1.56,1.25,2.06s1.63.75,2.77.75,2.12-.26,2.79-.77,1.02-1.22,1.02-2.14c0-1.44-.84-2.36-2.52-2.77l-2.88-.71c-1.38-.34-2.42-.9-3.12-1.69s-1.05-1.75-1.05-2.9c0-1.44.52-2.61,1.56-3.51s2.4-1.35,4.09-1.35Z"/>
|
||||
<path d="M388.35,49.75h-7.54l-1.59,4.4h-2.07l6.25-17.14h2.36l6.31,17.14h-2.15l-1.57-4.4ZM387.73,48.05l-3.09-8.71-3.2,8.71h6.29Z"/>
|
||||
<path d="M415.46,42.47c0,1.6-.5,2.91-1.5,3.95s-2.32,1.56-3.97,1.56h-4.53v6.18h-2.05v-17.14h6.6c1.67,0,3,.49,3.98,1.47s1.47,2.31,1.47,3.98ZM413.31,42.42c0-1.07-.32-1.92-.95-2.56s-1.51-.96-2.64-.96h-4.26v7.24h4.17c1.15,0,2.06-.34,2.71-1.02s.98-1.58.98-2.7Z"/>
|
||||
<path d="M428.37,46.9l3.43,7.26h-2.31l-3.18-6.95h-4.76v6.95h-2.05v-17.14h6.54c1.81,0,3.22.45,4.24,1.35s1.53,2.14,1.53,3.72c0,1.22-.3,2.26-.9,3.1s-1.44,1.41-2.53,1.7ZM429.64,42.12c0-1.01-.32-1.81-.95-2.38s-1.52-.86-2.66-.86h-4.5v6.47h4.53c1.15,0,2.03-.28,2.64-.85s.92-1.36.92-2.38Z"/>
|
||||
<path d="M443.34,36.74c1.18-.02,2.28.2,3.31.65s1.9,1.07,2.62,1.85,1.28,1.73,1.69,2.83.6,2.27.59,3.52c.02,1.67-.33,3.19-1.03,4.54s-1.68,2.42-2.95,3.19-2.68,1.14-4.25,1.12c-1.59,0-3-.38-4.25-1.13s-2.21-1.81-2.89-3.15-1.03-2.87-1.03-4.57c-.02-1.67.32-3.18,1.02-4.53s1.67-2.42,2.93-3.2,2.68-1.15,4.24-1.13ZM443.34,52.45c1.8,0,3.26-.64,4.38-1.91s1.68-2.92,1.68-4.95-.56-3.71-1.68-4.98-2.58-1.9-4.38-1.9-3.28.63-4.4,1.9-1.68,2.93-1.68,4.98.56,3.69,1.68,4.96,2.59,1.9,4.39,1.9Z"/>
|
||||
<path d="M464.3,37.02v12.42c0,1.49-.47,2.71-1.41,3.64s-2.17,1.39-3.71,1.39c-1.56,0-2.76-.46-3.61-1.37s-1.27-2.13-1.27-3.69v-.55h1.98v.55c0,1.18.29,1.99.86,2.45.59.45,1.26.67,2.02.67.93,0,1.68-.26,2.24-.78.57-.52.85-1.32.85-2.39v-12.35h2.05Z"/>
|
||||
<path d="M479.86,52.23v1.93h-10.35v-17.14h10.33v1.95h-8.31v5.67h7.53v1.8h-7.53v5.79h8.33Z"/>
|
||||
<path d="M496.97,42.42c-.36-1.15-1.01-2.06-1.93-2.71s-2.02-.98-3.3-.98c-1.79,0-3.24.63-4.35,1.89s-1.66,2.92-1.66,4.96.55,3.72,1.66,4.96,2.55,1.86,4.33,1.86c1.27,0,2.39-.31,3.35-.94.95-.63,1.61-1.44,1.98-2.45l1.93.83c-.55,1.41-1.48,2.53-2.78,3.36-1.31.81-2.81,1.22-4.51,1.22-2.4,0-4.35-.81-5.84-2.43s-2.24-3.75-2.24-6.4c0-1.74.34-3.28,1.02-4.62s1.64-2.39,2.88-3.13,2.65-1.11,4.25-1.11c1.8,0,3.33.46,4.59,1.38,1.26.91,2.12,2.1,2.57,3.59l-1.93.71Z"/>
|
||||
<path d="M512.92,38.95h-5.12v15.21h-2.05v-15.21h-5.16v-1.93h12.33v1.93Z"/>
|
||||
<path d="M536.4,49.66c0,1.39-.49,2.48-1.46,3.29s-2.27,1.21-3.9,1.21h-6.72v-17.12h6.58c1.65,0,2.95.41,3.89,1.24s1.41,1.93,1.41,3.32c0,.92-.21,1.72-.64,2.4s-1.02,1.2-1.79,1.57c.81.37,1.45.91,1.92,1.62s.7,1.53.7,2.47ZM526.3,38.81v5.97h4.54c1.03,0,1.85-.27,2.46-.81s.92-1.24.92-2.09c0-.91-.31-1.65-.93-2.22s-1.45-.85-2.5-.85h-4.5ZM534.45,49.39c0-.91-.32-1.64-.95-2.17s-1.44-.8-2.46-.8h-4.74v5.95h4.74c1.04,0,1.86-.27,2.48-.81s.92-1.26.92-2.16Z"/>
|
||||
<path d="M541.07,37.02l4.54,8.1,4.54-8.1h2.24l-5.76,10.05v7.09h-2.05v-7.09l-5.83-10.05h2.31Z"/>
|
||||
<path d="M574.27,38.95h-5.12v15.21h-2.05v-15.21h-5.16v-1.93h12.33v1.93Z"/>
|
||||
<path d="M577.95,37.02h2.05v7.55l8.74.02v-7.58h2.05v17.14h-2.05v-7.76l-8.74-.02v7.79h-2.05v-17.14Z"/>
|
||||
<path d="M606.55,52.23v1.93h-10.35v-17.14h10.33v1.95h-8.31v5.67h7.53v1.8h-7.53v5.79h8.33Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
@@ -1,10 +1,10 @@
|
||||
[build-system]
|
||||
requires = ["setuptools~=68.0", "wheel~=0.40.0"]
|
||||
requires = ["setuptools~=75.1"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20240802.0"
|
||||
version = "20241010.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -37,8 +37,7 @@
|
||||
{
|
||||
"description": "Group tsparticles engine and presets",
|
||||
"groupName": "tsparticles",
|
||||
"matchPackageNames": ["tsparticles-engine"],
|
||||
"matchPackagePrefixes": ["tsparticles-preset-"]
|
||||
"matchPackageNames": ["tsparticles-engine", "tsparticles-preset-{/,}**"]
|
||||
},
|
||||
{
|
||||
"description": "Group date-fns with dependent timezone package",
|
||||
@@ -48,8 +47,8 @@
|
||||
{
|
||||
"description": "Group and temporarily disable WDS packages",
|
||||
"groupName": "Web Dev Server",
|
||||
"matchPackagePrefixes": ["@web/dev-server"],
|
||||
"enabled": false
|
||||
"enabled": false,
|
||||
"matchPackageNames": ["@web/dev-server{/,}**"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -18,5 +18,9 @@ if [[ -n "$DEVCONTAINER" ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! command -v yarn &> /dev/null; then
|
||||
echo "Error: yarn not found. Please install it following the official instructions: https://yarnpkg.com/getting-started/install" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Install node modules
|
||||
yarn install
|
||||
yarn install
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import { theme2hex } from "./convert-color";
|
||||
|
||||
export const COLORS = [
|
||||
"#44739e",
|
||||
"#984ea3",
|
||||
"#00d2d5",
|
||||
"#ff7f00",
|
||||
"#af8d00",
|
||||
"#7f80cd",
|
||||
"#b3e900",
|
||||
"#c42e60",
|
||||
"#a65628",
|
||||
"#f781bf",
|
||||
"#8dd3c7",
|
||||
"#bebada",
|
||||
"#fb8072",
|
||||
"#80b1d3",
|
||||
"#fdb462",
|
||||
"#fccde5",
|
||||
"#bc80bd",
|
||||
"#ffed6f",
|
||||
"#c4eaff",
|
||||
"#cf8c00",
|
||||
"#1b9e77",
|
||||
"#d95f02",
|
||||
"#e7298a",
|
||||
"#e6ab02",
|
||||
"#a6761d",
|
||||
"#0097ff",
|
||||
"#00d067",
|
||||
"#f43600",
|
||||
"#4ba93b",
|
||||
"#5779bb",
|
||||
"#4269d0",
|
||||
"#f4bd4a",
|
||||
"#ff725c",
|
||||
"#6cc5b0",
|
||||
"#a463f2",
|
||||
"#ff8ab7",
|
||||
"#9c6b4e",
|
||||
"#97bbf5",
|
||||
"#01ab63",
|
||||
"#9498a0",
|
||||
"#094bad",
|
||||
"#c99000",
|
||||
"#d84f3e",
|
||||
"#49a28f",
|
||||
"#048732",
|
||||
"#d96895",
|
||||
"#8043ce",
|
||||
"#7599d1",
|
||||
"#7a4c31",
|
||||
"#74787f",
|
||||
"#6989f4",
|
||||
"#ffd444",
|
||||
"#ff957c",
|
||||
"#8fe9d3",
|
||||
"#62cc71",
|
||||
"#ffadda",
|
||||
"#c884ff",
|
||||
"#badeff",
|
||||
"#bf8b6d",
|
||||
"#b6bac2",
|
||||
"#927acc",
|
||||
"#97ee3f",
|
||||
"#bf3947",
|
||||
|
||||
@@ -40,7 +40,6 @@ import {
|
||||
mdiImageFilterFrames,
|
||||
mdiLightbulb,
|
||||
mdiLightningBolt,
|
||||
mdiMailbox,
|
||||
mdiMapMarkerRadius,
|
||||
mdiMeterGas,
|
||||
mdiMicrophoneMessage,
|
||||
@@ -119,7 +118,6 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
input_text: mdiFormTextbox,
|
||||
lawn_mower: mdiRobotMower,
|
||||
light: mdiLightbulb,
|
||||
mailbox: mdiMailbox,
|
||||
notify: mdiCommentAlert,
|
||||
number: mdiRayVertex,
|
||||
persistent_notification: mdiBell,
|
||||
@@ -236,7 +234,12 @@ export const SENSOR_ENTITIES = [
|
||||
"weather",
|
||||
];
|
||||
|
||||
export const ASSIST_ENTITIES = ["conversation", "stt", "tts"];
|
||||
export const ASSIST_ENTITIES = [
|
||||
"assist_satellite",
|
||||
"conversation",
|
||||
"stt",
|
||||
"tts",
|
||||
];
|
||||
|
||||
/** Domains that render an input element instead of a text value when displayed in a row.
|
||||
* Those rows should then not show a cursor pointer when hovered (which would normally
|
||||
|
||||
@@ -71,8 +71,7 @@ export const computeStateDisplayFromEntityAttributes = (
|
||||
if (
|
||||
attributes.device_class === "duration" &&
|
||||
attributes.unit_of_measurement &&
|
||||
UNIT_TO_MILLISECOND_CONVERT[attributes.unit_of_measurement] &&
|
||||
entity?.display_precision === undefined
|
||||
UNIT_TO_MILLISECOND_CONVERT[attributes.unit_of_measurement]
|
||||
) {
|
||||
try {
|
||||
return formatDuration(state, attributes.unit_of_measurement);
|
||||
|
||||
@@ -26,7 +26,7 @@ export const FIXED_DOMAIN_STATES = {
|
||||
humidifier: ["on", "off"],
|
||||
input_boolean: ["on", "off"],
|
||||
input_button: [],
|
||||
lawn_mower: ["error", "paused", "mowing", "docked"],
|
||||
lawn_mower: ["error", "paused", "mowing", "returning", "docked"],
|
||||
light: ["on", "off"],
|
||||
lock: [
|
||||
"jammed",
|
||||
|
||||
@@ -20,6 +20,15 @@ function findNestedItem(
|
||||
}, obj);
|
||||
}
|
||||
|
||||
function updateNestedItem(obj: any, path: ItemPath): any {
|
||||
const lastKey = path.pop()!;
|
||||
const parent = findNestedItem(obj, path);
|
||||
parent[lastKey] = Array.isArray(parent[lastKey])
|
||||
? [...parent[lastKey]]
|
||||
: [parent[lastKey]];
|
||||
return obj;
|
||||
}
|
||||
|
||||
export function nestedArrayMove<A>(
|
||||
obj: A,
|
||||
oldIndex: number,
|
||||
@@ -27,14 +36,18 @@ export function nestedArrayMove<A>(
|
||||
oldPath?: ItemPath,
|
||||
newPath?: ItemPath
|
||||
): A {
|
||||
const newObj = (Array.isArray(obj) ? [...obj] : { ...obj }) as A;
|
||||
let newObj = (Array.isArray(obj) ? [...obj] : { ...obj }) as A;
|
||||
|
||||
if (oldPath) {
|
||||
newObj = updateNestedItem(newObj, [...oldPath]);
|
||||
}
|
||||
if (newPath) {
|
||||
newObj = updateNestedItem(newObj, [...newPath]);
|
||||
}
|
||||
|
||||
const from = oldPath ? findNestedItem(newObj, oldPath) : newObj;
|
||||
const to = newPath ? findNestedItem(newObj, newPath, true) : newObj;
|
||||
|
||||
if (!Array.isArray(from) || !Array.isArray(to)) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
const item = from.splice(oldIndex, 1)[0];
|
||||
to.splice(newIndex, 0, item);
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { ChartEvent } from "chart.js";
|
||||
|
||||
export const clickIsTouch = (event: ChartEvent): boolean =>
|
||||
!(event.native instanceof MouseEvent) ||
|
||||
(event.native instanceof PointerEvent &&
|
||||
event.native.pointerType !== "mouse");
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
HaChartBase,
|
||||
MIN_TIME_BETWEEN_UPDATES,
|
||||
} from "./ha-chart-base";
|
||||
import { clickIsTouch } from "./click_is_touch";
|
||||
|
||||
const safeParseFloat = (value) => {
|
||||
const parsed = parseFloat(value);
|
||||
@@ -220,12 +221,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
onClick: (e: any) => {
|
||||
if (
|
||||
!this.clickForMoreInfo ||
|
||||
!(e.native instanceof MouseEvent) ||
|
||||
(e.native instanceof PointerEvent &&
|
||||
e.native.pointerType !== "mouse")
|
||||
) {
|
||||
if (!this.clickForMoreInfo || clickIsTouch(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from "./ha-chart-base";
|
||||
import type { TimeLineData } from "./timeline-chart/const";
|
||||
import { computeTimelineColor } from "./timeline-chart/timeline-color";
|
||||
import { clickIsTouch } from "./click_is_touch";
|
||||
|
||||
@customElement("state-history-chart-timeline")
|
||||
export class StateHistoryChartTimeline extends LitElement {
|
||||
@@ -224,11 +225,7 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
onClick: (e: any) => {
|
||||
if (
|
||||
!this.clickForMoreInfo ||
|
||||
!(e.native instanceof MouseEvent) ||
|
||||
(e.native instanceof PointerEvent && e.native.pointerType !== "mouse")
|
||||
) {
|
||||
if (!this.clickForMoreInfo || clickIsTouch(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import type {
|
||||
ChartDatasetExtra,
|
||||
HaChartBase,
|
||||
} from "./ha-chart-base";
|
||||
import { clickIsTouch } from "./click_is_touch";
|
||||
|
||||
export const supportedStatTypeMap: Record<StatisticType, StatisticType> = {
|
||||
mean: "mean",
|
||||
@@ -278,11 +279,7 @@ export class StatisticsChart extends LitElement {
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
onClick: (e: any) => {
|
||||
if (
|
||||
!this.clickForMoreInfo ||
|
||||
!(e.native instanceof MouseEvent) ||
|
||||
(e.native instanceof PointerEvent && e.native.pointerType !== "mouse")
|
||||
) {
|
||||
if (!this.clickForMoreInfo || clickIsTouch(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { groupBy } from "../../common/util/group-by";
|
||||
import { nextRender } from "../../common/util/render-status";
|
||||
import { haStyleScrollbar } from "../../resources/styles";
|
||||
import { loadVirtualizer } from "../../resources/virtualizer";
|
||||
import { HomeAssistant } from "../../types";
|
||||
@@ -35,6 +34,7 @@ import "../ha-svg-icon";
|
||||
import "../search-input";
|
||||
import { filterData, sortData } from "./sort-filter";
|
||||
import { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { nextRender } from "../../common/util/render-status";
|
||||
|
||||
export interface RowClickedEvent {
|
||||
id: string;
|
||||
@@ -169,8 +169,6 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
@query("slot[name='header']") private _header!: HTMLSlotElement;
|
||||
|
||||
@state() private _items: DataTableRowData[] = [];
|
||||
|
||||
@state() private _collapsedGroups: string[] = [];
|
||||
|
||||
private _checkableRowsCount?: number;
|
||||
@@ -179,7 +177,9 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
private _sortColumns: SortableColumnContainer = {};
|
||||
|
||||
private curRequest = 0;
|
||||
private _curRequest = 0;
|
||||
|
||||
private _lastUpdate = 0;
|
||||
|
||||
// @ts-ignore
|
||||
@restoreScroll(".scroller") private _savedScrollPos?: number;
|
||||
@@ -204,11 +204,34 @@ export class HaDataTable extends LitElement {
|
||||
this._checkedRowsChanged();
|
||||
}
|
||||
|
||||
public select(ids: string[], clear?: boolean): void {
|
||||
if (clear) {
|
||||
this._checkedRows = [];
|
||||
}
|
||||
ids.forEach((id) => {
|
||||
const row = this._filteredData.find((data) => data[this.id] === id);
|
||||
if (row?.selectable !== false && !this._checkedRows.includes(id)) {
|
||||
this._checkedRows.push(id);
|
||||
}
|
||||
});
|
||||
this._checkedRowsChanged();
|
||||
}
|
||||
|
||||
public unselect(ids: string[]): void {
|
||||
ids.forEach((id) => {
|
||||
const index = this._checkedRows.indexOf(id);
|
||||
if (index > -1) {
|
||||
this._checkedRows.splice(index, 1);
|
||||
}
|
||||
});
|
||||
this._checkedRowsChanged();
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this._items.length) {
|
||||
if (this._filteredData.length) {
|
||||
// Force update of location of rows
|
||||
this._items = [...this._items];
|
||||
this._filteredData = [...this._filteredData];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,16 +314,13 @@ export class HaDataTable extends LitElement {
|
||||
properties.has("columns") ||
|
||||
properties.has("_filter") ||
|
||||
properties.has("sortColumn") ||
|
||||
properties.has("sortDirection") ||
|
||||
properties.has("groupColumn") ||
|
||||
properties.has("groupOrder") ||
|
||||
properties.has("_collapsedGroups")
|
||||
properties.has("sortDirection")
|
||||
) {
|
||||
this._sortFilterData();
|
||||
}
|
||||
|
||||
if (properties.has("selectable") || properties.has("hiddenColumns")) {
|
||||
this._items = [...this._items];
|
||||
this._filteredData = [...this._filteredData];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,7 +487,15 @@ export class HaDataTable extends LitElement {
|
||||
scroller
|
||||
class="mdc-data-table__content scroller ha-scrollbar"
|
||||
@scroll=${this._saveScrollPos}
|
||||
.items=${this._items}
|
||||
.items=${this._groupData(
|
||||
this._filteredData,
|
||||
localize,
|
||||
this.appendRow,
|
||||
this.hasFab,
|
||||
this.groupColumn,
|
||||
this.groupOrder,
|
||||
this._collapsedGroups
|
||||
)}
|
||||
.keyFunction=${this._keyFunction}
|
||||
.renderItem=${renderRow}
|
||||
></lit-virtualizer>
|
||||
@@ -602,8 +630,13 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
private async _sortFilterData() {
|
||||
const startTime = new Date().getTime();
|
||||
this.curRequest++;
|
||||
const curRequest = this.curRequest;
|
||||
const timeBetweenUpdate = startTime - this._lastUpdate;
|
||||
const timeBetweenRequest = startTime - this._curRequest;
|
||||
this._curRequest = startTime;
|
||||
|
||||
const forceUpdate =
|
||||
!this._lastUpdate ||
|
||||
(timeBetweenUpdate > 500 && timeBetweenRequest < 500);
|
||||
|
||||
let filteredData = this.data;
|
||||
if (this._filter) {
|
||||
@@ -614,6 +647,10 @@ export class HaDataTable extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
if (!forceUpdate && this._curRequest !== startTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prom = this.sortColumn
|
||||
? sortData(
|
||||
filteredData,
|
||||
@@ -634,91 +671,103 @@ export class HaDataTable extends LitElement {
|
||||
setTimeout(resolve, 100 - elapsed);
|
||||
});
|
||||
}
|
||||
if (this.curRequest !== curRequest) {
|
||||
|
||||
if (!forceUpdate && this._curRequest !== startTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
const localize = this.localizeFunc || this.hass.localize;
|
||||
|
||||
if (this.appendRow || this.hasFab || this.groupColumn) {
|
||||
let items = [...data];
|
||||
|
||||
if (this.groupColumn) {
|
||||
const grouped = groupBy(items, (item) => item[this.groupColumn!]);
|
||||
if (grouped.undefined) {
|
||||
// make sure ungrouped items are at the bottom
|
||||
grouped[UNDEFINED_GROUP_KEY] = grouped.undefined;
|
||||
delete grouped.undefined;
|
||||
}
|
||||
const sorted: {
|
||||
[key: string]: DataTableRowData[];
|
||||
} = Object.keys(grouped)
|
||||
.sort((a, b) => {
|
||||
const orderA = this.groupOrder?.indexOf(a) ?? -1;
|
||||
const orderB = this.groupOrder?.indexOf(b) ?? -1;
|
||||
if (orderA !== orderB) {
|
||||
if (orderA === -1) {
|
||||
return 1;
|
||||
}
|
||||
if (orderB === -1) {
|
||||
return -1;
|
||||
}
|
||||
return orderA - orderB;
|
||||
}
|
||||
return stringCompare(
|
||||
["", "-", "—"].includes(a) ? "zzz" : a,
|
||||
["", "-", "—"].includes(b) ? "zzz" : b,
|
||||
this.hass.locale.language
|
||||
);
|
||||
})
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = grouped[key];
|
||||
return obj;
|
||||
}, {});
|
||||
const groupedItems: DataTableRowData[] = [];
|
||||
Object.entries(sorted).forEach(([groupName, rows]) => {
|
||||
groupedItems.push({
|
||||
append: true,
|
||||
content: html`<div
|
||||
class="mdc-data-table__cell group-header"
|
||||
role="cell"
|
||||
.group=${groupName}
|
||||
@click=${this._collapseGroup}
|
||||
>
|
||||
<ha-icon-button
|
||||
.path=${mdiChevronUp}
|
||||
class=${this._collapsedGroups.includes(groupName)
|
||||
? "collapsed"
|
||||
: ""}
|
||||
>
|
||||
</ha-icon-button>
|
||||
${groupName === UNDEFINED_GROUP_KEY
|
||||
? localize("ui.components.data-table.ungrouped")
|
||||
: groupName || ""}
|
||||
</div>`,
|
||||
});
|
||||
if (!this._collapsedGroups.includes(groupName)) {
|
||||
groupedItems.push(...rows);
|
||||
}
|
||||
});
|
||||
items = groupedItems;
|
||||
}
|
||||
|
||||
if (this.appendRow) {
|
||||
items.push({ append: true, content: this.appendRow });
|
||||
}
|
||||
|
||||
if (this.hasFab) {
|
||||
items.push({ empty: true });
|
||||
}
|
||||
|
||||
this._items = items;
|
||||
} else {
|
||||
this._items = data;
|
||||
}
|
||||
this._lastUpdate = startTime;
|
||||
this._filteredData = data;
|
||||
}
|
||||
|
||||
private _groupData = memoizeOne(
|
||||
(
|
||||
data: DataTableRowData[],
|
||||
localize: LocalizeFunc,
|
||||
appendRow,
|
||||
hasFab: boolean,
|
||||
groupColumn: string | undefined,
|
||||
groupOrder: string[] | undefined,
|
||||
collapsedGroups: string[]
|
||||
) => {
|
||||
if (appendRow || hasFab || groupColumn) {
|
||||
let items = [...data];
|
||||
|
||||
if (groupColumn) {
|
||||
const grouped = groupBy(items, (item) => item[groupColumn]);
|
||||
if (grouped.undefined) {
|
||||
// make sure ungrouped items are at the bottom
|
||||
grouped[UNDEFINED_GROUP_KEY] = grouped.undefined;
|
||||
delete grouped.undefined;
|
||||
}
|
||||
const sorted: {
|
||||
[key: string]: DataTableRowData[];
|
||||
} = Object.keys(grouped)
|
||||
.sort((a, b) => {
|
||||
const orderA = groupOrder?.indexOf(a) ?? -1;
|
||||
const orderB = groupOrder?.indexOf(b) ?? -1;
|
||||
if (orderA !== orderB) {
|
||||
if (orderA === -1) {
|
||||
return 1;
|
||||
}
|
||||
if (orderB === -1) {
|
||||
return -1;
|
||||
}
|
||||
return orderA - orderB;
|
||||
}
|
||||
return stringCompare(
|
||||
["", "-", "—"].includes(a) ? "zzz" : a,
|
||||
["", "-", "—"].includes(b) ? "zzz" : b,
|
||||
this.hass.locale.language
|
||||
);
|
||||
})
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = grouped[key];
|
||||
return obj;
|
||||
}, {});
|
||||
const groupedItems: DataTableRowData[] = [];
|
||||
Object.entries(sorted).forEach(([groupName, rows]) => {
|
||||
groupedItems.push({
|
||||
append: true,
|
||||
content: html`<div
|
||||
class="mdc-data-table__cell group-header"
|
||||
role="cell"
|
||||
.group=${groupName}
|
||||
@click=${this._collapseGroup}
|
||||
>
|
||||
<ha-icon-button
|
||||
.path=${mdiChevronUp}
|
||||
class=${collapsedGroups.includes(groupName)
|
||||
? "collapsed"
|
||||
: ""}
|
||||
>
|
||||
</ha-icon-button>
|
||||
${groupName === UNDEFINED_GROUP_KEY
|
||||
? localize("ui.components.data-table.ungrouped")
|
||||
: groupName || ""}
|
||||
</div>`,
|
||||
});
|
||||
if (!collapsedGroups.includes(groupName)) {
|
||||
groupedItems.push(...rows);
|
||||
}
|
||||
});
|
||||
items = groupedItems;
|
||||
}
|
||||
|
||||
if (appendRow) {
|
||||
items.push({ append: true, content: appendRow });
|
||||
}
|
||||
|
||||
if (hasFab) {
|
||||
items.push({ empty: true });
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
);
|
||||
|
||||
private _memFilterData = memoizeOne(
|
||||
(
|
||||
data: DataTableRowData[],
|
||||
@@ -802,8 +851,8 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
private _checkedRowsChanged() {
|
||||
// force scroller to update, change it's items
|
||||
if (this._items.length) {
|
||||
this._items = [...this._items];
|
||||
if (this._filteredData.length) {
|
||||
this._filteredData = [...this._filteredData];
|
||||
}
|
||||
fireEvent(this, "selection-changed", {
|
||||
value: this._checkedRows,
|
||||
@@ -985,6 +1034,7 @@ export class HaDataTable extends LitElement {
|
||||
/* @noflip */
|
||||
padding-inline-end: initial;
|
||||
width: 60px;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.mdc-data-table__table {
|
||||
|
||||
@@ -26,7 +26,7 @@ class HaDeviceTriggerPicker extends HaDeviceAutomationPicker<DeviceTrigger> {
|
||||
fetchDeviceTriggers,
|
||||
(deviceId?: string) => ({
|
||||
device_id: deviceId || "",
|
||||
platform: "device",
|
||||
trigger: "device",
|
||||
domain: "",
|
||||
entity_id: "",
|
||||
})
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
||||
import type { ValueChangedEvent, HomeAssistant } from "../../types";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "./ha-entity-picker";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./ha-entity-picker";
|
||||
|
||||
@@ -98,10 +97,7 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
.excludeEntities=${this.excludeEntities}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeUnitOfMeasurement=${this.includeUnitOfMeasurement}
|
||||
.entityFilter=${this._getEntityFilter(
|
||||
this.value,
|
||||
this.entityFilter
|
||||
)}
|
||||
.entityFilter=${this.entityFilter}
|
||||
.value=${entityId}
|
||||
.label=${this.pickedEntityLabel}
|
||||
.disabled=${this.disabled}
|
||||
@@ -118,10 +114,13 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
.includeEntities=${this.includeEntities}
|
||||
.excludeEntities=${this.excludeEntities}
|
||||
.excludeEntities=${this._excludeEntities(
|
||||
this.value,
|
||||
this.excludeEntities
|
||||
)}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeUnitOfMeasurement=${this.includeUnitOfMeasurement}
|
||||
.entityFilter=${this._getEntityFilter(this.value, this.entityFilter)}
|
||||
.entityFilter=${this.entityFilter}
|
||||
.label=${this.pickEntityLabel}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
@@ -133,14 +132,16 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _getEntityFilter = memoizeOne(
|
||||
private _excludeEntities = memoizeOne(
|
||||
(
|
||||
value: string[] | undefined,
|
||||
entityFilter: HaEntityPickerEntityFilterFunc | undefined
|
||||
): HaEntityPickerEntityFilterFunc =>
|
||||
(stateObj: HassEntity) =>
|
||||
(!value || !value.includes(stateObj.entity_id)) &&
|
||||
(!entityFilter || entityFilter(stateObj))
|
||||
excludeEntities: string[] | undefined
|
||||
): string[] | undefined => {
|
||||
if (value === undefined) {
|
||||
return excludeEntities;
|
||||
}
|
||||
return [...(excludeEntities || []), ...value];
|
||||
}
|
||||
);
|
||||
|
||||
private get _currentEntities() {
|
||||
|
||||
@@ -87,7 +87,7 @@ export class HaEntityPicker extends LitElement {
|
||||
public includeUnitOfMeasurement?: string[];
|
||||
|
||||
/**
|
||||
* List of allowed entities to show. Will ignore all other filters.
|
||||
* List of allowed entities to show.
|
||||
* @type {Array}
|
||||
* @attr include-entities
|
||||
*/
|
||||
@@ -220,30 +220,13 @@ export class HaEntityPicker extends LitElement {
|
||||
|
||||
if (includeEntities) {
|
||||
entityIds = entityIds.filter((entityId) =>
|
||||
this.includeEntities!.includes(entityId)
|
||||
includeEntities.includes(entityId)
|
||||
);
|
||||
|
||||
return entityIds
|
||||
.map((key) => {
|
||||
const friendly_name = computeStateName(hass!.states[key]) || key;
|
||||
return {
|
||||
...hass!.states[key],
|
||||
friendly_name,
|
||||
strings: [key, friendly_name],
|
||||
};
|
||||
})
|
||||
.sort((entityA, entityB) =>
|
||||
caseInsensitiveStringCompare(
|
||||
entityA.friendly_name,
|
||||
entityB.friendly_name,
|
||||
this.hass.locale.language
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (excludeEntities) {
|
||||
entityIds = entityIds.filter(
|
||||
(entityId) => !excludeEntities!.includes(entityId)
|
||||
(entityId) => !excludeEntities.includes(entityId)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,9 @@ class HaEntityStatePicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "allow-name" }) public allowName =
|
||||
false;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public value?: string[] | string;
|
||||
@@ -95,43 +98,55 @@ class HaEntityStatePicker extends LitElement {
|
||||
return !(!changedProps.has("_opened") && this._opened);
|
||||
}
|
||||
|
||||
private options = memoizeOne((entityId?: string, stateObj?: HassEntity) => {
|
||||
const domain = entityId ? computeDomain(entityId) : undefined;
|
||||
return [
|
||||
{
|
||||
label: this.hass.localize("ui.components.state-content-picker.state"),
|
||||
value: "state",
|
||||
},
|
||||
{
|
||||
label: this.hass.localize(
|
||||
"ui.components.state-content-picker.last_changed"
|
||||
),
|
||||
value: "last_changed",
|
||||
},
|
||||
{
|
||||
label: this.hass.localize(
|
||||
"ui.components.state-content-picker.last_updated"
|
||||
),
|
||||
value: "last_updated",
|
||||
},
|
||||
...(domain
|
||||
? STATE_DISPLAY_SPECIAL_CONTENT.filter((content) =>
|
||||
STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS[domain]?.includes(content)
|
||||
).map((content) => ({
|
||||
label: this.hass.localize(
|
||||
`ui.components.state-content-picker.${content}`
|
||||
),
|
||||
value: content,
|
||||
}))
|
||||
: []),
|
||||
...Object.keys(stateObj?.attributes ?? {})
|
||||
.filter((a) => !HIDDEN_ATTRIBUTES.includes(a))
|
||||
.map((attribute) => ({
|
||||
value: attribute,
|
||||
label: this.hass.formatEntityAttributeName(stateObj!, attribute),
|
||||
})),
|
||||
];
|
||||
});
|
||||
private options = memoizeOne(
|
||||
(entityId?: string, stateObj?: HassEntity, allowName?: boolean) => {
|
||||
const domain = entityId ? computeDomain(entityId) : undefined;
|
||||
return [
|
||||
{
|
||||
label: this.hass.localize("ui.components.state-content-picker.state"),
|
||||
value: "state",
|
||||
},
|
||||
...(allowName
|
||||
? [
|
||||
{
|
||||
label: this.hass.localize(
|
||||
"ui.components.state-content-picker.name"
|
||||
),
|
||||
value: "name",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: this.hass.localize(
|
||||
"ui.components.state-content-picker.last_changed"
|
||||
),
|
||||
value: "last_changed",
|
||||
},
|
||||
{
|
||||
label: this.hass.localize(
|
||||
"ui.components.state-content-picker.last_updated"
|
||||
),
|
||||
value: "last_updated",
|
||||
},
|
||||
...(domain
|
||||
? STATE_DISPLAY_SPECIAL_CONTENT.filter((content) =>
|
||||
STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS[domain]?.includes(content)
|
||||
).map((content) => ({
|
||||
label: this.hass.localize(
|
||||
`ui.components.state-content-picker.${content}`
|
||||
),
|
||||
value: content,
|
||||
}))
|
||||
: []),
|
||||
...Object.keys(stateObj?.attributes ?? {})
|
||||
.filter((a) => !HIDDEN_ATTRIBUTES.includes(a))
|
||||
.map((attribute) => ({
|
||||
value: attribute,
|
||||
label: this.hass.formatEntityAttributeName(stateObj!, attribute),
|
||||
})),
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
private _filter = "";
|
||||
|
||||
@@ -146,7 +161,7 @@ class HaEntityStatePicker extends LitElement {
|
||||
? this.hass.states[this.entityId]
|
||||
: undefined;
|
||||
|
||||
const options = this.options(this.entityId, stateObj);
|
||||
const options = this.options(this.entityId, stateObj, this.allowName);
|
||||
const optionItems = options.filter(
|
||||
(option) => !this._value.includes(option.value)
|
||||
);
|
||||
@@ -158,6 +173,7 @@ class HaEntityStatePicker extends LitElement {
|
||||
no-style
|
||||
@item-moved=${this._moveItem}
|
||||
.disabled=${this.disabled}
|
||||
filter="button.trailing.action"
|
||||
>
|
||||
<ha-chip-set>
|
||||
${repeat(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { mdiTextureBox } from "@mdi/js";
|
||||
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { LitElement, PropertyValues, TemplateResult, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
@@ -20,12 +20,7 @@ import {
|
||||
getDeviceEntityDisplayLookup,
|
||||
} from "../data/device_registry";
|
||||
import { EntityRegistryDisplayEntry } from "../data/entity_registry";
|
||||
import {
|
||||
FloorRegistryEntry,
|
||||
getFloorAreaLookup,
|
||||
subscribeFloorRegistry,
|
||||
} from "../data/floor_registry";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { FloorRegistryEntry, getFloorAreaLookup } from "../data/floor_registry";
|
||||
import { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import "./ha-combo-box";
|
||||
@@ -50,7 +45,7 @@ interface FloorAreaEntry {
|
||||
}
|
||||
|
||||
@customElement("ha-area-floor-picker")
|
||||
export class HaAreaFloorPicker extends SubscribeMixin(LitElement) {
|
||||
export class HaAreaFloorPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
@@ -111,22 +106,12 @@ export class HaAreaFloorPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@state() private _floors?: FloorRegistryEntry[];
|
||||
|
||||
@state() private _opened?: boolean;
|
||||
|
||||
@query("ha-combo-box", true) public comboBox!: HaComboBox;
|
||||
|
||||
private _init = false;
|
||||
|
||||
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
|
||||
return [
|
||||
subscribeFloorRegistry(this.hass.connection, (floors) => {
|
||||
this._floors = floors;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public async open() {
|
||||
await this.updateComplete;
|
||||
await this.comboBox?.open();
|
||||
@@ -431,12 +416,12 @@ export class HaAreaFloorPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (
|
||||
(!this._init && this.hass && this._floors) ||
|
||||
(!this._init && this.hass) ||
|
||||
(this._init && changedProps.has("_opened") && this._opened)
|
||||
) {
|
||||
this._init = true;
|
||||
const areas = this._getAreas(
|
||||
this._floors!,
|
||||
Object.values(this.hass.floors),
|
||||
Object.values(this.hass.areas),
|
||||
Object.values(this.hass.devices),
|
||||
Object.values(this.hass.entities),
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import "./ha-ripple";
|
||||
|
||||
type BadgeType = "badge" | "button";
|
||||
|
||||
@customElement("ha-badge")
|
||||
export class HaBadge extends LitElement {
|
||||
@property() public type: BadgeType = "badge";
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "icon-only" }) iconOnly = false;
|
||||
|
||||
protected render() {
|
||||
const label = this.label;
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="badge ${classMap({
|
||||
"icon-only": this.iconOnly,
|
||||
})}"
|
||||
role=${ifDefined(this.type === "button" ? "button" : undefined)}
|
||||
tabindex=${ifDefined(this.type === "button" ? "0" : undefined)}
|
||||
>
|
||||
<ha-ripple .disabled=${this.type !== "button"}></ha-ripple>
|
||||
<slot name="icon"></slot>
|
||||
${this.iconOnly
|
||||
? nothing
|
||||
: html`<span class="info">
|
||||
${label ? html`<span class="label">${label}</span>` : nothing}
|
||||
<span class="content"><slot></slot></span>
|
||||
</span>`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
--badge-color: var(--secondary-text-color);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.badge {
|
||||
position: relative;
|
||||
--ha-ripple-color: var(--badge-color);
|
||||
--ha-ripple-hover-opacity: 0.04;
|
||||
--ha-ripple-pressed-opacity: 0.12;
|
||||
transition:
|
||||
box-shadow 180ms ease-in-out,
|
||||
border-color 180ms ease-in-out;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
height: var(--ha-badge-size, 36px);
|
||||
min-width: var(--ha-badge-size, 36px);
|
||||
padding: 0px 12px;
|
||||
box-sizing: border-box;
|
||||
width: auto;
|
||||
border-radius: var(
|
||||
--ha-badge-border-radius,
|
||||
calc(var(--ha-badge-size, 36px) / 2)
|
||||
);
|
||||
background: var(
|
||||
--ha-card-background,
|
||||
var(--card-background-color, white)
|
||||
);
|
||||
-webkit-backdrop-filter: var(--ha-card-backdrop-filter, none);
|
||||
backdrop-filter: var(--ha-card-backdrop-filter, none);
|
||||
border-width: var(--ha-card-border-width, 1px);
|
||||
box-shadow: var(--ha-card-box-shadow, none);
|
||||
border-style: solid;
|
||||
border-color: var(
|
||||
--ha-card-border-color,
|
||||
var(--divider-color, #e0e0e0)
|
||||
);
|
||||
}
|
||||
.badge:focus-visible {
|
||||
--shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent);
|
||||
--shadow-focus: 0 0 0 1px var(--badge-color);
|
||||
border-color: var(--badge-color);
|
||||
box-shadow: var(--shadow-default), var(--shadow-focus);
|
||||
}
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
[role="button"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding-inline-start: initial;
|
||||
text-align: center;
|
||||
font-family: Roboto;
|
||||
}
|
||||
.label {
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 10px;
|
||||
letter-spacing: 0.1px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.content {
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
letter-spacing: 0.1px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
::slotted([slot="icon"]) {
|
||||
--mdc-icon-size: 18px;
|
||||
color: var(--badge-color);
|
||||
line-height: 0;
|
||||
margin-left: -4px;
|
||||
margin-right: 0;
|
||||
margin-inline-start: -4px;
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
::slotted(img[slot="icon"]) {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
overflow: hidden;
|
||||
margin-left: -10px;
|
||||
margin-right: 0;
|
||||
margin-inline-start: -10px;
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
.badge.icon-only {
|
||||
padding: 0;
|
||||
}
|
||||
.badge.icon-only ::slotted([slot="icon"]) {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-inline-start: 0;
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-badge": HaBadge;
|
||||
}
|
||||
}
|
||||
@@ -124,9 +124,12 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
const transactions: TransactionSpec[] = [];
|
||||
if (changedProps.has("mode")) {
|
||||
transactions.push({
|
||||
effects: this._loadedCodeMirror!.langCompartment!.reconfigure(
|
||||
this._mode
|
||||
),
|
||||
effects: [
|
||||
this._loadedCodeMirror!.langCompartment!.reconfigure(this._mode),
|
||||
this._loadedCodeMirror!.foldingCompartment.reconfigure(
|
||||
this._getFoldingExtensions()
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
if (changedProps.has("readOnly")) {
|
||||
@@ -177,6 +180,14 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
this._loadedCodeMirror.crosshairCursor(),
|
||||
this._loadedCodeMirror.highlightSelectionMatches(),
|
||||
this._loadedCodeMirror.highlightActiveLine(),
|
||||
this._loadedCodeMirror.indentationMarkers({
|
||||
thickness: 0,
|
||||
activeThickness: 1,
|
||||
colors: {
|
||||
activeLight: "var(--secondary-text-color)",
|
||||
activeDark: "var(--secondary-text-color)",
|
||||
},
|
||||
}),
|
||||
this._loadedCodeMirror.keymap.of([
|
||||
...this._loadedCodeMirror.defaultKeymap,
|
||||
...this._loadedCodeMirror.searchKeymap,
|
||||
@@ -194,6 +205,9 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
this.linewrap ? this._loadedCodeMirror.EditorView.lineWrapping : []
|
||||
),
|
||||
this._loadedCodeMirror.EditorView.updateListener.of(this._onUpdate),
|
||||
this._loadedCodeMirror.foldingCompartment.of(
|
||||
this._getFoldingExtensions()
|
||||
),
|
||||
];
|
||||
|
||||
if (!this.readOnly) {
|
||||
@@ -311,6 +325,17 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
fireEvent(this, "value-changed", { value: this._value });
|
||||
};
|
||||
|
||||
private _getFoldingExtensions = (): Extension => {
|
||||
if (this.mode === "yaml") {
|
||||
return [
|
||||
this._loadedCodeMirror!.foldGutter(),
|
||||
this._loadedCodeMirror!.foldingOnIndent,
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host(.error-state) .cm-gutters {
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiInvertColorsOff, mdiPalette } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeCssColor, THEME_COLORS } from "../common/color/compute-color";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import "./ha-select";
|
||||
import "./ha-list-item";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { LocalizeKeys } from "../common/translations/localize";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-list-item";
|
||||
import "./ha-md-divider";
|
||||
import "./ha-select";
|
||||
import type { HaSelect } from "./ha-select";
|
||||
|
||||
@customElement("ha-color-picker")
|
||||
export class HaColorPicker extends LitElement {
|
||||
@@ -20,43 +22,97 @@ export class HaColorPicker extends LitElement {
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property({ type: Boolean }) public defaultColor = false;
|
||||
@property({ type: String, attribute: "default_color" })
|
||||
public defaultColor?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "include_state" })
|
||||
public includeState = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "include_none" })
|
||||
public includeNone = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
_valueSelected(ev) {
|
||||
@query("ha-select") private _select?: HaSelect;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
// Refresh layout options when the field is connected to the DOM to ensure current value displayed
|
||||
this._select?.layoutOptions();
|
||||
}
|
||||
|
||||
private _valueSelected(ev) {
|
||||
ev.stopPropagation();
|
||||
if (!this.isConnected) return;
|
||||
const value = ev.target.value;
|
||||
if (value) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: value !== "default" ? value : undefined,
|
||||
});
|
||||
}
|
||||
this.value = value === this.defaultColor ? undefined : value;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: this.value,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const value = this.value || this.defaultColor || "";
|
||||
|
||||
const isCustom = !(
|
||||
THEME_COLORS.has(value) ||
|
||||
value === "none" ||
|
||||
value === "state"
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-select
|
||||
.icon=${Boolean(this.value)}
|
||||
.icon=${Boolean(value)}
|
||||
.label=${this.label}
|
||||
.value=${this.value || "default"}
|
||||
.value=${value}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
@closed=${stopPropagation}
|
||||
@selected=${this._valueSelected}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
.clearable=${!this.defaultColor}
|
||||
>
|
||||
${this.value
|
||||
${value
|
||||
? html`
|
||||
<span slot="icon">
|
||||
${this.renderColorCircle(this.value || "grey")}
|
||||
${value === "none"
|
||||
? html`
|
||||
<ha-svg-icon path=${mdiInvertColorsOff}></ha-svg-icon>
|
||||
`
|
||||
: value === "state"
|
||||
? html`<ha-svg-icon path=${mdiPalette}></ha-svg-icon>`
|
||||
: this.renderColorCircle(value || "grey")}
|
||||
</span>
|
||||
`
|
||||
: nothing}
|
||||
${this.defaultColor
|
||||
? html` <ha-list-item value="default">
|
||||
${this.hass.localize(`ui.components.color-picker.default_color`)}
|
||||
</ha-list-item>`
|
||||
${this.includeNone
|
||||
? html`
|
||||
<ha-list-item value="none" graphic="icon">
|
||||
${this.hass.localize("ui.components.color-picker.none")}
|
||||
${this.defaultColor === "none"
|
||||
? ` (${this.hass.localize("ui.components.color-picker.default")})`
|
||||
: nothing}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
path=${mdiInvertColorsOff}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
`
|
||||
: nothing}
|
||||
${this.includeState
|
||||
? html`
|
||||
<ha-list-item value="state" graphic="icon">
|
||||
${this.hass.localize("ui.components.color-picker.state")}
|
||||
${this.defaultColor === "state"
|
||||
? ` (${this.hass.localize("ui.components.color-picker.default")})`
|
||||
: nothing}
|
||||
<ha-svg-icon slot="graphic" path=${mdiPalette}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
`
|
||||
: nothing}
|
||||
${this.includeState || this.includeNone
|
||||
? html`<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>`
|
||||
: nothing}
|
||||
${Array.from(THEME_COLORS).map(
|
||||
(color) => html`
|
||||
@@ -64,10 +120,21 @@ export class HaColorPicker extends LitElement {
|
||||
${this.hass.localize(
|
||||
`ui.components.color-picker.colors.${color}` as LocalizeKeys
|
||||
) || color}
|
||||
${this.defaultColor === color
|
||||
? ` (${this.hass.localize("ui.components.color-picker.default")})`
|
||||
: nothing}
|
||||
<span slot="graphic">${this.renderColorCircle(color)}</span>
|
||||
</ha-list-item>
|
||||
`
|
||||
)}
|
||||
${isCustom
|
||||
? html`
|
||||
<ha-list-item .value=${value} graphic="icon">
|
||||
${value}
|
||||
<span slot="graphic">${this.renderColorCircle(value)}</span>
|
||||
</ha-list-item>
|
||||
`
|
||||
: nothing}
|
||||
</ha-select>
|
||||
`;
|
||||
}
|
||||
@@ -87,10 +154,11 @@ export class HaColorPicker extends LitElement {
|
||||
return css`
|
||||
.circle-color {
|
||||
display: block;
|
||||
background-color: var(--circle-color);
|
||||
background-color: var(--circle-color, var(--divider-color));
|
||||
border-radius: 10px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
ha-select {
|
||||
width: 100%;
|
||||
|
||||
@@ -45,15 +45,35 @@ export class HaConversationAgentPicker extends LitElement {
|
||||
if (!this._agents) {
|
||||
return nothing;
|
||||
}
|
||||
const value =
|
||||
this.value ??
|
||||
(this.required &&
|
||||
(!this.language ||
|
||||
this._agents
|
||||
.find((agent) => agent.id === "homeassistant")
|
||||
?.supported_languages.includes(this.language))
|
||||
? "homeassistant"
|
||||
: NONE);
|
||||
let value = this.value;
|
||||
if (!value && this.required) {
|
||||
// Select Home Assistant conversation agent if it supports the language
|
||||
for (const agent of this._agents) {
|
||||
if (
|
||||
agent.id === "conversation.home_assistant" &&
|
||||
agent.supported_languages.includes(this.language!)
|
||||
) {
|
||||
value = agent.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!value) {
|
||||
// Select the first agent that supports the language
|
||||
for (const agent of this._agents) {
|
||||
if (
|
||||
agent.supported_languages === "*" &&
|
||||
agent.supported_languages.includes(this.language!)
|
||||
) {
|
||||
value = agent.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!value) {
|
||||
value = NONE;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-select
|
||||
.label=${this.label ||
|
||||
|
||||
@@ -10,8 +10,13 @@ export class HaDialogHeader extends LitElement {
|
||||
<section class="header-navigation-icon">
|
||||
<slot name="navigationIcon"></slot>
|
||||
</section>
|
||||
<section class="header-title">
|
||||
<slot name="title"></slot>
|
||||
<section class="header-content">
|
||||
<div class="header-title">
|
||||
<slot name="title"></slot>
|
||||
</div>
|
||||
<div class="header-subtitle">
|
||||
<slot name="subtitle"></slot>
|
||||
</div>
|
||||
</section>
|
||||
<section class="header-action-items">
|
||||
<slot name="actionItems"></slot>
|
||||
@@ -39,17 +44,24 @@ export class HaDialogHeader extends LitElement {
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.header-title {
|
||||
.header-content {
|
||||
flex: 1;
|
||||
font-size: 22px;
|
||||
line-height: 28px;
|
||||
font-weight: 400;
|
||||
padding: 10px 4px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.header-title {
|
||||
font-size: 22px;
|
||||
line-height: 28px;
|
||||
font-weight: 400;
|
||||
}
|
||||
.header-subtitle {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
@media all and (min-width: 450px) and (min-height: 500px) {
|
||||
.header-bar {
|
||||
padding: 12px;
|
||||
|
||||
@@ -68,8 +68,8 @@ export class HaExpansionPanel extends LitElement {
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: ""}
|
||||
<slot name="icons"></slot>
|
||||
</div>
|
||||
<slot name="icons"></slot>
|
||||
</div>
|
||||
<div
|
||||
class="container ${classMap({ expanded: this.expanded })}"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import "@material/mwc-menu/mwc-menu-surface";
|
||||
import { mdiFilterVariantRemove, mdiTextureBox } from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
@@ -15,13 +14,8 @@ import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import {
|
||||
FloorRegistryEntry,
|
||||
getFloorAreaLookup,
|
||||
subscribeFloorRegistry,
|
||||
} from "../data/floor_registry";
|
||||
import { getFloorAreaLookup } from "../data/floor_registry";
|
||||
import { RelatedResult, findRelated } from "../data/search";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-check-list-item";
|
||||
@@ -31,7 +25,7 @@ import "./ha-svg-icon";
|
||||
import "./ha-tree-indicator";
|
||||
|
||||
@customElement("ha-filter-floor-areas")
|
||||
export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
|
||||
export class HaFilterFloorAreas extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public value?: {
|
||||
@@ -47,8 +41,6 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _shouldRender = false;
|
||||
|
||||
@state() private _floors?: FloorRegistryEntry[];
|
||||
|
||||
public willUpdate(properties: PropertyValues) {
|
||||
super.willUpdate(properties);
|
||||
|
||||
@@ -60,7 +52,7 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const areas = this._areas(this.hass.areas, this._floors);
|
||||
const areas = this._areas(this.hass.areas, this.hass.floors);
|
||||
|
||||
return html`
|
||||
<ha-expansion-panel
|
||||
@@ -189,14 +181,6 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
|
||||
this._findRelated();
|
||||
}
|
||||
|
||||
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
|
||||
return [
|
||||
subscribeFloorRegistry(this.hass.connection, (floors) => {
|
||||
this._floors = floors;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected updated(changed) {
|
||||
if (changed.has("expanded") && this.expanded) {
|
||||
setTimeout(() => {
|
||||
@@ -220,9 +204,9 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _areas = memoizeOne(
|
||||
(areaReg: HomeAssistant["areas"], floors?: FloorRegistryEntry[]) => {
|
||||
(areaReg: HomeAssistant["areas"], floorReg: HomeAssistant["floors"]) => {
|
||||
const areas = Object.values(areaReg);
|
||||
|
||||
const floors = Object.values(floorReg);
|
||||
const floorAreaLookup = getFloorAreaLookup(areas);
|
||||
|
||||
const unassisgnedAreas = areas.filter(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { LitElement, PropertyValues, TemplateResult, html } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@@ -24,10 +24,8 @@ import {
|
||||
FloorRegistryEntry,
|
||||
createFloorRegistryEntry,
|
||||
getFloorAreaLookup,
|
||||
subscribeFloorRegistry,
|
||||
} from "../data/floor_registry";
|
||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { showFloorRegistryDetailDialog } from "../panels/config/areas/show-dialog-floor-registry-detail";
|
||||
import { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
@@ -53,7 +51,7 @@ const rowRenderer: ComboBoxLitRenderer<FloorRegistryEntry> = (item) =>
|
||||
</ha-list-item>`;
|
||||
|
||||
@customElement("ha-floor-picker")
|
||||
export class HaFloorPicker extends SubscribeMixin(LitElement) {
|
||||
export class HaFloorPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
@@ -111,8 +109,6 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _opened?: boolean;
|
||||
|
||||
@state() private _floors?: FloorRegistryEntry[];
|
||||
|
||||
@query("ha-combo-box", true) public comboBox!: HaComboBox;
|
||||
|
||||
private _suggestion?: string;
|
||||
@@ -129,14 +125,6 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
|
||||
await this.comboBox?.focus();
|
||||
}
|
||||
|
||||
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
|
||||
return [
|
||||
subscribeFloorRegistry(this.hass.connection, (floors) => {
|
||||
this._floors = floors;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
private _getFloors = memoizeOne(
|
||||
(
|
||||
floors: FloorRegistryEntry[],
|
||||
@@ -320,12 +308,12 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (
|
||||
(!this._init && this.hass && this._floors) ||
|
||||
(!this._init && this.hass) ||
|
||||
(this._init && changedProps.has("_opened") && this._opened)
|
||||
) {
|
||||
this._init = true;
|
||||
const floors = this._getFloors(
|
||||
this._floors!,
|
||||
Object.values(this.hass.floors),
|
||||
Object.values(this.hass.areas),
|
||||
Object.values(this.hass.devices),
|
||||
Object.values(this.hass.entities),
|
||||
@@ -360,8 +348,7 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
|
||||
? this.hass.localize("ui.components.floor-picker.floor")
|
||||
: this.label}
|
||||
.placeholder=${this.placeholder
|
||||
? this._floors?.find((floor) => floor.floor_id === this.placeholder)
|
||||
?.name
|
||||
? this.hass.floors[this.placeholder]?.name
|
||||
: undefined}
|
||||
.renderer=${rowRenderer}
|
||||
@filter-changed=${this._filterChanged}
|
||||
@@ -460,7 +447,7 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
|
||||
floor_id: floor.floor_id,
|
||||
});
|
||||
});
|
||||
const floors = [...this._floors!, floor];
|
||||
const floors = [...Object.values(this.hass.floors), floor];
|
||||
this.comboBox.filteredItems = this._getFloors(
|
||||
floors,
|
||||
Object.values(this.hass.areas)!,
|
||||
|
||||
@@ -95,10 +95,10 @@ export const computeInitialHaFormData = (
|
||||
} else if (
|
||||
"action" in selector ||
|
||||
"trigger" in selector ||
|
||||
"condition" in selector ||
|
||||
"media" in selector ||
|
||||
"target" in selector
|
||||
"condition" in selector
|
||||
) {
|
||||
data[field.name] = [];
|
||||
} else if ("media" in selector || "target" in selector) {
|
||||
data[field.name] = {};
|
||||
} else {
|
||||
throw new Error(
|
||||
|
||||
@@ -21,13 +21,49 @@ export class HaFormExpendable extends LitElement implements HaFormElement {
|
||||
|
||||
@property({ attribute: false }) public computeLabel?: (
|
||||
schema: HaFormSchema,
|
||||
data?: HaFormDataContainer
|
||||
data?: HaFormDataContainer,
|
||||
options?: { path?: string[] }
|
||||
) => string;
|
||||
|
||||
@property({ attribute: false }) public computeHelper?: (
|
||||
schema: HaFormSchema
|
||||
schema: HaFormSchema,
|
||||
options?: { path?: string[] }
|
||||
) => string;
|
||||
|
||||
@property({ attribute: false }) public localizeValue?: (
|
||||
key: string
|
||||
) => string;
|
||||
|
||||
private _renderDescription() {
|
||||
const description = this.computeHelper?.(this.schema);
|
||||
return description ? html`<p>${description}</p>` : nothing;
|
||||
}
|
||||
|
||||
private _computeLabel = (
|
||||
schema: HaFormSchema,
|
||||
data?: HaFormDataContainer,
|
||||
options?: { path?: string[] }
|
||||
) => {
|
||||
if (!this.computeLabel) return this.computeLabel;
|
||||
|
||||
return this.computeLabel(schema, data, {
|
||||
...options,
|
||||
path: [...(options?.path || []), this.schema.name],
|
||||
});
|
||||
};
|
||||
|
||||
private _computeHelper = (
|
||||
schema: HaFormSchema,
|
||||
options?: { path?: string[] }
|
||||
) => {
|
||||
if (!this.computeHelper) return this.computeHelper;
|
||||
|
||||
return this.computeHelper(schema, {
|
||||
...options,
|
||||
path: [...(options?.path || []), this.schema.name],
|
||||
});
|
||||
};
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-expansion-panel outlined .expanded=${Boolean(this.schema.expanded)}>
|
||||
@@ -43,16 +79,18 @@ export class HaFormExpendable extends LitElement implements HaFormElement {
|
||||
<ha-svg-icon .path=${this.schema.iconPath}></ha-svg-icon>
|
||||
`
|
||||
: nothing}
|
||||
${this.schema.title}
|
||||
${this.schema.title || this.computeLabel?.(this.schema)}
|
||||
</div>
|
||||
<div class="content">
|
||||
${this._renderDescription()}
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this.data}
|
||||
.schema=${this.schema.schema}
|
||||
.disabled=${this.disabled}
|
||||
.computeLabel=${this.computeLabel}
|
||||
.computeHelper=${this.computeHelper}
|
||||
.computeLabel=${this._computeLabel}
|
||||
.computeHelper=${this._computeHelper}
|
||||
.localizeValue=${this.localizeValue}
|
||||
></ha-form>
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
@@ -71,6 +109,9 @@ export class HaFormExpendable extends LitElement implements HaFormElement {
|
||||
.content {
|
||||
padding: 12px;
|
||||
}
|
||||
.content p {
|
||||
margin: 0 0 24px;
|
||||
}
|
||||
ha-expansion-panel {
|
||||
display: block;
|
||||
--expansion-panel-content-padding: 0;
|
||||
|
||||
@@ -35,6 +35,10 @@ export class HaFormGrid extends LitElement implements HaFormElement {
|
||||
schema: HaFormSchema
|
||||
) => string;
|
||||
|
||||
@property({ attribute: false }) public localizeValue?: (
|
||||
key: string
|
||||
) => string;
|
||||
|
||||
public async focus() {
|
||||
await this.updateComplete;
|
||||
this.renderRoot.querySelector("ha-form")?.focus();
|
||||
@@ -65,6 +69,7 @@ export class HaFormGrid extends LitElement implements HaFormElement {
|
||||
.disabled=${this.disabled}
|
||||
.computeLabel=${this.computeLabel}
|
||||
.computeHelper=${this.computeHelper}
|
||||
.localizeValue=${this.localizeValue}
|
||||
></ha-form>
|
||||
`
|
||||
)}
|
||||
|
||||
@@ -31,7 +31,7 @@ const LOAD_ELEMENTS = {
|
||||
};
|
||||
|
||||
const getValue = (obj, item) =>
|
||||
obj ? (!item.name ? obj : obj[item.name]) : null;
|
||||
obj ? (!item.name || item.flatten ? obj : obj[item.name]) : null;
|
||||
|
||||
const getError = (obj, item) => (obj && item.name ? obj[item.name] : null);
|
||||
|
||||
@@ -163,6 +163,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
localize: this.hass?.localize,
|
||||
computeLabel: this.computeLabel,
|
||||
computeHelper: this.computeHelper,
|
||||
localizeValue: this.localizeValue,
|
||||
context: this._generateContext(item),
|
||||
...this.getFormProperties(),
|
||||
})}
|
||||
@@ -204,9 +205,10 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
|
||||
if (ev.target === this) return;
|
||||
|
||||
const newValue = !schema.name
|
||||
? ev.detail.value
|
||||
: { [schema.name]: ev.detail.value };
|
||||
const newValue =
|
||||
!schema.name || ("flatten" in schema && schema.flatten)
|
||||
? ev.detail.value
|
||||
: { [schema.name]: ev.detail.value };
|
||||
|
||||
this.data = {
|
||||
...this.data,
|
||||
|
||||
@@ -31,15 +31,15 @@ export interface HaFormBaseSchema {
|
||||
|
||||
export interface HaFormGridSchema extends HaFormBaseSchema {
|
||||
type: "grid";
|
||||
name: string;
|
||||
flatten?: boolean;
|
||||
column_min_width?: string;
|
||||
schema: readonly HaFormSchema[];
|
||||
}
|
||||
|
||||
export interface HaFormExpandableSchema extends HaFormBaseSchema {
|
||||
type: "expandable";
|
||||
name: "";
|
||||
title: string;
|
||||
flatten?: boolean;
|
||||
title?: string;
|
||||
icon?: string;
|
||||
iconPath?: string;
|
||||
expanded?: boolean;
|
||||
@@ -100,7 +100,7 @@ export type SchemaUnion<
|
||||
SchemaArray extends readonly HaFormSchema[],
|
||||
Schema = SchemaArray[number],
|
||||
> = Schema extends HaFormGridSchema | HaFormExpandableSchema
|
||||
? SchemaUnion<Schema["schema"]>
|
||||
? SchemaUnion<Schema["schema"]> | Schema
|
||||
: Schema;
|
||||
|
||||
export interface HaFormDataContainer {
|
||||
|
||||
@@ -18,9 +18,9 @@ export class HaFormfield extends FormfieldBase {
|
||||
|
||||
return html` <div class="mdc-form-field ${classMap(classes)}">
|
||||
<slot></slot>
|
||||
<label class="mdc-label" @click=${this._labelClick}
|
||||
><slot name="label">${this.label}</slot></label
|
||||
>
|
||||
<label class="mdc-label" @click=${this._labelClick}>
|
||||
<slot name="label">${this.label}</slot>
|
||||
</label>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -57,13 +57,13 @@ export class HaFormfield extends FormfieldBase {
|
||||
}
|
||||
.mdc-form-field {
|
||||
align-items: var(--ha-formfield-align-items, center);
|
||||
gap: 4px;
|
||||
}
|
||||
.mdc-form-field > label {
|
||||
direction: var(--direction);
|
||||
margin-inline-start: 0;
|
||||
margin-inline-end: auto;
|
||||
padding-inline-start: 4px;
|
||||
padding-inline-end: 0;
|
||||
padding: 0;
|
||||
}
|
||||
:host([disabled]) label {
|
||||
color: var(--disabled-text-color);
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "./ha-icon-button";
|
||||
import "../panels/lovelace/editor/card-editor/ha-grid-layout-slider";
|
||||
import "./ha-icon-button";
|
||||
|
||||
import { mdiRestore } from "@mdi/js";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { conditionalClamp } from "../common/number/clamp";
|
||||
|
||||
type GridSizeValue = {
|
||||
rows?: number | "auto";
|
||||
columns?: number;
|
||||
};
|
||||
import {
|
||||
CardGridSize,
|
||||
DEFAULT_GRID_SIZE,
|
||||
} from "../panels/lovelace/common/compute-card-grid-size";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
@customElement("ha-grid-size-picker")
|
||||
export class HaGridSizeEditor extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public value?: GridSizeValue;
|
||||
@property({ attribute: false }) public value?: CardGridSize;
|
||||
|
||||
@property({ attribute: false }) public rows = 8;
|
||||
|
||||
@@ -34,7 +34,7 @@ export class HaGridSizeEditor extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public isDefault?: boolean;
|
||||
|
||||
@state() public _localValue?: GridSizeValue = undefined;
|
||||
@state() public _localValue?: CardGridSize = { rows: 1, columns: 1 };
|
||||
|
||||
protected willUpdate(changedProperties) {
|
||||
if (changedProperties.has("value")) {
|
||||
@@ -49,6 +49,7 @@ export class HaGridSizeEditor extends LitElement {
|
||||
this.rowMin !== undefined && this.rowMin === this.rowMax;
|
||||
|
||||
const autoHeight = this._localValue?.rows === "auto";
|
||||
const fullWidth = this._localValue?.columns === "full";
|
||||
|
||||
const rowMin = this.rowMin ?? 1;
|
||||
const rowMax = this.rowMax ?? this.rows;
|
||||
@@ -67,7 +68,7 @@ export class HaGridSizeEditor extends LitElement {
|
||||
.min=${columnMin}
|
||||
.max=${columnMax}
|
||||
.range=${this.columns}
|
||||
.value=${columnValue}
|
||||
.value=${fullWidth ? this.columns : columnValue}
|
||||
@value-changed=${this._valueChanged}
|
||||
@slider-moved=${this._sliderMoved}
|
||||
.disabled=${disabledColumns}
|
||||
@@ -104,12 +105,12 @@ export class HaGridSizeEditor extends LitElement {
|
||||
`
|
||||
: nothing}
|
||||
<div
|
||||
class="preview"
|
||||
class="preview ${classMap({ "full-width": fullWidth })}"
|
||||
style=${styleMap({
|
||||
"--total-rows": this.rows,
|
||||
"--total-columns": this.columns,
|
||||
"--rows": rowValue,
|
||||
"--columns": columnValue,
|
||||
"--columns": fullWidth ? this.columns : columnValue,
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
@@ -140,12 +141,21 @@ export class HaGridSizeEditor extends LitElement {
|
||||
const cell = ev.currentTarget as HTMLElement;
|
||||
const rows = Number(cell.getAttribute("data-row"));
|
||||
const columns = Number(cell.getAttribute("data-column"));
|
||||
const clampedRow = conditionalClamp(rows, this.rowMin, this.rowMax);
|
||||
const clampedColumn = conditionalClamp(
|
||||
const clampedRow: CardGridSize["rows"] = conditionalClamp(
|
||||
rows,
|
||||
this.rowMin,
|
||||
this.rowMax
|
||||
);
|
||||
let clampedColumn: CardGridSize["columns"] = conditionalClamp(
|
||||
columns,
|
||||
this.columnMin,
|
||||
this.columnMax
|
||||
);
|
||||
|
||||
const currentSize = this.value ?? DEFAULT_GRID_SIZE;
|
||||
if (currentSize.columns === "full" && clampedColumn === this.columns) {
|
||||
clampedColumn = "full";
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { rows: clampedRow, columns: clampedColumn },
|
||||
});
|
||||
@@ -153,12 +163,23 @@ export class HaGridSizeEditor extends LitElement {
|
||||
|
||||
private _valueChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
const key = ev.currentTarget.id;
|
||||
const newValue = {
|
||||
...this.value,
|
||||
[key]: ev.detail.value,
|
||||
const key = ev.currentTarget.id as "rows" | "columns";
|
||||
const currentSize = this.value ?? DEFAULT_GRID_SIZE;
|
||||
let value = ev.detail.value as CardGridSize[typeof key];
|
||||
|
||||
if (
|
||||
key === "columns" &&
|
||||
currentSize.columns === "full" &&
|
||||
value === this.columns
|
||||
) {
|
||||
value = "full";
|
||||
}
|
||||
|
||||
const newSize = {
|
||||
...currentSize,
|
||||
[key]: value,
|
||||
};
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
fireEvent(this, "value-changed", { value: newSize });
|
||||
}
|
||||
|
||||
private _reset(ev) {
|
||||
@@ -173,11 +194,14 @@ export class HaGridSizeEditor extends LitElement {
|
||||
|
||||
private _sliderMoved(ev) {
|
||||
ev.stopPropagation();
|
||||
const key = ev.currentTarget.id;
|
||||
const value = ev.detail.value;
|
||||
const key = ev.currentTarget.id as "rows" | "columns";
|
||||
const currentSize = this.value ?? DEFAULT_GRID_SIZE;
|
||||
const value = ev.detail.value as CardGridSize[typeof key] | undefined;
|
||||
|
||||
if (value === undefined) return;
|
||||
|
||||
this._localValue = {
|
||||
...this.value,
|
||||
...currentSize,
|
||||
[key]: ev.detail.value,
|
||||
};
|
||||
}
|
||||
@@ -189,7 +213,7 @@ export class HaGridSizeEditor extends LitElement {
|
||||
grid-template-areas:
|
||||
"reset column-slider"
|
||||
"row-slider preview";
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
@@ -205,17 +229,12 @@ export class HaGridSizeEditor extends LitElement {
|
||||
.preview {
|
||||
position: relative;
|
||||
grid-area: preview;
|
||||
aspect-ratio: 1 / 1.2;
|
||||
}
|
||||
.preview > div {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--total-columns), 1fr);
|
||||
grid-template-rows: repeat(var(--total-rows), 1fr);
|
||||
grid-template-rows: repeat(var(--total-rows), 25px);
|
||||
gap: 4px;
|
||||
}
|
||||
.preview .cell {
|
||||
@@ -226,15 +245,23 @@ export class HaGridSizeEditor extends LitElement {
|
||||
opacity: 0.2;
|
||||
cursor: pointer;
|
||||
}
|
||||
.selected {
|
||||
.preview .selected {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.selected .cell {
|
||||
background-color: var(--primary-color);
|
||||
grid-column: 1 / span var(--columns, 0);
|
||||
grid-row: 1 / span var(--rows, 0);
|
||||
grid-column: 1 / span min(var(--columns, 0), var(--total-columns));
|
||||
grid-row: 1 / span min(var(--rows, 0), var(--total-rows));
|
||||
opacity: 0.5;
|
||||
}
|
||||
.preview.full-width .selected .cell {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
|
||||
type HeadingBadgeType = "text" | "button";
|
||||
|
||||
@customElement("ha-heading-badge")
|
||||
export class HaBadge extends LitElement {
|
||||
@property() public type: HeadingBadgeType = "text";
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div
|
||||
class="heading-badge"
|
||||
role=${ifDefined(this.type === "button" ? "button" : undefined)}
|
||||
tabindex=${ifDefined(this.type === "button" ? "0" : undefined)}
|
||||
>
|
||||
<slot name="icon"></slot>
|
||||
<slot></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
.heading-badge {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
white-space: nowrap;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
font-family: Roboto;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0.1px;
|
||||
--mdc-icon-size: 14px;
|
||||
}
|
||||
::slotted([slot="icon"]) {
|
||||
--ha-icon-display: block;
|
||||
color: var(--icon-color, inherit);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-heading-badge": HaBadge;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,11 @@ const LAWN_MOWER_ACTIONS: Partial<
|
||||
service: "start_mowing",
|
||||
feature: LawnMowerEntityFeature.START_MOWING,
|
||||
},
|
||||
returning: {
|
||||
action: "pause",
|
||||
service: "pause",
|
||||
feature: LawnMowerEntityFeature.PAUSE,
|
||||
},
|
||||
paused: {
|
||||
action: "resume_mowing",
|
||||
service: "start_mowing",
|
||||
|
||||
@@ -96,7 +96,25 @@ class HaMarkdownElement extends ReactiveElement {
|
||||
|
||||
haAlertNode.append(
|
||||
...Array.from(node.childNodes)
|
||||
.map((child) => Array.from(child.childNodes))
|
||||
.map((child) => {
|
||||
const arr = Array.from(child.childNodes);
|
||||
if (!this.breaks && arr.length) {
|
||||
// When we are not breaking, the first line of the blockquote is not considered,
|
||||
// so we need to adjust the first child text content
|
||||
const firstChild = arr[0];
|
||||
if (
|
||||
firstChild.nodeType === Node.TEXT_NODE &&
|
||||
firstChild.textContent === gitHubAlertMatch.input &&
|
||||
firstChild.textContent?.includes("\n")
|
||||
) {
|
||||
firstChild.textContent = firstChild.textContent
|
||||
.split("\n")
|
||||
.slice(1)
|
||||
.join("\n");
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
})
|
||||
.reduce((acc, val) => acc.concat(val), [])
|
||||
.filter(
|
||||
(childNode) =>
|
||||
|
||||
@@ -6,8 +6,8 @@ import type { HaIconButton } from "./ha-icon-button";
|
||||
import "./ha-menu";
|
||||
import type { HaMenu } from "./ha-menu";
|
||||
|
||||
@customElement("ha-button-menu-new")
|
||||
export class HaButtonMenuNew extends LitElement {
|
||||
@customElement("ha-md-button-menu")
|
||||
export class HaMdButtonMenu extends LitElement {
|
||||
protected readonly [FOCUS_TARGET];
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
@@ -84,6 +84,6 @@ export class HaButtonMenuNew extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-button-menu-new": HaButtonMenuNew;
|
||||
"ha-md-button-menu": HaMdButtonMenu;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
import { MdDialog } from "@material/web/dialog/dialog";
|
||||
import {
|
||||
type DialogAnimation,
|
||||
DIALOG_DEFAULT_CLOSE_ANIMATION,
|
||||
DIALOG_DEFAULT_OPEN_ANIMATION,
|
||||
} from "@material/web/dialog/internal/animations";
|
||||
import { css } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
|
||||
// workaround to be able to overlay an dialog with another dialog
|
||||
MdDialog.addInitializer(async (instance) => {
|
||||
await instance.updateComplete;
|
||||
|
||||
const dialogInstance = instance as MdDialog;
|
||||
|
||||
// @ts-expect-error dialog is private
|
||||
dialogInstance.dialog.prepend(dialogInstance.scrim);
|
||||
// @ts-expect-error scrim is private
|
||||
dialogInstance.scrim.style.inset = 0;
|
||||
// @ts-expect-error scrim is private
|
||||
dialogInstance.scrim.style.zIndex = 0;
|
||||
|
||||
const { getOpenAnimation, getCloseAnimation } = dialogInstance;
|
||||
dialogInstance.getOpenAnimation = () => {
|
||||
const animations = getOpenAnimation.call(this);
|
||||
animations.container = [
|
||||
...(animations.container ?? []),
|
||||
...(animations.dialog ?? []),
|
||||
];
|
||||
animations.dialog = [];
|
||||
return animations;
|
||||
};
|
||||
dialogInstance.getCloseAnimation = () => {
|
||||
const animations = getCloseAnimation.call(this);
|
||||
animations.container = [
|
||||
...(animations.container ?? []),
|
||||
...(animations.dialog ?? []),
|
||||
];
|
||||
animations.dialog = [];
|
||||
return animations;
|
||||
};
|
||||
});
|
||||
|
||||
let DIALOG_POLYFILL: Promise<typeof import("dialog-polyfill")>;
|
||||
|
||||
/**
|
||||
* Based on the home assistant design: https://design.home-assistant.io/#components/ha-dialogs
|
||||
*
|
||||
*/
|
||||
@customElement("ha-md-dialog")
|
||||
export class HaMdDialog extends MdDialog {
|
||||
/**
|
||||
* When true the dialog will not close when the user presses the esc key or press out of the dialog.
|
||||
*/
|
||||
@property({ attribute: "disable-cancel-action", type: Boolean })
|
||||
public disableCancelAction = false;
|
||||
|
||||
private _polyfillDialogRegistered = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.addEventListener("cancel", this._handleCancel);
|
||||
|
||||
if (typeof HTMLDialogElement !== "function") {
|
||||
this.addEventListener("open", this._handleOpen);
|
||||
|
||||
if (!DIALOG_POLYFILL) {
|
||||
DIALOG_POLYFILL = import("dialog-polyfill");
|
||||
}
|
||||
}
|
||||
|
||||
// if browser doesn't support animate API disable open/close animations
|
||||
if (this.animate === undefined) {
|
||||
this.quick = true;
|
||||
}
|
||||
|
||||
// if browser doesn't support animate API disable open/close animations
|
||||
if (this.animate === undefined) {
|
||||
this.quick = true;
|
||||
}
|
||||
}
|
||||
|
||||
// prevent open in older browsers and wait for polyfill to load
|
||||
private async _handleOpen(openEvent: Event) {
|
||||
openEvent.preventDefault();
|
||||
|
||||
if (this._polyfillDialogRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._polyfillDialogRegistered = true;
|
||||
this._loadPolyfillStylesheet("/static/polyfills/dialog-polyfill.css");
|
||||
const dialog = this.shadowRoot?.querySelector(
|
||||
"dialog"
|
||||
) as HTMLDialogElement;
|
||||
|
||||
const dialogPolyfill = await DIALOG_POLYFILL;
|
||||
dialogPolyfill.default.registerDialog(dialog);
|
||||
this.removeEventListener("open", this._handleOpen);
|
||||
|
||||
this.show();
|
||||
}
|
||||
|
||||
private async _loadPolyfillStylesheet(href) {
|
||||
const link = document.createElement("link");
|
||||
link.rel = "stylesheet";
|
||||
link.href = href;
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
link.onload = () => resolve();
|
||||
link.onerror = () =>
|
||||
reject(new Error(`Stylesheet failed to load: ${href}`));
|
||||
|
||||
this.shadowRoot?.appendChild(link);
|
||||
});
|
||||
}
|
||||
|
||||
_handleCancel(closeEvent: Event) {
|
||||
if (this.disableCancelAction) {
|
||||
closeEvent.preventDefault();
|
||||
const dialogElement = this.shadowRoot?.querySelector("dialog .container");
|
||||
if (this.animate !== undefined) {
|
||||
dialogElement?.animate(
|
||||
[
|
||||
{
|
||||
transform: "rotate(-1deg)",
|
||||
"animation-timing-function": "ease-in",
|
||||
},
|
||||
{
|
||||
transform: "rotate(1.5deg)",
|
||||
"animation-timing-function": "ease-out",
|
||||
},
|
||||
{
|
||||
transform: "rotate(0deg)",
|
||||
"animation-timing-function": "ease-in",
|
||||
},
|
||||
],
|
||||
{
|
||||
duration: 200,
|
||||
iterations: 2,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
...super.styles,
|
||||
css`
|
||||
:host {
|
||||
--md-dialog-container-color: var(--card-background-color);
|
||||
--md-dialog-headline-color: var(--primary-text-color);
|
||||
--md-dialog-supporting-text-color: var(--primary-text-color);
|
||||
--md-sys-color-scrim: #000000;
|
||||
|
||||
--md-dialog-headline-weight: 400;
|
||||
--md-dialog-headline-size: 1.574rem;
|
||||
--md-dialog-supporting-text-size: 1rem;
|
||||
--md-dialog-supporting-text-line-height: 1.5rem;
|
||||
}
|
||||
|
||||
:host([type="alert"]) {
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
:host(:not([type="alert"])) {
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
min-width: calc(
|
||||
100vw - env(safe-area-inset-right) - env(safe-area-inset-left)
|
||||
);
|
||||
max-width: calc(
|
||||
100vw - env(safe-area-inset-right) - env(safe-area-inset-left)
|
||||
);
|
||||
min-height: 100%;
|
||||
max-height: 100%;
|
||||
--md-dialog-container-shape: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:host ::slotted(ha-dialog-header) {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
slot[name="content"]::slotted(*) {
|
||||
padding: var(--dialog-content-padding, 24px);
|
||||
}
|
||||
.scrim {
|
||||
z-index: 10; // overlay navigation
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
// by default the dialog open/close animation will be from/to the top
|
||||
// but if we have a special mobile dialog which is at the bottom of the screen, an from bottom animation can be used:
|
||||
const OPEN_FROM_BOTTOM_ANIMATION: DialogAnimation = {
|
||||
...DIALOG_DEFAULT_OPEN_ANIMATION,
|
||||
dialog: [
|
||||
[
|
||||
// Dialog slide up
|
||||
[{ transform: "translateY(50px)" }, { transform: "translateY(0)" }],
|
||||
{ duration: 500, easing: "cubic-bezier(.3,0,0,1)" },
|
||||
],
|
||||
],
|
||||
container: [
|
||||
[
|
||||
// Container fade in
|
||||
[{ opacity: 0 }, { opacity: 1 }],
|
||||
{ duration: 50, easing: "linear", pseudoElement: "::before" },
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
const CLOSE_TO_BOTTOM_ANIMATION: DialogAnimation = {
|
||||
...DIALOG_DEFAULT_CLOSE_ANIMATION,
|
||||
dialog: [
|
||||
[
|
||||
// Dialog slide down
|
||||
[{ transform: "translateY(0)" }, { transform: "translateY(50px)" }],
|
||||
{ duration: 150, easing: "cubic-bezier(.3,0,0,1)" },
|
||||
],
|
||||
],
|
||||
container: [
|
||||
[
|
||||
// Container fade out
|
||||
[{ opacity: "1" }, { opacity: "0" }],
|
||||
{ delay: 100, duration: 50, easing: "linear", pseudoElement: "::before" },
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
export const getMobileOpenFromBottomAnimation = () => {
|
||||
const matches = window.matchMedia(
|
||||
"all and (max-width: 450px), all and (max-height: 500px)"
|
||||
).matches;
|
||||
return matches ? OPEN_FROM_BOTTOM_ANIMATION : DIALOG_DEFAULT_OPEN_ANIMATION;
|
||||
};
|
||||
|
||||
export const getMobileCloseToBottomAnimation = () => {
|
||||
const matches = window.matchMedia(
|
||||
"all and (max-width: 450px), all and (max-height: 500px)"
|
||||
).matches;
|
||||
return matches ? CLOSE_TO_BOTTOM_ANIMATION : DIALOG_DEFAULT_CLOSE_ANIMATION;
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-md-dialog": HaMdDialog;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { MdDivider } from "@material/web/divider/divider";
|
||||
import { css } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
|
||||
@customElement("ha-md-divider")
|
||||
export class HaMdDivider extends MdDivider {
|
||||
static override styles = [
|
||||
...super.styles,
|
||||
css`
|
||||
:host {
|
||||
--md-divider-color: var(--divider-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-md-divider": HaMdDivider;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@ import { MdListItem } from "@material/web/list/list-item";
|
||||
import { css } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
|
||||
@customElement("ha-list-item-new")
|
||||
export class HaListItemNew extends MdListItem {
|
||||
@customElement("ha-md-list-item")
|
||||
export class HaMdListItem extends MdListItem {
|
||||
static override styles = [
|
||||
...super.styles,
|
||||
css`
|
||||
@@ -21,6 +21,6 @@ export class HaListItemNew extends MdListItem {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-list-item-new": HaListItemNew;
|
||||
"ha-md-list-item": HaMdListItem;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@ import { MdList } from "@material/web/list/list";
|
||||
import { css } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
|
||||
@customElement("ha-list-new")
|
||||
export class HaListNew extends MdList {
|
||||
@customElement("ha-md-list")
|
||||
export class HaMdList extends MdList {
|
||||
static override styles = [
|
||||
...super.styles,
|
||||
css`
|
||||
@@ -16,6 +16,6 @@ export class HaListNew extends MdList {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-list-new": HaListNew;
|
||||
"ha-md-list": HaMdList;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@ import { MdMenuItem } from "@material/web/menu/menu-item";
|
||||
import { css } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
|
||||
@customElement("ha-menu-item")
|
||||
export class HaMenuItem extends MdMenuItem {
|
||||
@customElement("ha-md-menu-item")
|
||||
export class HaMdMenuItem extends MdMenuItem {
|
||||
@property({ attribute: false }) clickAction?: (item?: HTMLElement) => void;
|
||||
|
||||
static override styles = [
|
||||
@@ -41,6 +41,6 @@ export class HaMenuItem extends MdMenuItem {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-menu-item": HaMenuItem;
|
||||
"ha-md-menu-item": HaMdMenuItem;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from "@material/web/menu/internal/controllers/shared";
|
||||
import { css } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import type { HaMenuItem } from "./ha-menu-item";
|
||||
import type { HaMdMenuItem } from "./ha-md-menu-item";
|
||||
|
||||
@customElement("ha-menu")
|
||||
export class HaMenu extends MdMenu {
|
||||
@@ -22,7 +22,7 @@ export class HaMenu extends MdMenu {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
(ev.detail.initiator as HaMenuItem).clickAction?.(ev.detail.initiator);
|
||||
(ev.detail.initiator as HaMdMenuItem).clickAction?.(ev.detail.initiator);
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
|
||||
@@ -24,9 +24,11 @@ export class HaOutlinedField extends MdOutlinedField {
|
||||
}
|
||||
.with-start .start {
|
||||
margin-inline-end: var(--ha-outlined-field-start-margin, 4px);
|
||||
margin-inline-start: initial;
|
||||
}
|
||||
.with-end .end {
|
||||
margin-inline-start: var(--ha-outlined-field-end-margin, 4px);
|
||||
margin-inline-end: initial;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
import { TextAreaCharCounter } from "@material/mwc-textfield/mwc-textfield-base";
|
||||
import { mdiEye, mdiEyeOff } from "@mdi/js";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import {
|
||||
customElement,
|
||||
eventOptions,
|
||||
property,
|
||||
query,
|
||||
state,
|
||||
} from "lit/decorators";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-textfield";
|
||||
import type { HaTextField } from "./ha-textfield";
|
||||
|
||||
@customElement("ha-password-field")
|
||||
export class HaPasswordField extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public invalid?: boolean;
|
||||
|
||||
@property({ attribute: "error-message" }) public errorMessage?: string;
|
||||
|
||||
@property({ type: Boolean }) public icon = false;
|
||||
|
||||
@property({ type: Boolean }) public iconTrailing = false;
|
||||
|
||||
@property() public autocomplete?: string;
|
||||
|
||||
@property() public autocorrect?: string;
|
||||
|
||||
@property({ attribute: "input-spellcheck" })
|
||||
public inputSpellcheck?: string;
|
||||
|
||||
@property({ type: String }) value = "";
|
||||
|
||||
@property({ type: String }) placeholder = "";
|
||||
|
||||
@property({ type: String }) label = "";
|
||||
|
||||
@property({ type: Boolean, reflect: true }) disabled = false;
|
||||
|
||||
@property({ type: Boolean }) required = false;
|
||||
|
||||
@property({ type: Number }) minLength = -1;
|
||||
|
||||
@property({ type: Number }) maxLength = -1;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) outlined = false;
|
||||
|
||||
@property({ type: String }) helper = "";
|
||||
|
||||
@property({ type: Boolean }) validateOnInitialRender = false;
|
||||
|
||||
@property({ type: String }) validationMessage = "";
|
||||
|
||||
@property({ type: Boolean }) autoValidate = false;
|
||||
|
||||
@property({ type: String }) pattern = "";
|
||||
|
||||
@property({ type: Number }) size: number | null = null;
|
||||
|
||||
@property({ type: Boolean }) helperPersistent = false;
|
||||
|
||||
@property({ type: Boolean }) charCounter: boolean | TextAreaCharCounter =
|
||||
false;
|
||||
|
||||
@property({ type: Boolean }) endAligned = false;
|
||||
|
||||
@property({ type: String }) prefix = "";
|
||||
|
||||
@property({ type: String }) suffix = "";
|
||||
|
||||
@property({ type: String }) name = "";
|
||||
|
||||
@property({ type: String, attribute: "input-mode" })
|
||||
inputMode!: string;
|
||||
|
||||
@property({ type: Boolean }) readOnly = false;
|
||||
|
||||
@property({ type: String }) autocapitalize = "";
|
||||
|
||||
@state() private _unmaskedPassword = false;
|
||||
|
||||
@query("ha-textfield") private _textField!: HaTextField;
|
||||
|
||||
protected render() {
|
||||
return html`<ha-textfield
|
||||
.invalid=${this.invalid}
|
||||
.errorMessage=${this.errorMessage}
|
||||
.icon=${this.icon}
|
||||
.iconTrailing=${this.iconTrailing}
|
||||
.autocomplete=${this.autocomplete}
|
||||
.autocorrect=${this.autocorrect}
|
||||
.inputSpellcheck=${this.inputSpellcheck}
|
||||
.value=${this.value}
|
||||
.placeholder=${this.placeholder}
|
||||
.label=${this.label}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.minLength=${this.minLength}
|
||||
.maxLength=${this.maxLength}
|
||||
.outlined=${this.outlined}
|
||||
.helper=${this.helper}
|
||||
.validateOnInitialRender=${this.validateOnInitialRender}
|
||||
.validationMessage=${this.validationMessage}
|
||||
.autoValidate=${this.autoValidate}
|
||||
.pattern=${this.pattern}
|
||||
.size=${this.size}
|
||||
.helperPersistent=${this.helperPersistent}
|
||||
.charCounter=${this.charCounter}
|
||||
.endAligned=${this.endAligned}
|
||||
.prefix=${this.prefix}
|
||||
.name=${this.name}
|
||||
.inputMode=${this.inputMode}
|
||||
.readOnly=${this.readOnly}
|
||||
.autocapitalize=${this.autocapitalize}
|
||||
.type=${this._unmaskedPassword ? "text" : "password"}
|
||||
.suffix=${html`<div style="width: 24px"></div>`}
|
||||
@input=${this._handleInputChange}
|
||||
></ha-textfield>
|
||||
<ha-icon-button
|
||||
toggles
|
||||
.label=${this.hass?.localize(
|
||||
this._unmaskedPassword
|
||||
? "ui.components.selectors.text.hide_password"
|
||||
: "ui.components.selectors.text.show_password"
|
||||
) || (this._unmaskedPassword ? "Hide password" : "Show password")}
|
||||
@click=${this._toggleUnmaskedPassword}
|
||||
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
|
||||
></ha-icon-button>`;
|
||||
}
|
||||
|
||||
public checkValidity(): boolean {
|
||||
return this._textField.checkValidity();
|
||||
}
|
||||
|
||||
public reportValidity(): boolean {
|
||||
return this._textField.reportValidity();
|
||||
}
|
||||
|
||||
public setCustomValidity(message: string): void {
|
||||
return this._textField.setCustomValidity(message);
|
||||
}
|
||||
|
||||
public layout(): Promise<void> {
|
||||
return this._textField.layout();
|
||||
}
|
||||
|
||||
private _toggleUnmaskedPassword(): void {
|
||||
this._unmaskedPassword = !this._unmaskedPassword;
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private _handleInputChange(ev) {
|
||||
this.value = ev.target.value;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
ha-textfield {
|
||||
width: 100%;
|
||||
}
|
||||
ha-icon-button {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
inset-inline-start: initial;
|
||||
inset-inline-end: 8px;
|
||||
--mdc-icon-button-size: 40px;
|
||||
--mdc-icon-size: 20px;
|
||||
color: var(--secondary-text-color);
|
||||
direction: var(--direction);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-password-field": HaPasswordField;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { Action } from "../../data/script";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { Action, migrateAutomationAction } from "../../data/script";
|
||||
import { ActionSelector } from "../../data/selector";
|
||||
import "../../panels/config/automation/action/ha-automation-action";
|
||||
import { HomeAssistant } from "../../types";
|
||||
@@ -17,12 +18,19 @@ export class HaActionSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
private _actions = memoizeOne((action: Action | undefined) => {
|
||||
if (!action) {
|
||||
return [];
|
||||
}
|
||||
return migrateAutomationAction(action);
|
||||
});
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
${this.label ? html`<label>${this.label}</label>` : nothing}
|
||||
<ha-automation-action
|
||||
.disabled=${this.disabled}
|
||||
.actions=${this.value || []}
|
||||
.actions=${this._actions(this.value)}
|
||||
.hass=${this.hass}
|
||||
.path=${this.selector.action?.path}
|
||||
></ha-automation-action>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../../types";
|
||||
@@ -28,10 +28,13 @@ export class HaBooleanSelector extends LitElement {
|
||||
@change=${this._handleChange}
|
||||
.disabled=${this.disabled}
|
||||
></ha-switch>
|
||||
<span slot="label">
|
||||
<p class="primary">${this.label}</p>
|
||||
${this.helper
|
||||
? html`<p class="secondary">${this.helper}</p>`
|
||||
: nothing}
|
||||
</span>
|
||||
</ha-formfield>
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -47,10 +50,21 @@ export class HaBooleanSelector extends LitElement {
|
||||
return css`
|
||||
ha-formfield {
|
||||
display: flex;
|
||||
height: 56px;
|
||||
min-height: 56px;
|
||||
align-items: center;
|
||||
--mdc-typography-body2-font-size: 1em;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
.secondary {
|
||||
direction: var(--direction);
|
||||
padding-top: 4px;
|
||||
box-sizing: border-box;
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 0.875rem;
|
||||
font-weight: var(--mdc-typography-body2-font-weight, 400);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||