├── .gitattributes ├── .gitignore ├── card.png ├── hacs.json ├── .prettierrc.json ├── webpack ├── config.dev.js ├── config.prod.js └── config.common.js ├── src ├── globalElementLoader.js ├── buildElementDefinitions.js ├── color-wheel │ ├── convert-color.js │ ├── events-mixin.js │ ├── fire_event.js │ └── color-picker.js ├── defaults.js ├── style-editor.js ├── style.js ├── index-editor.js └── index.js ├── babel.config.js ├── .github └── workflows │ ├── validate.yaml │ └── deploy.yaml ├── .eslintrc.yaml ├── .babelrc ├── rollup-plugins └── ignore.js ├── LICENSE ├── package.json ├── README.md └── dist └── light-entity-card.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js text eol=lf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ljmerza/light-entity-card/HEAD/card.png -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Light Entity Card", 3 | "render_readme": true, 4 | "filename": "dist/light-entity-card.js" 5 | } -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "singleQuote": true, 4 | "printWidth": 120, 5 | "arrowParens": "avoid" 6 | } 7 | -------------------------------------------------------------------------------- /webpack/config.dev.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const commonConfig = require('./config.common'); 3 | 4 | 5 | module.exports = merge(commonConfig, { 6 | mode: 'development' 7 | }); -------------------------------------------------------------------------------- /src/globalElementLoader.js: -------------------------------------------------------------------------------- 1 | const globalElementLoader = name => ({ 2 | name, 3 | promise: customElements.whenDefined(name).then(() => customElements.get(name)), 4 | }); 5 | 6 | export default globalElementLoader; 7 | -------------------------------------------------------------------------------- /webpack/config.prod.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const commonConfig = require('./config.common'); 3 | 4 | 5 | module.exports = merge(commonConfig, { 6 | mode: 'production', 7 | optimization: { 8 | minimize: true 9 | }, 10 | output: { 11 | publicPath: '/local/' 12 | }, 13 | }); -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "useBuiltIns": "usage", 7 | "debug": true, 8 | "targets": "> 0.25%, not dead", 9 | "shippedProposals": true 10 | } 11 | ] 12 | ] 13 | } -------------------------------------------------------------------------------- /.github/workflows/validate.yaml: -------------------------------------------------------------------------------- 1 | name: Validate 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 0 * * *" 8 | 9 | jobs: 10 | validate: 11 | runs-on: "ubuntu-latest" 12 | steps: 13 | - uses: "actions/checkout@v2" 14 | - name: HACS validation 15 | uses: "hacs/action@main" 16 | with: 17 | category: "plugin" 18 | -------------------------------------------------------------------------------- /.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | extends: airbnb-base 2 | rules: 3 | no-else-return: 0 4 | no-underscore-dangle: 0 5 | nonblock-statement-body-position: 0 6 | curly: 0 7 | no-return-assign: 0 8 | consistent-return: 0 9 | no-mixed-operators: 0 10 | class-methods-use-this: 0 11 | no-nested-ternary: 0 12 | camelcase: 0 13 | no-bitwise: 0 14 | max-len: 0 15 | no-useless-constructor: 0 16 | no-restricted-globals: 0 17 | globals: 18 | window: true 19 | Event: true 20 | customElements: true -------------------------------------------------------------------------------- /src/buildElementDefinitions.js: -------------------------------------------------------------------------------- 1 | const buildElementDefinitions = (elements, constructor) => elements.reduce((aggregate, element) => { 2 | if (element.defineId) { 3 | // eslint-disable-next-line no-param-reassign 4 | aggregate[element.defineId] = element; 5 | } else { 6 | element.promise.then((clazz) => { 7 | if (constructor.registry.get(element.name) === undefined) { 8 | constructor.registry.define(element.name, clazz); 9 | } 10 | }); 11 | } 12 | return aggregate; 13 | }, {}); 14 | 15 | export default buildElementDefinitions; 16 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "modules": false, 7 | "targets": { 8 | "esmodules": true 9 | } 10 | } 11 | ], 12 | ["minify"] 13 | ], 14 | "comments": false, 15 | "plugins": [ 16 | [ 17 | "@babel/plugin-proposal-decorators", 18 | { "legacy": true } 19 | ], 20 | [ 21 | "@babel/plugin-proposal-class-properties", 22 | { "loose": true } 23 | ], 24 | ["@babel/plugin-transform-template-literals"], 25 | ["iife-wrap"] 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /rollup-plugins/ignore.js: -------------------------------------------------------------------------------- 1 | export default function (userOptions = {}) { 2 | // Files need to be absolute paths. 3 | // This only works if the file has no exports 4 | // and only is imported for its side effects 5 | const files = userOptions.files || []; 6 | 7 | if (files.length === 0) { 8 | return { 9 | name: 'ignore', 10 | }; 11 | } 12 | 13 | return { 14 | name: 'ignore', 15 | 16 | load(id) { 17 | return files.some(toIgnorePath => id.startsWith(toIgnorePath)) 18 | ? { 19 | code: '', 20 | } 21 | : null; 22 | }, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /webpack/config.common.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: "./src/index.js", 5 | devtool: "source-map", 6 | output: { 7 | filename: "light-entity-card.js", 8 | path: path.resolve(__dirname, "../dist"), 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.js$/, 14 | include: [/node_modules(?:\/|\\)lit-element|lit-html/], 15 | use: { 16 | loader: "babel-loader", 17 | options: { 18 | presets: ["@babel/preset-env"], 19 | }, 20 | }, 21 | }, 22 | ], 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/color-wheel/convert-color.js: -------------------------------------------------------------------------------- 1 | export const rgb2hsv = (rgb) => { 2 | const [r, g, b] = rgb; 3 | const v = Math.max(r, g, b); 4 | const c = v - Math.min(r, g, b); 5 | const h = 6 | c && (v === r ? (g - b) / c : v === g ? 2 + (b - r) / c : 4 + (r - g) / c); 7 | return [60 * (h < 0 ? h + 6 : h), v && c / v, v]; 8 | }; 9 | 10 | export const hsv2rgb = (hsv) => { 11 | const [h, s, v] = hsv; 12 | const f = (n) => { 13 | const k = (n + h / 60) % 6; 14 | return v - v * s * Math.max(Math.min(k, 4 - k, 1), 0); 15 | }; 16 | return [f(5), f(3), f(1)]; 17 | }; 18 | 19 | export const rgb2hs = (rgb) => rgb2hsv(rgb).slice(0, 2); 20 | 21 | export const hs2rgb = (hs) => hsv2rgb([hs[0], hs[1], 255]); 22 | -------------------------------------------------------------------------------- /src/defaults.js: -------------------------------------------------------------------------------- 1 | export default { 2 | shorten_cards: false, 3 | consolidate_entities: false, 4 | child_card: false, 5 | hide_header: false, 6 | show_header_icon: false, 7 | header: '', 8 | 9 | color_wheel: true, 10 | persist_features: false, 11 | brightness: true, 12 | color_temp: true, 13 | white_value: true, 14 | color_picker: true, 15 | speed: true, 16 | intensity: true, 17 | 18 | force_features: false, 19 | 20 | show_slider_percent: false, 21 | full_width_sliders: false, 22 | 23 | brightness_icon: 'weather-sunny', 24 | white_icon: 'file-word-box', 25 | temperature_icon: 'thermometer', 26 | speed_icon: 'speedometer', 27 | intensity_icon: 'transit-connection-horizontal', 28 | }; 29 | -------------------------------------------------------------------------------- /src/style-editor.js: -------------------------------------------------------------------------------- 1 | import { css } from 'lit'; 2 | 3 | const style = css` 4 | .entities { 5 | padding-top: 10px; 6 | padding-bottom: 10px; 7 | display: flex; 8 | } 9 | 10 | .entities ha-formfield { 11 | display: block; 12 | margin-bottom: 10px; 13 | margin-left: 10px; 14 | } 15 | 16 | .checkbox-options { 17 | display: flex; 18 | } 19 | 20 | mwc-select { 21 | width: 100%; 22 | } 23 | 24 | .checkbox-options ha-formfield, 25 | .entities mwc-switch, 26 | .entities ha-form-string { 27 | padding-right: 2%; 28 | width: 48%; 29 | } 30 | 31 | .checkbox-options ha-formfield { 32 | margin-top: 10px; 33 | } 34 | 35 | .overall-config { 36 | margin-bottom: 20px; 37 | } 38 | `; 39 | 40 | export default style; 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Leonardo Merza 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "light-entity-card", 3 | "version": "6.1.3", 4 | "description": "A light-entity card for Home Assistant Lovelace UI", 5 | "keywords": [ 6 | "home-assistant", 7 | "homeassistant", 8 | "hass", 9 | "automation", 10 | "lovelace", 11 | "custom-cards", 12 | "light-entity" 13 | ], 14 | "repository": "git@github.com:ljmerza/light-entity-card.git", 15 | "author": "Leonardo Merza ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "@babel/polyfill": "^7.4.4", 19 | "@jaames/iro": "^5.5.2", 20 | "@lit-labs/scoped-registry-mixin": "^1.0.0", 21 | "@material/mwc-icon": "^0.25.3", 22 | "@material/mwc-list": "^0.25.3", 23 | "@material/mwc-menu": "^0.25.3", 24 | "@material/mwc-notched-outline": "^0.25.3", 25 | "@material/mwc-select": "^0.25.3", 26 | "core-js": "^2.6.5", 27 | "lit": "^2.1.2", 28 | "lit-element": "^2.2.1" 29 | }, 30 | "devDependencies": { 31 | "@babel/cli": "^7.5.5", 32 | "@babel/core": "^7.5.5", 33 | "@babel/preset-env": "^7.5.5", 34 | "babel-loader": "^8.0.6", 35 | "eslint": "^6.1.0", 36 | "webpack": "^4.38.0", 37 | "webpack-cli": "^3.3.6", 38 | "webpack-merge": "^4.2.1" 39 | }, 40 | "scripts": { 41 | "lint": "eslint --fix ./src", 42 | "start": "webpack --watch --config webpack/config.dev.js", 43 | "build": "webpack --config webpack/config.prod.js" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/style.js: -------------------------------------------------------------------------------- 1 | import { css } from 'lit'; 2 | 3 | const style = css` 4 | .IroSlider { 5 | display: none !important; 6 | } 7 | 8 | .light-entity-card { 9 | padding: 16px; 10 | } 11 | 12 | .light-entity-child-card { 13 | box-shadow: none !important; 14 | padding: 0 !important; 15 | } 16 | 17 | .light-entity-card.group { 18 | padding-bottom: 5; 19 | padding-top: 0; 20 | } 21 | 22 | .ha-slider-full-width ha-slider { 23 | width: 100%; 24 | } 25 | 26 | .percent-slider { 27 | color: var(--primary-text-color); 28 | display: flex; 29 | justify-content: center; 30 | align-items: center; 31 | } 32 | 33 | .light-entity-card__header { 34 | display: flex; 35 | justify-content: space-between; 36 | @apply --paper-font-headline; 37 | line-height: 40px; 38 | color: var(--primary-text-color); 39 | } 40 | 41 | .group .light-entity-card__header { 42 | } 43 | 44 | .light-entity-card-sliders > div { 45 | margin-top: 10px; 46 | } 47 | 48 | .group .light-entity-card-sliders > div { 49 | margin-top: 0px; 50 | } 51 | 52 | .light-entity-card__toggle { 53 | display: flex; 54 | cursor: pointer; 55 | } 56 | 57 | .light-entity-card__color-picker { 58 | display: flex; 59 | justify-content: space-around; 60 | margin-top: 10px; 61 | } 62 | 63 | .light-entity-card-color_temp { 64 | background-image: var(--ha-slider-background); 65 | } 66 | 67 | .light-entity-card-effectlist { 68 | padding-top: 10px; 69 | padding-bottom: 10px; 70 | } 71 | 72 | .group .light-entity-card-effectlist { 73 | padding-bottom: 20px; 74 | } 75 | 76 | .light-entity-card-center { 77 | display: flex; 78 | justify-content: center; 79 | cursor: pointer; 80 | } 81 | 82 | .hidden { 83 | display: none; 84 | } 85 | 86 | .icon-container { 87 | display: flex; 88 | justify-content: center; 89 | align-items: center; 90 | } 91 | `; 92 | 93 | export default style; 94 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Release on Tag 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - '*' 8 | 9 | jobs: 10 | build-and-release: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout Repository 15 | uses: actions/checkout@v3 16 | 17 | - name: Set up Node.js 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: '16' 21 | 22 | - name: Install Dependencies 23 | run: npm install 24 | 25 | - name: Run Build 26 | run: npm run build 27 | 28 | - name: Commit dist folder 29 | run: | 30 | git config --local user.email "action@github.com" 31 | git config --local user.name "GitHub Action" 32 | git add dist/ 33 | git commit -m "Add dist folder" 34 | 35 | - name: Push changes 36 | uses: ad-m/github-push-action@master 37 | with: 38 | github_token: ${{ secrets.GITHUB_TOKEN }} 39 | force: true 40 | directory: dist 41 | 42 | - name: Get current and previous tags 43 | id: tags 44 | run: | 45 | current_tag=$(git describe --tags --abbrev=0) 46 | previous_tag=$(git describe --tags --abbrev=0 $(git rev-list --tags --skip=1 --max-count=1)) 47 | echo ::set-output name=current_tag::${current_tag} 48 | echo ::set-output name=previous_tag::${previous_tag} 49 | 50 | - name: Generate release notes 51 | id: releasenotes 52 | run: | 53 | echo "Release Notes:" > release_notes.md 54 | git log ${previous_tag}..${current_tag} --pretty=format:"%h - %s" >> release_notes.md 55 | echo ::set-output name=notes::$(cat release_notes.md) 56 | 57 | - name: Create Release 58 | uses: actions/create-release@v1 59 | env: 60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 61 | with: 62 | tag_name: ${{ steps.tags.outputs.current_tag }} 63 | release_name: Release ${{ steps.tags.outputs.current_tag }} 64 | body: ${{ steps.releasenotes.outputs.notes }} 65 | draft: false 66 | prerelease: false 67 | 68 | - name: Upload light-entity-card.js to Release 69 | uses: actions/upload-release-asset@v1 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | with: 73 | upload_url: ${{ steps.create_release.outputs.upload_url }} 74 | asset_path: ./dist/light-entity-card.js 75 | asset_name: light-entity-card.js 76 | asset_content_type: application/javascript 77 | -------------------------------------------------------------------------------- /src/color-wheel/events-mixin.js: -------------------------------------------------------------------------------- 1 | import { dedupingMixin } from "@polymer/polymer/lib/utils/mixin"; 2 | import { fireEvent } from "./fire_event"; 3 | 4 | // Polymer legacy event helpers used courtesy of the Polymer project. 5 | // 6 | // Copyright (c) 2017 The Polymer Authors. All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions are 10 | // met: 11 | // 12 | // * Redistributions of source code must retain the above copyright 13 | // notice, this list of conditions and the following disclaimer. 14 | // * Redistributions in binary form must reproduce the above 15 | // copyright notice, this list of conditions and the following disclaimer 16 | // in the documentation and/or other materials provided with the 17 | // distribution. 18 | // * Neither the name of Google Inc. nor the names of its 19 | // contributors may be used to endorse or promote products derived from 20 | // this software without specific prior written permission. 21 | // 22 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | 34 | /* @polymerMixin */ 35 | export const EventsMixin = dedupingMixin( 36 | (superClass) => 37 | class extends superClass { 38 | /** 39 | * Dispatches a custom event with an optional detail value. 40 | * 41 | * @param {string} type Name of event type. 42 | * @param {*=} detail Detail value containing event-specific 43 | * payload. 44 | * @param {{ bubbles: (boolean|undefined), 45 | cancelable: (boolean|undefined), 46 | composed: (boolean|undefined) }=} 47 | * options Object specifying options. These may include: 48 | * `bubbles` (boolean, defaults to `true`), 49 | * `cancelable` (boolean, defaults to false), and 50 | * `node` on which to fire the event (HTMLElement, defaults to `this`). 51 | * @return {Event} The new event that was fired. 52 | */ 53 | fire(type, detail, options) { 54 | options = options || {}; 55 | return fireEvent(options.node || this, type, detail, options); 56 | } 57 | } 58 | ); 59 | -------------------------------------------------------------------------------- /src/color-wheel/fire_event.js: -------------------------------------------------------------------------------- 1 | // Polymer legacy event helpers used courtesy of the Polymer project. 2 | // 3 | // Copyright (c) 2017 The Polymer Authors. All rights reserved. 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | /** 32 | * Dispatches a custom event with an optional detail value. 33 | * 34 | * @param {string} type Name of event type. 35 | * @param {*=} detail Detail value containing event-specific 36 | * payload. 37 | * @param {{ bubbles: (boolean|undefined), 38 | * cancelable: (boolean|undefined), 39 | * composed: (boolean|undefined) }=} 40 | * options Object specifying options. These may include: 41 | * `bubbles` (boolean, defaults to `true`), 42 | * `cancelable` (boolean, defaults to false), and 43 | * `node` on which to fire the event (HTMLElement, defaults to `this`). 44 | * @return {Event} The new event that was fired. 45 | */ 46 | export const fireEvent = ( 47 | node, 48 | type, 49 | detail, 50 | options 51 | ) => { 52 | options = options || {}; 53 | 54 | detail = detail === null || detail === undefined ? {} : detail; 55 | 56 | const event = new Event(type, { 57 | bubbles: options.bubbles === undefined ? true : options.bubbles, 58 | cancelable: Boolean(options.cancelable), 59 | composed: options.composed === undefined ? true : options.composed, 60 | }); 61 | 62 | event.detail = detail; 63 | node.dispatchEvent(event); 64 | return event; 65 | }; 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Light Entity Card 2 | 3 | Control any light/switch entity through lovelace 4 | 5 | ## Support 6 | 7 | For help, visit the light entity support thread [here](https://community.home-assistant.io/t/light-entity-card/96146) 8 | 9 | 10 | 11 | [![GitHub Release][releases-shield]][releases] 12 | [![License][license-shield]](LICENSE.md) 13 | ![Project Maintenance][maintenance-shield] 14 | [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg?style=for-the-badge)](https://github.com/hacs/integration) 15 | 16 | ## Features 17 | 18 | * Works on any light and switch based entity 19 | * Toggle on/off 20 | * HS Color wheel 21 | * Color temperature and white value support 22 | * Support for configured language 23 | * Compact card support for grouped entities 24 | * use `persist_features: true` to always show entity features 25 | * use `effects_list` to add custom effects list or use `input_select` entity 26 | * always show or hide header and each input 27 | 28 | ## Installation 29 | 30 | Add through [HACS](https://github.com/custom-components/hacs) 31 | 32 | Issues with the installation should be asked in the [Home Asssitant forums](https://community.home-assistant.io/t/light-entity-card/96146) 33 | 34 | ## Configurations 35 | 36 | ```yaml 37 | type: custom:light-entity-card 38 | entity: light.downstairs 39 | ``` 40 | 41 | ```yaml 42 | type: custom:light-entity-card 43 | entity: light.downstairs 44 | effects_list: 45 | - effect1 46 | - effect2 47 | ``` 48 | 49 | ```yaml 50 | type: custom:light-entity-card 51 | entity: light.downstairs 52 | effects_list: input_select.custom_effect_list 53 | ``` 54 | 55 | ```yaml 56 | type: custom:light-entity-card 57 | entity: light.downstairs 58 | group: true 59 | ``` 60 | 61 | ## Options 62 | 63 | | Name | Type | Requirement | `Default value` Description | 64 | | -------------------- | ------------------- | ------------ | --------------------------------------------------------------------------- | 65 | | type | string | **Required** | `custom:light-entity-card` | 66 | | entity | string | **Required** | The entity name of the light entity to control | 67 | | shorten_cards | boolean | **Optional** | `false` show a compact version of the card | 68 | | consolidate_entities | boolean | **Optional** | `false` if entity is a group you can consolidate all entities into one | 69 | | persist_features | boolean | **Optional** | `false` always show entity features | 70 | | effects_list | list/string/boolean | **Optional** | custom list of effects, an input_select entity, or set false to always hide | 71 | | header | string | **Optional** | custom header name | 72 | | hide_header | boolean | **Optional** | `false` hides the entity header of the card including toggle | 73 | | show_header_icon | boolean | **Optional** | `false` shows the entity icon of the card including toggle | 74 | | brightness | boolean | **Optional** | `true` show brightness slider if available | 75 | | color_temp | boolean | **Optional** | `true` show color temp slider if available | 76 | | white_value | boolean | **Optional** | `true` show white value slider if available | 77 | | color_picker | boolean | **Optional** | `true` show color picker wheel if available | 78 | | speed | boolean | **Optional** | `false` show speed slider if available | 79 | | intensity | boolean | **Optional** | `false` show intensity slider if available | 80 | | force_features | boolean | **Optional** | `false` force showing all features in card | 81 | | full_width_sliders | boolean | **Optional** | `false` makes slider the full width of card | 82 | | brightness_icon | string | **Optional** | `weather-sunny` change the brightness slider icon | 83 | | white_icon | string | **Optional** | `file-word-box` change the white slider icon | 84 | | temperature_icon | string | **Optional** | `thermometer` change the temperature slider icon | 85 | | speed_icon | string | **Optional** | `speedometer` change the speed slider icon | 86 | | intensity_icon | string | **Optional** | `transit-connection-horizontal` change the intensity slider icon | 87 | | show_slider_percent | boolean | **Optional** | `false` show percent next to sliders | 88 | | child_card | boolean | **Optional** | `false` remove padding/margin to make this card within another card | 89 | 90 | --- 91 | 92 | Enjoy my card? Help me out for a couple of :beers: or a :coffee:! 93 | 94 | [![coffee](https://www.buymeacoffee.com/assets/img/custom_images/black_img.png)](https://www.buymeacoffee.com/JMISm06AD) 95 | 96 | [commits-shield]: https://img.shields.io/github/commit-activity/y/ljmerza/light-entity-card.svg?style=for-the-badge 97 | [commits]: https://github.com/ljmerza/light-entity-card/commits/master 98 | [license-shield]: https://img.shields.io/github/license/ljmerza/light-entity-card.svg?style=for-the-badge 99 | [maintenance-shield]: https://img.shields.io/badge/maintainer-Leonardo%20Merza%20%40ljmerza-blue.svg?style=for-the-badge 100 | [releases-shield]: https://img.shields.io/github/release/ljmerza/light-entity-card.svg?style=for-the-badge 101 | [releases]: https://github.com/ljmerza/light-entity-card/releases 102 | -------------------------------------------------------------------------------- /src/index-editor.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html } from 'lit'; 2 | import { ScopedRegistryHost } from '@lit-labs/scoped-registry-mixin'; 3 | import style from './style-editor'; 4 | import defaultConfig from './defaults'; 5 | import buildElementDefinitions from './buildElementDefinitions'; 6 | import globalElementLoader from './globalElementLoader'; 7 | 8 | export const fireEvent = (node, type, detail = {}, options = {}) => { 9 | const event = new Event(type, { 10 | bubbles: options.bubbles === undefined ? true : options.bubbles, 11 | cancelable: Boolean(options.cancelable), 12 | composed: options.composed === undefined ? true : options.composed, 13 | }); 14 | 15 | event.detail = detail; 16 | node.dispatchEvent(event); 17 | return event; 18 | }; 19 | 20 | export default class LightEntityCardEditor extends ScopedRegistryHost(LitElement) { 21 | static get elementDefinitions() { 22 | return buildElementDefinitions([ 23 | globalElementLoader('ha-checkbox'), 24 | globalElementLoader('ha-formfield'), 25 | globalElementLoader('ha-form-string'), 26 | globalElementLoader('ha-select'), 27 | globalElementLoader('mwc-list-item'), 28 | ], LightEntityCardEditor); 29 | } 30 | 31 | static get styles() { 32 | return style; 33 | } 34 | 35 | static get properties() { 36 | return { hass: {}, _config: {} }; 37 | } 38 | 39 | setConfig(config) { 40 | this._config = { 41 | ...defaultConfig, 42 | ...config, 43 | }; 44 | } 45 | 46 | get entityOptions() { 47 | const allEntities = Object.keys(this.hass.states).filter(eid => ['switch', 'light', 'group'].includes(eid.substr(0, eid.indexOf('.')))); 48 | 49 | allEntities.sort(); 50 | return allEntities; 51 | } 52 | 53 | firstUpdated() { 54 | this._firstRendered = true; 55 | } 56 | 57 | render() { 58 | if (!this.hass) { 59 | return html``; 60 | } 61 | 62 | // get header name 63 | let { header } = this._config; 64 | if (!header && this._config.entity) { 65 | let name = this._config.entity.split('.')[1] || ''; 66 | if (name) { 67 | name = name.charAt(0).toUpperCase() + name.slice(1); 68 | header = name; 69 | } 70 | } 71 | 72 | // eslint-disable-next-line arrow-body-style 73 | // eslint-disable-next-line arrow-parens 74 | const options = this.entityOptions.map(entity => html`${entity}`); 75 | 76 | return html` 77 |
78 | 79 |
80 | 87 |
88 | 89 |
90 | 96 | ${options} 97 | 98 | 105 |
106 | 107 |
108 | 115 | 122 |
123 | 124 |
125 |
126 | 127 | 132 | 133 | 134 | 139 | 140 |
141 | 142 |
143 | 144 | 149 | 150 | 151 | 156 | 157 |
158 | 159 |
160 | 161 | 166 | 167 | 168 | 173 | 174 |
175 | 176 |
177 | 178 | 183 | 184 | 185 | 190 | 191 |
192 | 193 |
194 | 195 | 200 | 201 | 202 | 207 | 208 |
209 | 210 |
211 | 212 | 217 | 218 | 219 | 224 | 225 |
226 | 227 |
228 | 229 | 234 | 235 | 236 | 241 | 242 | 243 | 248 | 249 |
250 | 251 |
252 | 253 | 258 | 259 |
260 | 261 |
262 | 263 | 268 | 269 |
270 |
271 |
272 | `; 273 | } 274 | 275 | configChanged(ev) { 276 | if (!this._config || !this.hass || !this._firstRendered) return; 277 | const { 278 | target: { configValue, value }, 279 | detail: { value: checkedValue }, 280 | } = ev; 281 | 282 | if (checkedValue !== undefined && checkedValue !== null) { 283 | this._config = { ...this._config, [configValue]: checkedValue }; 284 | } else { 285 | this._config = { ...this._config, [configValue]: value }; 286 | } 287 | 288 | fireEvent(this, 'config-changed', { config: this._config }); 289 | } 290 | 291 | checkboxConfigChanged(ev) { 292 | if (!this._config || !this.hass || !this._firstRendered) return; 293 | const { 294 | target: { value, checked }, 295 | } = ev; 296 | 297 | this._config = { ...this._config, [value]: checked }; 298 | 299 | fireEvent(this, 'config-changed', { config: this._config }); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/color-wheel/color-picker.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html } from 'lit'; 2 | import { ScopedRegistryHost } from '@lit-labs/scoped-registry-mixin'; 3 | 4 | import { hs2rgb, rgb2hs } from "./convert-color"; 5 | 6 | class LmeColorPicker extends ScopedRegistryHost(LitElement) { 7 | static get properties() { 8 | return { 9 | hsColor: { 10 | type: Object, 11 | }, 12 | 13 | // use these properties to update the state via attributes 14 | desiredHsColor: { 15 | type: Object, 16 | observer: "applyHsColor", 17 | }, 18 | 19 | // use these properties to update the state via attributes 20 | desiredRgbColor: { 21 | type: Object, 22 | observer: "applyRgbColor", 23 | }, 24 | 25 | // width, height and radius apply to the coordinates of 26 | // of the canvas. 27 | // border width are relative to these numbers 28 | // the onscreen displayed size should be controlled with css 29 | // and should be the same or smaller 30 | width: { 31 | type: Number, 32 | value: 500, 33 | }, 34 | 35 | height: { 36 | type: Number, 37 | value: 500, 38 | }, 39 | 40 | radius: { 41 | type: Number, 42 | value: 225, 43 | }, 44 | 45 | // the amount segments for the hue 46 | // 0 = continuous gradient 47 | // other than 0 gives 'pie-pieces' 48 | hueSegments: { 49 | type: Number, 50 | value: 0, 51 | observer: "segmentationChange", 52 | }, 53 | 54 | // the amount segments for the hue 55 | // 0 = continuous gradient 56 | // 1 = only fully saturated 57 | // > 1 = segments from white to fully saturated 58 | saturationSegments: { 59 | type: Number, 60 | value: 0, 61 | observer: "segmentationChange", 62 | }, 63 | 64 | // set to true to make the segments purely esthetical 65 | // this allows selection off all collors, also 66 | // interpolated between the segments 67 | ignoreSegments: { 68 | type: Boolean, 69 | value: false, 70 | }, 71 | 72 | // throttle te amount of 'colorselected' events fired 73 | // value is timeout in milliseconds 74 | throttle: { 75 | type: Number, 76 | value: 500, 77 | }, 78 | }; 79 | } 80 | 81 | render() { 82 | console.log('test') 83 | return html` 84 | 137 |
138 | 139 | 140 | 148 | 154 | 159 | 160 | 161 | 162 | 167 | 168 | 169 | 170 | 171 |
172 | `; 173 | } 174 | 175 | 176 | 177 | firstUpdated() { 178 | super.firstUpdated(); 179 | this.setupLayers(); 180 | this.drawColorWheel(); 181 | this.drawMarker(); 182 | 183 | if (this.desiredHsColor) { 184 | this.applyHsColor(this.desiredHsColor); 185 | } 186 | 187 | if (this.desiredRgbColor) { 188 | this.applyRgbColor(this.desiredRgbColor); 189 | } 190 | 191 | this.interactionLayer.addEventListener("mousedown", (ev) => 192 | this.onMouseDown(ev) 193 | ); 194 | this.interactionLayer.addEventListener("touchstart", (ev) => 195 | this.onTouchStart(ev) 196 | ); 197 | } 198 | 199 | // converts browser coordinates to canvas canvas coordinates 200 | // origin is wheel center 201 | // returns {x: X, y: Y} object 202 | convertToCanvasCoordinates(clientX, clientY) { 203 | const svgPoint = this.interactionLayer.createSVGPoint(); 204 | svgPoint.x = clientX; 205 | svgPoint.y = clientY; 206 | const cc = svgPoint.matrixTransform( 207 | this.interactionLayer.getScreenCTM().inverse() 208 | ); 209 | return { x: cc.x, y: cc.y }; 210 | } 211 | 212 | // Mouse events 213 | 214 | onMouseDown(ev) { 215 | const cc = this.convertToCanvasCoordinates(ev.clientX, ev.clientY); 216 | // return if we're not on the wheel 217 | if (!this.isInWheel(cc.x, cc.y)) { 218 | return; 219 | } 220 | // a mousedown in wheel is always a color select action 221 | this.onMouseSelect(ev); 222 | // allow dragging 223 | this.canvas.classList.add("mouse", "dragging"); 224 | this.addEventListener("mousemove", this.onMouseSelect); 225 | this.addEventListener("mouseup", this.onMouseUp); 226 | } 227 | 228 | onMouseUp() { 229 | this.canvas.classList.remove("mouse", "dragging"); 230 | this.removeEventListener("mousemove", this.onMouseSelect); 231 | } 232 | 233 | onMouseSelect(ev) { 234 | requestAnimationFrame(() => this.processUserSelect(ev)); 235 | } 236 | 237 | // Touch events 238 | onTouchStart(ev) { 239 | const touch = ev.changedTouches[0]; 240 | const cc = this.convertToCanvasCoordinates(touch.clientX, touch.clientY); 241 | // return if we're not on the wheel 242 | if (!this.isInWheel(cc.x, cc.y)) { 243 | return; 244 | } 245 | if (ev.target === this.marker) { 246 | // drag marker 247 | ev.preventDefault(); 248 | this.canvas.classList.add("touch", "dragging"); 249 | this.addEventListener("touchmove", this.onTouchSelect); 250 | this.addEventListener("touchend", this.onTouchEnd); 251 | return; 252 | } 253 | // don't fire color selection immediately, 254 | // wait for touchend and invalidate when we scroll 255 | this.tapBecameScroll = false; 256 | this.addEventListener("touchend", this.onTap); 257 | this.addEventListener( 258 | "touchmove", 259 | () => { 260 | this.tapBecameScroll = true; 261 | }, 262 | { passive: true } 263 | ); 264 | } 265 | 266 | onTap(ev) { 267 | if (this.tapBecameScroll) { 268 | return; 269 | } 270 | ev.preventDefault(); 271 | this.onTouchSelect(ev); 272 | } 273 | 274 | onTouchEnd() { 275 | this.canvas.classList.remove("touch", "dragging"); 276 | this.removeEventListener("touchmove", this.onTouchSelect); 277 | } 278 | 279 | onTouchSelect(ev) { 280 | requestAnimationFrame(() => this.processUserSelect(ev.changedTouches[0])); 281 | } 282 | 283 | /* 284 | * General event/selection handling 285 | */ 286 | 287 | // Process user input to color 288 | processUserSelect(ev) { 289 | const canvasXY = this.convertToCanvasCoordinates(ev.clientX, ev.clientY); 290 | const hs = this.getColor(canvasXY.x, canvasXY.y); 291 | let rgb; 292 | if (!this.isInWheel(canvasXY.x, canvasXY.y)) { 293 | const [r, g, b] = hs2rgb([hs.h, hs.s]); 294 | rgb = { r, g, b }; 295 | } else { 296 | rgb = this.getRgbColor(canvasXY.x, canvasXY.y); 297 | } 298 | this.onColorSelect(hs, rgb); 299 | } 300 | 301 | // apply color to marker position and canvas 302 | onColorSelect(hs, rgb) { 303 | this.setMarkerOnColor(hs); // marker always follows mouse 'raw' hs value (= mouse position) 304 | if (!this.ignoreSegments) { 305 | // apply segments if needed 306 | hs = this.applySegmentFilter(hs); 307 | } 308 | // always apply the new color to the interface / canvas 309 | this.applyColorToCanvas(hs); 310 | // throttling is applied to updating the exposed colors (properties) 311 | // and firing of events 312 | if (this.colorSelectIsThrottled) { 313 | // make sure we apply the last selected color 314 | // eventually after throttle limit has passed 315 | clearTimeout(this.ensureFinalSelect); 316 | this.ensureFinalSelect = setTimeout(() => { 317 | this.fireColorSelected(hs, rgb); // do it for the final time 318 | }, this.throttle); 319 | return; 320 | } 321 | this.fireColorSelected(hs, rgb); // do it 322 | this.colorSelectIsThrottled = true; 323 | setTimeout(() => { 324 | this.colorSelectIsThrottled = false; 325 | }, this.throttle); 326 | } 327 | 328 | // set color values and fire colorselected event 329 | fireColorSelected(hs, rgb) { 330 | this.hsColor = hs; 331 | this.fire("colorselected", { hs, rgb }); 332 | } 333 | 334 | /* 335 | * Interface updating 336 | */ 337 | 338 | // set marker position to the given color 339 | setMarkerOnColor(hs) { 340 | if (!this.marker || !this.tooltip) { 341 | return; 342 | } 343 | const dist = hs.s * this.radius; 344 | const theta = ((hs.h - 180) / 180) * Math.PI; 345 | const markerdX = -dist * Math.cos(theta); 346 | const markerdY = -dist * Math.sin(theta); 347 | const translateString = `translate(${markerdX},${markerdY})`; 348 | this.marker.setAttribute("transform", translateString); 349 | this.tooltip.setAttribute("transform", translateString); 350 | } 351 | 352 | // apply given color to interface elements 353 | applyColorToCanvas(hs) { 354 | if (!this.interactionLayer) { 355 | return; 356 | } 357 | // we're not really converting hs to hsl here, but we keep it cheap 358 | // setting the color on the interactionLayer, the svg elements can inherit 359 | this.interactionLayer.style.color = `hsl(${hs.h}, 100%, ${ 360 | 100 - hs.s * 50 361 | }%)`; 362 | } 363 | 364 | applyHsColor(hs) { 365 | // do nothing is we already have the same color 366 | if (this.hsColor && this.hsColor.h === hs.h && this.hsColor.s === hs.s) { 367 | return; 368 | } 369 | this.setMarkerOnColor(hs); // marker is always set on 'raw' hs position 370 | if (!this.ignoreSegments) { 371 | // apply segments if needed 372 | hs = this.applySegmentFilter(hs); 373 | } 374 | this.hsColor = hs; 375 | // always apply the new color to the interface / canvas 376 | this.applyColorToCanvas(hs); 377 | } 378 | 379 | applyRgbColor(rgb) { 380 | const [h, s] = rgb2hs(rgb); 381 | this.applyHsColor({ h, s }); 382 | } 383 | 384 | /* 385 | * input processing helpers 386 | */ 387 | 388 | // get angle (degrees) 389 | getAngle(dX, dY) { 390 | const theta = Math.atan2(-dY, -dX); // radians from the left edge, clockwise = positive 391 | const angle = (theta / Math.PI) * 180 + 180; // degrees, clockwise from right 392 | return angle; 393 | } 394 | 395 | // returns true when coordinates are in the colorwheel 396 | isInWheel(x, y) { 397 | return this.getDistance(x, y) <= 1; 398 | } 399 | 400 | // returns distance from wheel center, 0 = center, 1 = edge, >1 = outside 401 | getDistance(dX, dY) { 402 | return Math.sqrt(dX * dX + dY * dY) / this.radius; 403 | } 404 | 405 | /* 406 | * Getting colors 407 | */ 408 | 409 | getColor(x, y) { 410 | const hue = this.getAngle(x, y); // degrees, clockwise from right 411 | const relativeDistance = this.getDistance(x, y); // edge of radius = 1 412 | const sat = Math.min(relativeDistance, 1); // Distance from center 413 | return { h: hue, s: sat }; 414 | } 415 | 416 | getRgbColor(x, y) { 417 | // get current pixel 418 | const imageData = this.backgroundLayer 419 | .getContext("2d") 420 | .getImageData(x + 250, y + 250, 1, 1); 421 | const pixel = imageData.data; 422 | return { r: pixel[0], g: pixel[1], b: pixel[2] }; 423 | } 424 | 425 | applySegmentFilter(hs) { 426 | // apply hue segment steps 427 | if (this.hueSegments) { 428 | const angleStep = 360 / this.hueSegments; 429 | const halfAngleStep = angleStep / 2; 430 | hs.h -= halfAngleStep; // take the 'centered segemnts' into account 431 | if (hs.h < 0) { 432 | hs.h += 360; 433 | } // don't end up below 0 434 | const rest = hs.h % angleStep; 435 | hs.h -= rest - angleStep; 436 | } 437 | 438 | // apply saturation segment steps 439 | if (this.saturationSegments) { 440 | if (this.saturationSegments === 1) { 441 | hs.s = 1; 442 | } else { 443 | const segmentSize = 1 / this.saturationSegments; 444 | const saturationStep = 1 / (this.saturationSegments - 1); 445 | const calculatedSat = Math.floor(hs.s / segmentSize) * saturationStep; 446 | hs.s = Math.min(calculatedSat, 1); 447 | } 448 | } 449 | return hs; 450 | } 451 | 452 | /* 453 | * Drawing related stuff 454 | */ 455 | 456 | setupLayers() { 457 | this.canvas = this.$.canvas; 458 | this.backgroundLayer = this.$.backgroundLayer; 459 | this.interactionLayer = this.$.interactionLayer; 460 | 461 | // coordinate origin position (center of the wheel) 462 | this.originX = this.width / 2; 463 | this.originY = this.originX; 464 | 465 | // synchronise width/height coordinates 466 | this.backgroundLayer.width = this.width; 467 | this.backgroundLayer.height = this.height; 468 | this.interactionLayer.setAttribute( 469 | "viewBox", 470 | `${-this.originX} ${-this.originY} ${this.width} ${this.height}` 471 | ); 472 | } 473 | 474 | drawColorWheel() { 475 | /* 476 | * Setting up all paremeters 477 | */ 478 | let shadowColor; 479 | let shadowOffsetX; 480 | let shadowOffsetY; 481 | let shadowBlur; 482 | const context = this.backgroundLayer.getContext("2d"); 483 | // postioning and sizing 484 | const cX = this.originX; 485 | const cY = this.originY; 486 | const radius = this.radius; 487 | const counterClockwise = false; 488 | // styling of the wheel 489 | const wheelStyle = window.getComputedStyle(this.backgroundLayer, null); 490 | const borderWidth = parseInt( 491 | wheelStyle.getPropertyValue("--wheel-borderwidth"), 492 | 10 493 | ); 494 | const borderColor = wheelStyle 495 | .getPropertyValue("--wheel-bordercolor") 496 | .trim(); 497 | const wheelShadow = wheelStyle.getPropertyValue("--wheel-shadow").trim(); 498 | // extract shadow properties from CSS variable 499 | // the shadow should be defined as: "10px 5px 5px 0px COLOR" 500 | if (wheelShadow !== "none") { 501 | const values = wheelShadow.split("px "); 502 | shadowColor = values.pop(); 503 | shadowOffsetX = parseInt(values[0], 10); 504 | shadowOffsetY = parseInt(values[1], 10); 505 | shadowBlur = parseInt(values[2], 10) || 0; 506 | } 507 | const borderRadius = radius + borderWidth / 2; 508 | const wheelRadius = radius; 509 | const shadowRadius = radius + borderWidth; 510 | 511 | /* 512 | * Drawing functions 513 | */ 514 | function drawCircle(hueSegments, saturationSegments) { 515 | hueSegments = hueSegments || 360; // reset 0 segments to 360 516 | const angleStep = 360 / hueSegments; 517 | const halfAngleStep = angleStep / 2; // center segments on color 518 | for (let angle = 0; angle <= 360; angle += angleStep) { 519 | const startAngle = (angle - halfAngleStep) * (Math.PI / 180); 520 | const endAngle = (angle + halfAngleStep + 1) * (Math.PI / 180); 521 | context.beginPath(); 522 | context.moveTo(cX, cY); 523 | context.arc( 524 | cX, 525 | cY, 526 | wheelRadius, 527 | startAngle, 528 | endAngle, 529 | counterClockwise 530 | ); 531 | context.closePath(); 532 | // gradient 533 | const gradient = context.createRadialGradient( 534 | cX, 535 | cY, 536 | 0, 537 | cX, 538 | cY, 539 | wheelRadius 540 | ); 541 | let lightness = 100; 542 | // first gradient stop 543 | gradient.addColorStop(0, `hsl(${angle}, 100%, ${lightness}%)`); 544 | // segment gradient stops 545 | if (saturationSegments > 0) { 546 | const ratioStep = 1 / saturationSegments; 547 | let ratio = 0; 548 | for (let stop = 1; stop < saturationSegments; stop += 1) { 549 | const prevLighness = lightness; 550 | ratio = stop * ratioStep; 551 | lightness = 100 - 50 * ratio; 552 | gradient.addColorStop( 553 | ratio, 554 | `hsl(${angle}, 100%, ${prevLighness}%)` 555 | ); 556 | gradient.addColorStop(ratio, `hsl(${angle}, 100%, ${lightness}%)`); 557 | } 558 | gradient.addColorStop(ratio, `hsl(${angle}, 100%, 50%)`); 559 | } 560 | // last gradient stop 561 | gradient.addColorStop(1, `hsl(${angle}, 100%, 50%)`); 562 | 563 | context.fillStyle = gradient; 564 | context.fill(); 565 | } 566 | } 567 | 568 | function drawShadow() { 569 | context.save(); 570 | context.beginPath(); 571 | context.arc(cX, cY, shadowRadius, 0, 2 * Math.PI, false); 572 | context.shadowColor = shadowColor; 573 | context.shadowOffsetX = shadowOffsetX; 574 | context.shadowOffsetY = shadowOffsetY; 575 | context.shadowBlur = shadowBlur; 576 | context.fillStyle = "white"; 577 | context.fill(); 578 | context.restore(); 579 | } 580 | 581 | function drawBorder() { 582 | context.beginPath(); 583 | context.arc(cX, cY, borderRadius, 0, 2 * Math.PI, false); 584 | context.lineWidth = borderWidth; 585 | context.strokeStyle = borderColor; 586 | context.stroke(); 587 | } 588 | 589 | /* 590 | * Call the drawing functions 591 | * draws the shadow, wheel and border 592 | */ 593 | if (wheelStyle.shadow !== "none") { 594 | drawShadow(); 595 | } 596 | drawCircle(this.hueSegments, this.saturationSegments); 597 | if (borderWidth > 0) { 598 | drawBorder(); 599 | } 600 | } 601 | 602 | /* 603 | * Draw the (draggable) marker and tooltip 604 | * on the interactionLayer) 605 | */ 606 | 607 | drawMarker() { 608 | const svgElement = this.interactionLayer; 609 | const markerradius = this.radius * 0.08; 610 | const tooltipradius = this.radius * 0.15; 611 | const TooltipOffsetY = -(tooltipradius * 3); 612 | const TooltipOffsetX = 0; 613 | 614 | svgElement.marker = document.createElementNS( 615 | "http://www.w3.org/2000/svg", 616 | "circle" 617 | ); 618 | svgElement.marker.setAttribute("id", "marker"); 619 | svgElement.marker.setAttribute("r", markerradius); 620 | this.marker = svgElement.marker; 621 | svgElement.appendChild(svgElement.marker); 622 | 623 | svgElement.tooltip = document.createElementNS( 624 | "http://www.w3.org/2000/svg", 625 | "circle" 626 | ); 627 | svgElement.tooltip.setAttribute("id", "colorTooltip"); 628 | svgElement.tooltip.setAttribute("r", tooltipradius); 629 | svgElement.tooltip.setAttribute("cx", TooltipOffsetX); 630 | svgElement.tooltip.setAttribute("cy", TooltipOffsetY); 631 | this.tooltip = svgElement.tooltip; 632 | svgElement.appendChild(svgElement.tooltip); 633 | } 634 | 635 | segmentationChange() { 636 | if (this.backgroundLayer) { 637 | this.drawColorWheel(); 638 | } 639 | } 640 | } 641 | 642 | export default LmeColorPicker; 643 | 644 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html } from 'lit'; 2 | import iro from '@jaames/iro'; 3 | import { ScopedRegistryHost } from '@lit-labs/scoped-registry-mixin'; 4 | 5 | import style from './style'; 6 | import defaultConfig from './defaults'; 7 | import LightEntityCardEditor from './index-editor'; 8 | import packageJson from '../package.json'; 9 | import buildElementDefinitions from './buildElementDefinitions'; 10 | import globalElementLoader from './globalElementLoader'; 11 | 12 | const editorName = 'light-entity-card-editor'; 13 | customElements.define(editorName, LightEntityCardEditor); 14 | 15 | console.info(`light-entity-card v${packageJson.version}`); 16 | 17 | class LightEntityCard extends ScopedRegistryHost(LitElement) { 18 | static get elementDefinitions() { 19 | return buildElementDefinitions( 20 | [ 21 | globalElementLoader('ha-card'), 22 | globalElementLoader('more-info-light'), 23 | globalElementLoader('ha-switch'), 24 | globalElementLoader('ha-icon'), 25 | globalElementLoader('state-badge'), 26 | globalElementLoader('ha-slider'), 27 | globalElementLoader('ha-color-picker'), 28 | globalElementLoader('ha-select'), 29 | globalElementLoader('mwc-list-item'), 30 | ], 31 | LightEntityCard 32 | ); 33 | } 34 | 35 | static get properties() { 36 | return { 37 | hass: {}, 38 | config: {}, 39 | }; 40 | } 41 | 42 | async firstUpdated() { 43 | this.setColorWheels(); 44 | this._firstUpdate = true; 45 | } 46 | 47 | async updated() { 48 | this.setColorWheels(); 49 | } 50 | 51 | setColorWheels() { 52 | if(!this._shownStateObjects) return; 53 | 54 | const colorPickerWidth = this.getColorPickerWidth(); 55 | 56 | for(const entity of this._shownStateObjects) { 57 | const picker = this.renderRoot.getElementById(`picker-${entity.entity_id}`) 58 | if(!picker) continue; 59 | picker.innerHTML = ''; 60 | 61 | let color = { h: 0, s: 0, l: 50 } 62 | 63 | if(entity.attributes.hs_color) { 64 | const h = parseInt(entity.attributes.hs_color[0]); 65 | const s = parseInt(entity.attributes.hs_color[1]); 66 | color = { h, s, l: 50 } 67 | } 68 | 69 | const colorPicker = new iro.ColorPicker(picker, { 70 | sliderSize: 0, 71 | color, 72 | width: colorPickerWidth, 73 | wheelLightness: false, 74 | }) 75 | 76 | colorPicker.on("input:end", color => this.setColorPicker(color.hsl, entity)); 77 | } 78 | } 79 | 80 | getColorPickerWidth() { 81 | const elem = this.shadowRoot.querySelector('.light-entity-card'); 82 | 83 | const width = elem.offsetWidth; 84 | const shorten = this.config.shorten_cards; 85 | 86 | const calcWidth = width - (shorten ? 100: 50); 87 | const maxWidth = shorten ? 200 : 300; 88 | const realWidth = maxWidth > calcWidth ? calcWidth : maxWidth 89 | 90 | return realWidth; 91 | } 92 | 93 | /** 94 | * checks and saves config of entity 95 | * @param {*} config 96 | */ 97 | setConfig(config) { 98 | if (!config.entity) throw Error('entity required.'); 99 | 100 | this.config = { 101 | ...defaultConfig, 102 | ...config, 103 | }; 104 | } 105 | 106 | static async getConfigElement() { 107 | // eslint-disable-next-line no-undef 108 | return document.createElement(editorName); 109 | } 110 | 111 | static get featureNames() { 112 | return { 113 | brightness: 1, 114 | colorTemp: 2, 115 | effectList: 4, 116 | color: 16, 117 | whiteValue: 128, 118 | }; 119 | } 120 | 121 | static get cmdToggle() { 122 | return { 123 | on: 'turn_on', 124 | off: 'turn_off', 125 | }; 126 | } 127 | 128 | static get entityLength() { 129 | return { 130 | light: 10, 131 | switch: 1, 132 | }; 133 | } 134 | 135 | /** 136 | * get the current size of the card 137 | * @return {Number} 138 | */ 139 | getCardSize() { 140 | if (!this.config || !this.__hass || !this.__hass.states[this.config.entity]) { 141 | return 1; 142 | } 143 | 144 | let cardLength = 0; 145 | const entities = this.__hass.states[this.config.entity]; 146 | 147 | // if given a group entity then sum length of each entity by type 148 | // else just get the sible entity length 149 | if (Array.isArray(entities.attributes.entity_id)) { 150 | entities.attributes.entity_id.forEach(entity_id => (cardLength += this.getEntityLength(entity_id))); 151 | } else { 152 | cardLength += this.getEntityLength(entities.attributes.entity_id); 153 | } 154 | 155 | // if we are compacting the card account for that 156 | if (this.config.group) { 157 | cardLength *= 0.8; 158 | } 159 | 160 | return parseInt(cardLength, 1); 161 | } 162 | 163 | /** 164 | * determines the UI length of an entity 165 | * @param {string} entity_id 166 | */ 167 | getEntityLength(entity_id) { 168 | if (/^light\./.test(entity_id)) { 169 | return LightEntityCard.entityLength.light; 170 | } else if (/^switch\./.test(entity_id)) { 171 | return LightEntityCard.entityLength.switch; 172 | } else { 173 | return 0; 174 | } 175 | } 176 | 177 | /** 178 | * generates the CSS styles for this card 179 | * @return {TemplateResult} 180 | */ 181 | get styles() { 182 | return style; 183 | } 184 | 185 | get language() { 186 | return this.__hass.resources[this.__hass.language]; 187 | } 188 | 189 | /** 190 | * check if the given entity is on or off 191 | * @param {LightEntity} stateObj 192 | * @return {Boolean} 193 | */ 194 | isEntityOn(stateObj) { 195 | return stateObj.state === 'on'; 196 | } 197 | 198 | /** 199 | * generates a card for each given entiy in the config 200 | * @return {TemplateResult} 201 | */ 202 | render() { 203 | const entity = this.hass.states[this.config.entity]; 204 | if (!entity) { 205 | return html` 206 | 209 | ${`Invalid entity: ${this.config.entity}`} 210 | `; 211 | } 212 | 213 | this._stateObjects = this.getEntitiesToShow(entity); 214 | 215 | // need to find what state objects are actually going to be shown 216 | if (this.config.consolidate_entities) { 217 | this._shownStateObjects = [entity]; 218 | } else { 219 | this._shownStateObjects = [...this._stateObjects]; 220 | } 221 | 222 | const templates = this._shownStateObjects.reduce( 223 | (htmlTemplate, stateObj) => html`${htmlTemplate}${this.createEntityTemplate(stateObj)}`, 224 | // eslint-disable-next-line comma-dangle 225 | '' 226 | ); 227 | 228 | const css = `light-entity-card ${this.config.shorten_cards ? ' group' : ''} ${ 229 | this.config.child_card ? ' light-entity-child-card' : '' 230 | }`; 231 | 232 | setTimeout(() => { 233 | this.setColorWheels(); 234 | }, 100) 235 | 236 | return html` 237 | 240 | 241 | ${templates} 242 | 243 | `; 244 | } 245 | 246 | /** 247 | * gets all the entities we need to build this card for 248 | * @param {LightEntity|GroupEntity} entities 249 | * @return {Array} 250 | */ 251 | getEntitiesToShow(entities) { 252 | if (entities.attributes.entity_id && Array.isArray(entities.attributes.entity_id)) 253 | return entities.attributes.entity_id.map(entity_id => this.hass.states[entity_id]).filter(Boolean); 254 | 255 | return [entities]; 256 | } 257 | 258 | /** 259 | * creates an entity's template 260 | * @param {LightEntity} stateObj 261 | * @return {TemplateResult} 262 | */ 263 | createEntityTemplate(stateObj) { 264 | const sliderClass = this.config.full_width_sliders ? 'ha-slider-full-width' : ''; 265 | 266 | return html` 267 | ${this.createHeader(stateObj)} 268 |
269 | ${this.createBrightnessSlider(stateObj)} ${this.createSpeedSlider(stateObj)} 270 | ${this.createIntensitySlider(stateObj)} ${this.createColorTemperature(stateObj)} 271 | ${this.createWhiteValue(stateObj)} 272 |
273 | ${this.createColorPicker(stateObj)} ${this.createEffectList(stateObj)} 274 | `; 275 | } 276 | 277 | /** 278 | * creates card header with state toggle for a given entity 279 | * @param {LightEntity} stateObj 280 | * @return {TemplateResult} 281 | */ 282 | createHeader(stateObj) { 283 | if (this.config.hide_header) return html``; 284 | const title = this.config.header || stateObj.attributes.friendly_name || stateObj.entity_id; 285 | 286 | return html` 287 |
288 | ${this.showHeaderIcon(stateObj)} 289 |
${title}
290 |
291 | this.setToggle(e, stateObj)}> 292 |
293 |
294 | `; 295 | } 296 | 297 | showHeaderIcon(stateObj) { 298 | if (!this.config.show_header_icon) return html``; 299 | 300 | return html` 301 |
302 | 303 |
304 | `; 305 | } 306 | 307 | /** 308 | * creates brightness slider 309 | * @param {LightEntity} stateObj 310 | * @return {TemplateResult} 311 | */ 312 | createBrightnessSlider(stateObj) { 313 | if (this.config.brightness === false) return html``; 314 | if (this.dontShowFeature('brightness', stateObj)) return html``; 315 | 316 | return html` 317 |
318 |
319 | 320 |
321 | 327 | ${this.showPercent(stateObj.attributes.brightness, 0, 254)} 328 |
329 | `; 330 | } 331 | 332 | /** 333 | * creates speed slider 334 | * @param {LightEntity} stateObj 335 | * @return {TemplateResult} 336 | */ 337 | createSpeedSlider(stateObj) { 338 | if (this.config.speed === false) return html``; 339 | if (this.dontShowFeature('speed', stateObj)) return html``; 340 | 341 | return html` 342 |
343 |
344 | 345 |
346 | 352 | ${this.showPercent(stateObj.attributes.speed, 0, 254)} 353 |
354 | `; 355 | } 356 | 357 | /** 358 | * creates intensity slider 359 | * @param {LightEntity} stateObj 360 | * @return {TemplateResult} 361 | */ 362 | createIntensitySlider(stateObj) { 363 | if (this.config.speed === false) return html``; 364 | if (this.dontShowFeature('intensity', stateObj)) return html``; 365 | 366 | return html` 367 |
368 |
369 | 370 |
371 | 377 | ${this.showPercent(stateObj.attributes.intensity, 0, 254)} 378 |
379 | `; 380 | } 381 | 382 | /** 383 | * shows slider percent if config is set 384 | * @param {number} value 385 | * @param {number} min 386 | * @param {number} max 387 | * @return {TemplateResult} 388 | */ 389 | showPercent(value, min, max) { 390 | if (!this.config.show_slider_percent) return html``; 391 | let percent = parseInt(((value - min) * 100) / (max - min), 0); 392 | if (isNaN(percent)) percent = 0; 393 | 394 | return html`
${percent}%
`; 395 | } 396 | 397 | /** 398 | * creates color temperature slider for a given entity 399 | * @param {LightEntity} stateObj 400 | * @return {TemplateResult} 401 | */ 402 | createColorTemperature(stateObj) { 403 | if (this.config.color_temp === false) return html``; 404 | if (this.dontShowFeature('colorTemp', stateObj)) return html``; 405 | 406 | const percent = this.showPercent( 407 | stateObj.attributes.color_temp, 408 | stateObj.attributes.min_mireds - 1, 409 | // eslint-disable-next-line comma-dangle 410 | stateObj.attributes.max_mireds - 1 411 | ); 412 | 413 | return html` 414 |
415 |
416 | 417 |
418 | 425 | 426 | ${percent} 427 |
428 | `; 429 | } 430 | 431 | /** 432 | * creates white value slider for a given entity 433 | * @param {LightEntity} stateObj 434 | * @return {TemplateResult} 435 | */ 436 | createWhiteValue(stateObj) { 437 | if (this.config.white_value === false) return html``; 438 | if (this.dontShowFeature('whiteValue', stateObj)) return html``; 439 | 440 | return html` 441 |
442 |
443 | 444 |
445 | 450 | 451 | ${this.showPercent(stateObj.attributes.white_value, 0, 254)} 452 |
453 | `; 454 | } 455 | 456 | /** 457 | * creates effect list dropdown for a given entity 458 | * @param {LightEntity} stateObj 459 | * @return {TemplateResult} 460 | */ 461 | createEffectList(stateObj) { 462 | // do we disable effect list always? 463 | if (this.config.effects_list === false) return html``; 464 | 465 | // need to check state and persist_features here because if given custom effect list we may 466 | // want to sho that even if the feature doesn't exist so dont check that part to move forward just persist_features/state 467 | if (!this.config.persist_features && !this.isEntityOn(stateObj)) return html``; 468 | 469 | let effect_list = stateObj.attributes.effect_list || []; 470 | 471 | // if we were given a custom list then use that 472 | if (this.config.effects_list && Array.isArray(this.config.effects_list)) { 473 | effect_list = this.config.effects_list; 474 | } else if (this.config.effects_list && this.hass.states[this.config.effects_list]) { 475 | // else if given an input_select entity use that as effect list 476 | const inputSelect = this.hass.states[this.config.effects_list]; 477 | effect_list = (inputSelect.attributes && inputSelect.attributes.options) || []; 478 | } else if (this.dontShowFeature('effectList', stateObj)) { 479 | // finally if no custom list nor feature exists then dont show effect list 480 | return html``; 481 | } 482 | 483 | const listItems = effect_list.map(effect => this.createListItem(stateObj, effect)); 484 | const caption = this.language['ui.card.light.effect']; 485 | 486 | return html` 487 |
488 | this.setEffect(e, stateObj)} 491 | label="${caption}" 492 | > 493 | ${listItems} 494 | 495 |
496 | `; 497 | } 498 | 499 | createListItem(stateObj, effect) { 500 | return html`${effect}`; 503 | } 504 | 505 | /** 506 | * creates color picker wheel for a given entity 507 | * @param {LightEntity} stateObj 508 | * @return {TemplateResult} 509 | */ 510 | createColorPicker(stateObj) { 511 | if (this.config.color_picker === false) return html``; 512 | if (this.dontShowFeature('color', stateObj)) return html``; 513 | 514 | return html` 515 |
516 |
517 |
518 | `; 519 | 520 | } 521 | 522 | /** 523 | * do we show a feature or not? 524 | * @param {string} featureName 525 | * @param {LightEntity} stateObj 526 | * @return {boolean} 527 | */ 528 | dontShowFeature(featureName, stateObj) { 529 | // show all feature if this is set to true 530 | if (this.config.force_features) return false; 531 | 532 | // WLED support 533 | if (featureName === 'speed' && 'speed' in stateObj.attributes) return true; 534 | if (featureName === 'intensity' && 'intensity' in stateObj.attributes) return true; 535 | 536 | // old deprecated way to seeing if supported feature 537 | let featureSupported = LightEntityCard.featureNames[featureName] & stateObj.attributes.supported_features; 538 | 539 | // support new color modes https://developers.home-assistant.io/docs/core/entity/light/#color-modes 540 | const colorModes = stateObj.attributes.supported_color_modes || []; 541 | 542 | if (!featureSupported) { 543 | switch (featureName) { 544 | case 'brightness': 545 | featureSupported = Object.prototype.hasOwnProperty.call(stateObj.attributes, 'brightness'); 546 | if (!featureSupported) { 547 | const supportedModes = ['hs', 'rgb', 'rgbw', 'rgbww', 'white', 'brightness', 'color_temp', 'xy']; 548 | featureSupported = [...new Set(colorModes.filter(mode => supportedModes.includes(mode)))].length > 0; 549 | } 550 | 551 | break; 552 | case 'colorTemp': 553 | if (colorModes) { 554 | const supportedModes = ['color_temp']; 555 | featureSupported = [...new Set(colorModes.filter(mode => supportedModes.includes(mode)))].length > 0; 556 | } 557 | break; 558 | case 'effectList': 559 | featureSupported = stateObj.attributes.effect_list && stateObj.attributes.effect_list.length; 560 | break; 561 | case 'color': 562 | if (!featureSupported) { 563 | const supportedModes = ['hs', 'rgb', 'rgbw', 'rgbww', 'xy']; 564 | featureSupported = [...new Set(colorModes.filter(mode => supportedModes.includes(mode)))].length > 0; 565 | } 566 | break; 567 | case 'whiteValue': 568 | featureSupported = Object.prototype.hasOwnProperty.call(stateObj.attributes, 'white_value'); 569 | break; 570 | default: 571 | featureSupported = false; 572 | break; 573 | } 574 | } 575 | 576 | if (!featureSupported) return true; 577 | if (!this.config.persist_features && !this.isEntityOn(stateObj)) return true; 578 | } 579 | 580 | /** 581 | * change to hs color for a given entity 582 | * @param {HSL} hsl 583 | * @param {LightEntity} stateObj 584 | */ 585 | setColorPicker(hsl, stateObj) { 586 | this.callEntityService({ hs_color: [hsl.h, hsl.s] }, stateObj); 587 | } 588 | 589 | _setValue(event, stateObj, valueName) { 590 | const newValue = parseInt(event.target.value, 0); 591 | if (isNaN(newValue) || parseInt(stateObj.attributes[valueName], 0) === newValue) return; 592 | 593 | this.callEntityService({ [valueName]: newValue }, stateObj); 594 | } 595 | 596 | /** 597 | * sets the toggle state based on the given entity state 598 | * @param {CustomEvent} event 599 | * @param {LightEntity} stateObj 600 | */ 601 | setToggle(event, stateObj) { 602 | const newState = this.isEntityOn(stateObj) ? LightEntityCard.cmdToggle.off : LightEntityCard.cmdToggle.on; 603 | this.callEntityService({}, stateObj, newState); 604 | } 605 | 606 | /** 607 | * sets the current effect selected for an entity 608 | * @param {CustomEvent} event 609 | * @param {LightEntity} entity 610 | */ 611 | setEffect(event, stateObj) { 612 | if(!event.target.value ) return; 613 | this.callEntityService({ effect: event.target.value }, stateObj); 614 | } 615 | 616 | /** 617 | * call light service to update a state of an entity 618 | * @param {Object} payload 619 | * @param {LightEntity} entity 620 | * @param {String} state 621 | */ 622 | callEntityService(payload, stateObj, state) { 623 | if(!this._firstUpdate) return; 624 | 625 | let entityType = stateObj.entity_id.split('.')[0]; 626 | if (entityType === 'group') entityType = 'homeassistant'; 627 | 628 | this.hass.callService(entityType, state || LightEntityCard.cmdToggle.on, { 629 | entity_id: stateObj.entity_id, 630 | ...payload, 631 | }); 632 | } 633 | } 634 | 635 | customElements.define('light-entity-card', LightEntityCard); 636 | window.customCards = window.customCards || []; 637 | window.customCards.push({ 638 | type: 'light-entity-card', 639 | name: 'Light Entity Card', 640 | description: 'Control lights and switches', 641 | }); 642 | -------------------------------------------------------------------------------- /dist/light-entity-card.js: -------------------------------------------------------------------------------- 1 | !function(t){var e={};function i(n){if(e[n])return e[n].exports;var r=e[n]={i:n,l:!1,exports:{}};return t[n].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.m=t,i.c=e,i.d=function(t,e,n){i.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},i.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)i.d(n,r,function(e){return t[e]}.bind(null,r));return n},i.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return i.d(e,"a",e),e},i.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},i.p="/local/",i(i.s=1)}([function(t){t.exports=JSON.parse('{"name":"light-entity-card","version":"6.1.3","description":"A light-entity card for Home Assistant Lovelace UI","keywords":["home-assistant","homeassistant","hass","automation","lovelace","custom-cards","light-entity"],"repository":"git@github.com:ljmerza/light-entity-card.git","author":"Leonardo Merza ","license":"MIT","dependencies":{"@babel/polyfill":"^7.4.4","@jaames/iro":"^5.5.2","@lit-labs/scoped-registry-mixin":"^1.0.0","@material/mwc-icon":"^0.25.3","@material/mwc-list":"^0.25.3","@material/mwc-menu":"^0.25.3","@material/mwc-notched-outline":"^0.25.3","@material/mwc-select":"^0.25.3","core-js":"^2.6.5","lit":"^2.1.2","lit-element":"^2.2.1"},"devDependencies":{"@babel/cli":"^7.5.5","@babel/core":"^7.5.5","@babel/preset-env":"^7.5.5","babel-loader":"^8.0.6","eslint":"^6.1.0","webpack":"^4.38.0","webpack-cli":"^3.3.6","webpack-merge":"^4.2.1"},"scripts":{"lint":"eslint --fix ./src","start":"webpack --watch --config webpack/config.dev.js","build":"webpack --config webpack/config.prod.js"}}')},function(t,e,i){"use strict";i.r(e); 2 | /** 3 | * @license 4 | * Copyright 2019 Google LLC 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | const n=window,r=n.ShadowRoot&&(void 0===n.ShadyCSS||n.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,o=Symbol(),s=new WeakMap;class a{constructor(t,e,i){if(this._$cssResult$=!0,i!==o)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const e=this.t;if(r&&void 0===t){const i=void 0!==e&&1===e.length;i&&(t=s.get(e)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),i&&s.set(e,t))}return t}toString(){return this.cssText}}const l=(t,...e)=>{const i=1===t.length?t[0]:e.reduce((e,i,n)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[n+1],t[0]);return new a(i,t,o)},c=r?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new a("string"==typeof t?t:t+"",void 0,o))(e)})(t):t 8 | /** 9 | * @license 10 | * Copyright 2017 Google LLC 11 | * SPDX-License-Identifier: BSD-3-Clause 12 | */;var h;const u=window,d=u.trustedTypes,f=d?d.emptyScript:"",p=u.reactiveElementPolyfillSupport,g={toAttribute(t,e){switch(e){case Boolean:t=t?f:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},v=(t,e)=>e!==t&&(e==e||t==t),_={attribute:!0,type:String,converter:g,reflect:!1,hasChanged:v};class y extends HTMLElement{constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$El=null,this.u()}static addInitializer(t){var e;this.finalize(),(null!==(e=this.h)&&void 0!==e?e:this.h=[]).push(t)}static get observedAttributes(){this.finalize();const t=[];return this.elementProperties.forEach((e,i)=>{const n=this._$Ep(i,e);void 0!==n&&(this._$Ev.set(n,i),t.push(n))}),t}static createProperty(t,e=_){if(e.state&&(e.attribute=!1),this.finalize(),this.elementProperties.set(t,e),!e.noAccessor&&!this.prototype.hasOwnProperty(t)){const i="symbol"==typeof t?Symbol():"__"+t,n=this.getPropertyDescriptor(t,i,e);void 0!==n&&Object.defineProperty(this.prototype,t,n)}}static getPropertyDescriptor(t,e,i){return{get(){return this[e]},set(n){const r=this[t];this[e]=n,this.requestUpdate(t,r,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)||_}static finalize(){if(this.hasOwnProperty("finalized"))return!1;this.finalized=!0;const t=Object.getPrototypeOf(this);if(t.finalize(),void 0!==t.h&&(this.h=[...t.h]),this.elementProperties=new Map(t.elementProperties),this._$Ev=new Map,this.hasOwnProperty("properties")){const t=this.properties,e=[...Object.getOwnPropertyNames(t),...Object.getOwnPropertySymbols(t)];for(const i of e)this.createProperty(i,t[i])}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(c(t))}else void 0!==t&&e.push(c(t));return e}static _$Ep(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}u(){var t;this._$E_=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$Eg(),this.requestUpdate(),null===(t=this.constructor.h)||void 0===t||t.forEach(t=>t(this))}addController(t){var e,i;(null!==(e=this._$ES)&&void 0!==e?e:this._$ES=[]).push(t),void 0!==this.renderRoot&&this.isConnected&&(null===(i=t.hostConnected)||void 0===i||i.call(t))}removeController(t){var e;null===(e=this._$ES)||void 0===e||e.splice(this._$ES.indexOf(t)>>>0,1)}_$Eg(){this.constructor.elementProperties.forEach((t,e)=>{this.hasOwnProperty(e)&&(this._$Ei.set(e,this[e]),delete this[e])})}createRenderRoot(){var t;const e=null!==(t=this.shadowRoot)&&void 0!==t?t:this.attachShadow(this.constructor.shadowRootOptions);return((t,e)=>{r?t.adoptedStyleSheets=e.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet):e.forEach(e=>{const i=document.createElement("style"),r=n.litNonce;void 0!==r&&i.setAttribute("nonce",r),i.textContent=e.cssText,t.appendChild(i)})})(e,this.constructor.elementStyles),e}connectedCallback(){var t;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(t=this._$ES)||void 0===t||t.forEach(t=>{var e;return null===(e=t.hostConnected)||void 0===e?void 0:e.call(t)})}enableUpdating(t){}disconnectedCallback(){var t;null===(t=this._$ES)||void 0===t||t.forEach(t=>{var e;return null===(e=t.hostDisconnected)||void 0===e?void 0:e.call(t)})}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$EO(t,e,i=_){var n;const r=this.constructor._$Ep(t,i);if(void 0!==r&&!0===i.reflect){const o=(void 0!==(null===(n=i.converter)||void 0===n?void 0:n.toAttribute)?i.converter:g).toAttribute(e,i.type);this._$El=t,null==o?this.removeAttribute(r):this.setAttribute(r,o),this._$El=null}}_$AK(t,e){var i;const n=this.constructor,r=n._$Ev.get(t);if(void 0!==r&&this._$El!==r){const t=n.getPropertyOptions(r),o="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==(null===(i=t.converter)||void 0===i?void 0:i.fromAttribute)?t.converter:g;this._$El=r,this[r]=o.fromAttribute(e,t.type),this._$El=null}}requestUpdate(t,e,i){let n=!0;void 0!==t&&(((i=i||this.constructor.getPropertyOptions(t)).hasChanged||v)(this[t],e)?(this._$AL.has(t)||this._$AL.set(t,e),!0===i.reflect&&this._$El!==t&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(t,i))):n=!1),!this.isUpdatePending&&n&&(this._$E_=this._$Ej())}async _$Ej(){this.isUpdatePending=!0;try{await this._$E_}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var t;if(!this.isUpdatePending)return;this.hasUpdated,this._$Ei&&(this._$Ei.forEach((t,e)=>this[e]=t),this._$Ei=void 0);let e=!1;const i=this._$AL;try{e=this.shouldUpdate(i),e?(this.willUpdate(i),null===(t=this._$ES)||void 0===t||t.forEach(t=>{var e;return null===(e=t.hostUpdate)||void 0===e?void 0:e.call(t)}),this.update(i)):this._$Ek()}catch(t){throw e=!1,this._$Ek(),t}e&&this._$AE(i)}willUpdate(t){}_$AE(t){var e;null===(e=this._$ES)||void 0===e||e.forEach(t=>{var e;return null===(e=t.hostUpdated)||void 0===e?void 0:e.call(t)}),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$Ek(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$E_}shouldUpdate(t){return!0}update(t){void 0!==this._$EC&&(this._$EC.forEach((t,e)=>this._$EO(e,this[e],t)),this._$EC=void 0),this._$Ek()}updated(t){}firstUpdated(t){}}function b(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,"prototype",{writable:!1}),e&&m(t,e)}function m(t,e){return(m=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function w(t){var e=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(t){return!1}}();return function(){var i,n=k(t);if(e){var r=k(this).constructor;i=Reflect.construct(n,arguments,r)}else i=n.apply(this,arguments);return $(this,i)}}function $(t,e){if(e&&("object"===j(e)||"function"==typeof e))return e;if(void 0!==e)throw new TypeError("Derived constructors may only return object or undefined");return function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(t)}function k(t){return(k=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function x(t,e){var i="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(!i){if(Array.isArray(t)||(i=E(t))||e&&t&&"number"==typeof t.length){i&&(t=i);var n=0,r=function(){};return{s:r,n:function(){return n>=t.length?{done:!0}:{done:!1,value:t[n++]}},e:function(t){throw t},f:r}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,s=!0,a=!1;return{s:function(){i=i.call(t)},n:function(){var t=i.next();return s=t.done,t},e:function(t){a=!0,o=t},f:function(){try{s||null==i.return||i.return()}finally{if(a)throw o}}}}function S(t){return function(t){if(Array.isArray(t))return C(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||E(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function A(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var i=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null==i)return;var n,r,o=[],s=!0,a=!1;try{for(i=i.call(t);!(s=(n=i.next()).done)&&(o.push(n.value),!e||o.length!==e);s=!0);}catch(t){a=!0,r=t}finally{try{s||null==i.return||i.return()}finally{if(a)throw r}}return o}(t,e)||E(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function E(t,e){if(t){if("string"==typeof t)return C(t,e);var i=Object.prototype.toString.call(t).slice(8,-1);return"Object"===i&&t.constructor&&(i=t.constructor.name),"Map"===i||"Set"===i?Array.from(t):"Arguments"===i||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(i)?C(t,e):void 0}}function C(t,e){(null==e||e>t.length)&&(e=t.length);for(var i=0,n=new Array(e);i"),D=document,W=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";return D.createComment(t)},B=function(t){return null===t||"object"!=j(t)&&"function"!=typeof t},F=Array.isArray,z=function(t){return F(t)||"function"==typeof(null==t?void 0:t[Symbol.iterator])},V=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,q=/-->/g,K=/>/g,G=RegExp(">|[ \t\n\f\r](?:([^\\s\"'>=/]+)([ \t\n\f\r]*=[ \t\n\f\r]*(?:[^ \t\n\f\r\"'`<>=]|(\"|')|))|$)","g"),J=/'/g,Z=/"/g,X=/^(?:script|style|textarea|title)$/i,Y=function(t){return function(e){for(var i=arguments.length,n=new Array(i>1?i-1:0),r=1;r":"",s=V,a=0;a"===h[0]?(s=null!=i?i:V,u=-1):void 0===h[1]?u=-2:(u=s.lastIndex-h[2].length,c=h[1],s=void 0===h[3]?G:'"'===h[3]?Z:J):s===Z||s===J?s=G:s===q||s===K?s=V:(s=G,i=void 0);var f=s===G&&t[a+1].startsWith("/>")?" ":"";o+=s===V?l+L:u>=0?(r.push(c),l.slice(0,u)+"$lit$"+l.slice(u)+N+f):l+N+(-2===u?(r.push(void 0),a):f)}var p=o+(t[n]||"")+(2===e?"":"");if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return[void 0!==H?H.createHTML(p):p,r]},ot=function(){function t(e,i){var n,r=e.strings,o=e._$litType$;O(this,t),this.parts=[];var s=0,a=0,l=r.length-1,c=this.parts,h=A(rt(r,o),2),u=h[0],d=h[1];if(this.el=t.createElement(u,i),nt.currentNode=this.el.content,2===o){var f=this.el.content,p=f.firstChild;p.remove(),f.append.apply(f,S(p.childNodes))}for(;null!==(n=nt.nextNode())&&c.length0){n.textContent=M?M.emptyScript:"";for(var T=0;T2&&void 0!==arguments[2]?arguments[2]:t,a=arguments.length>3?arguments[3]:void 0;if(e===tt)return e;var l=void 0!==a?null===(i=s._$Co)||void 0===i?void 0:i[a]:s._$Cl,c=B(e)?void 0:e._$litDirective$;return(null==l?void 0:l.constructor)!==c&&(null===(n=null==l?void 0:l._$AO)||void 0===n||n.call(l,!1),void 0===c?l=void 0:(l=new c(t))._$AT(t,s,a),void 0!==a?(null!==(r=(o=s)._$Co)&&void 0!==r?r:o._$Co=[])[a]=l:s._$Cl=l),void 0!==l&&(e=st(t,l._$AS(t,e.values),l,a)),e}var at=function(){function t(e,i){O(this,t),this.u=[],this._$AN=void 0,this._$AD=e,this._$AM=i}return T(t,[{key:"parentNode",get:function(){return this._$AM.parentNode}},{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"v",value:function(t){var e,i=this._$AD,n=i.el.content,r=i.parts,o=(null!==(e=null==t?void 0:t.creationScope)&&void 0!==e?e:D).importNode(n,!0);nt.currentNode=o;for(var s=nt.nextNode(),a=0,l=0,c=r[0];void 0!==c;){if(a===c.index){var h=void 0;2===c.type?h=new lt(s,s.nextSibling,this,t):1===c.type?h=new c.ctor(s,c.name,c.strings,this,t):6===c.type&&(h=new pt(s,this,t)),this.u.push(h),c=r[++l]}a!==(null==c?void 0:c.index)&&(s=nt.nextNode(),a++)}return o}},{key:"p",value:function(t){var e,i=0,n=x(this.u);try{for(n.s();!(e=n.n()).done;){var r=e.value;void 0!==r&&(void 0!==r.strings?(r._$AI(t,r,i),i+=r.strings.length-2):r._$AI(t[i])),i++}}catch(t){n.e(t)}finally{n.f()}}}]),t}(),lt=function(){function t(e,i,n,r){var o;O(this,t),this.type=2,this._$AH=et,this._$AN=void 0,this._$AA=e,this._$AB=i,this._$AM=n,this.options=r,this._$Cm=null===(o=null==r?void 0:r.isConnected)||void 0===o||o}return T(t,[{key:"_$AU",get:function(){var t,e;return null!==(e=null===(t=this._$AM)||void 0===t?void 0:t._$AU)&&void 0!==e?e:this._$Cm}},{key:"parentNode",get:function(){var t=this._$AA.parentNode,e=this._$AM;return void 0!==e&&11===t.nodeType&&(t=e.parentNode),t}},{key:"startNode",get:function(){return this._$AA}},{key:"endNode",get:function(){return this._$AB}},{key:"_$AI",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this;t=st(this,t,e),B(t)?t===et||null==t||""===t?(this._$AH!==et&&this._$AR(),this._$AH=et):t!==this._$AH&&t!==tt&&this.g(t):void 0!==t._$litType$?this.$(t):void 0!==t.nodeType?this.T(t):z(t)?this.k(t):this.g(t)}},{key:"O",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this._$AB;return this._$AA.parentNode.insertBefore(t,e)}},{key:"T",value:function(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}},{key:"g",value:function(t){this._$AH!==et&&B(this._$AH)?this._$AA.nextSibling.data=t:this.T(D.createTextNode(t)),this._$AH=t}},{key:"$",value:function(t){var e,i=t.values,n=t._$litType$,r="number"==typeof n?this._$AC(t):(void 0===n.el&&(n.el=ot.createElement(n.h,this.options)),n);if((null===(e=this._$AH)||void 0===e?void 0:e._$AD)===r)this._$AH.p(i);else{var o=new at(r,this),s=o.v(this.options);o.p(i),this.T(s),this._$AH=o}}},{key:"_$AC",value:function(t){var e=it.get(t.strings);return void 0===e&&it.set(t.strings,e=new ot(t)),e}},{key:"k",value:function(e){F(this._$AH)||(this._$AH=[],this._$AR());var i,n,r=this._$AH,o=0,s=x(e);try{for(s.s();!(n=s.n()).done;){var a=n.value;o===r.length?r.push(i=new t(this.O(W()),this.O(W()),this,this.options)):i=r[o],i._$AI(a),o++}}catch(t){s.e(t)}finally{s.f()}o0&&void 0!==arguments[0]?arguments[0]:this._$AA.nextSibling,i=arguments.length>1?arguments[1]:void 0;for(null===(t=this._$AP)||void 0===t||t.call(this,!1,!0,i);e&&e!==this._$AB;){var n=e.nextSibling;e.remove(),e=n}}},{key:"setConnected",value:function(t){var e;void 0===this._$AM&&(this._$Cm=t,null===(e=this._$AP)||void 0===e||e.call(this,t))}}]),t}(),ct=function(){function t(e,i,n,r,o){O(this,t),this.type=1,this._$AH=et,this._$AN=void 0,this.element=e,this.name=i,this._$AM=r,this.options=o,n.length>2||""!==n[0]||""!==n[1]?(this._$AH=Array(n.length-1).fill(new String),this.strings=n):this._$AH=et}return T(t,[{key:"tagName",get:function(){return this.element.tagName}},{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"_$AI",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this,i=arguments.length>2?arguments[2]:void 0,n=arguments.length>3?arguments[3]:void 0,r=this.strings,o=!1;if(void 0===r)t=st(this,t,e,0),(o=!B(t)||t!==this._$AH&&t!==tt)&&(this._$AH=t);else{var s,a,l=t;for(t=r[0],s=0;s1&&void 0!==arguments[1]?arguments[1]:this;if((t=null!==(e=st(this,t,i,0))&&void 0!==e?e:et)!==tt){var n=this._$AH,r=t===et&&n!==et||t.capture!==n.capture||t.once!==n.once||t.passive!==n.passive,o=t!==et&&(n===et||r);r&&this.element.removeEventListener(this.name,this,n),o&&this.element.addEventListener(this.name,this,t),this._$AH=t}}},{key:"handleEvent",value:function(t){var e,i;"function"==typeof this._$AH?this._$AH.call(null!==(i=null===(e=this.options)||void 0===e?void 0:e.host)&&void 0!==i?i:this.element,t):this._$AH.handleEvent(t)}}]),i}(ct),pt=function(){function t(e,i,n){O(this,t),this.element=e,this.type=6,this._$AN=void 0,this._$AM=i,this.options=n}return T(t,[{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"_$AI",value:function(t){st(this,t)}}]),t}(),gt=I.litHtmlPolyfillSupport;null==gt||gt(ot,lt),(null!==(R=I.litHtmlVersions)&&void 0!==R?R:I.litHtmlVersions=[]).push("2.6.1");var vt,_t;function yt(t){return(yt="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function bt(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function mt(t,e){for(var i=0;i3)for(i=[i],n=3;n-1,n=parseFloat(t);return i?e/100*n:n}function $e(t){return parseInt(t,16)}function ke(t){return t.toString(16).padStart(2,"0")}var xe=function(){function t(t,e){this.$={h:0,s:0,v:0,a:1},t&&this.set(t),this.onChange=e,this.initialValue=oe({},this.$)}var e,i,n,r=t.prototype;return r.set=function(e){if("string"==typeof e)/^(?:#?|0x?)[0-9a-fA-F]{3,8}$/.test(e)?this.hexString=e:/^rgba?/.test(e)?this.rgbString=e:/^hsla?/.test(e)&&(this.hslString=e);else{if("object"!=typeof e)throw new Error("Invalid color value");e instanceof t?this.hsva=e.hsva:"r"in e&&"g"in e&&"b"in e?this.rgb=e:"h"in e&&"s"in e&&"v"in e?this.hsv=e:"h"in e&&"s"in e&&"l"in e?this.hsl=e:"kelvin"in e&&(this.kelvin=e.kelvin)}},r.setChannel=function(t,e,i){var n;this[t]=oe({},this[t],((n={})[e]=i,n))},r.reset=function(){this.hsva=this.initialValue},r.clone=function(){return new t(this)},r.unbind=function(){this.onChange=void 0},t.hsvToRgb=function(t){var e=t.h/60,i=t.s/100,n=t.v/100,r=be(e),o=e-r,s=n*(1-i),a=n*(1-o*i),l=n*(1-(1-o)*i),c=r%6,h=[l,n,n,a,s,s][c],u=[s,s,l,n,n,a][c];return{r:me(255*[n,a,s,s,l,n][c],0,255),g:me(255*h,0,255),b:me(255*u,0,255)}},t.rgbToHsv=function(t){var e=t.r/255,i=t.g/255,n=t.b/255,r=Math.max(e,i,n),o=Math.min(e,i,n),s=r-o,a=0,l=r,c=0===r?0:s/r;switch(r){case o:a=0;break;case e:a=(i-n)/s+(i.4;){i=.5*(s+o);var a=t.kelvinToRgb(i);a.b/a.r>=r/n?s=i:o=i}return i},e=t,(i=[{key:"hsv",get:function(){var t=this.$;return{h:t.h,s:t.s,v:t.v}},set:function(t){var e=this.$;if(t=oe({},e,t),this.onChange){var i={h:!1,v:!1,s:!1,a:!1};for(var n in e)i[n]=t[n]!=e[n];this.$=t,(i.h||i.s||i.v||i.a)&&this.onChange(this,i)}else this.$=t}},{key:"hsva",get:function(){return oe({},this.$)},set:function(t){this.hsv=t}},{key:"hue",get:function(){return this.$.h},set:function(t){this.hsv={h:t}}},{key:"saturation",get:function(){return this.$.s},set:function(t){this.hsv={s:t}}},{key:"value",get:function(){return this.$.v},set:function(t){this.hsv={v:t}}},{key:"alpha",get:function(){return this.$.a},set:function(t){this.hsv=oe({},this.hsv,{a:t})}},{key:"kelvin",get:function(){return t.rgbToKelvin(this.rgb)},set:function(e){this.rgb=t.kelvinToRgb(e)}},{key:"red",get:function(){return this.rgb.r},set:function(t){this.rgb=oe({},this.rgb,{r:t})}},{key:"green",get:function(){return this.rgb.g},set:function(t){this.rgb=oe({},this.rgb,{g:t})}},{key:"blue",get:function(){return this.rgb.b},set:function(t){this.rgb=oe({},this.rgb,{b:t})}},{key:"rgb",get:function(){var e=t.hsvToRgb(this.$),i=e.r,n=e.g,r=e.b;return{r:ye(i),g:ye(n),b:ye(r)}},set:function(e){this.hsv=oe({},t.rgbToHsv(e),{a:void 0===e.a?1:e.a})}},{key:"rgba",get:function(){return oe({},this.rgb,{a:this.alpha})},set:function(t){this.rgb=t}},{key:"hsl",get:function(){var e=t.hsvToHsl(this.$),i=e.h,n=e.s,r=e.l;return{h:ye(i),s:ye(n),l:ye(r)}},set:function(e){this.hsv=oe({},t.hslToHsv(e),{a:void 0===e.a?1:e.a})}},{key:"hsla",get:function(){return oe({},this.hsl,{a:this.alpha})},set:function(t){this.hsl=t}},{key:"rgbString",get:function(){var t=this.rgb;return"rgb("+t.r+", "+t.g+", "+t.b+")"},set:function(t){var e,i,n,r,o=1;if((e=ce.exec(t))?(i=we(e[1],255),n=we(e[2],255),r=we(e[3],255)):(e=he.exec(t))&&(i=we(e[1],255),n=we(e[2],255),r=we(e[3],255),o=we(e[4],1)),!e)throw new Error("Invalid rgb string");this.rgb={r:i,g:n,b:r,a:o}}},{key:"rgbaString",get:function(){var t=this.rgba;return"rgba("+t.r+", "+t.g+", "+t.b+", "+t.a+")"},set:function(t){this.rgbString=t}},{key:"hexString",get:function(){var t=this.rgb;return"#"+ke(t.r)+ke(t.g)+ke(t.b)},set:function(t){var e,i,n,r,o=255;if((e=fe.exec(t))?(i=17*$e(e[1]),n=17*$e(e[2]),r=17*$e(e[3])):(e=pe.exec(t))?(i=17*$e(e[1]),n=17*$e(e[2]),r=17*$e(e[3]),o=17*$e(e[4])):(e=ge.exec(t))?(i=$e(e[1]),n=$e(e[2]),r=$e(e[3])):(e=ve.exec(t))&&(i=$e(e[1]),n=$e(e[2]),r=$e(e[3]),o=$e(e[4])),!e)throw new Error("Invalid hex string");this.rgb={r:i,g:n,b:r,a:o/255}}},{key:"hex8String",get:function(){var t=this.rgba;return"#"+ke(t.r)+ke(t.g)+ke(t.b)+ke(be(255*t.a))},set:function(t){this.hexString=t}},{key:"hslString",get:function(){var t=this.hsl;return"hsl("+t.h+", "+t.s+"%, "+t.l+"%)"},set:function(t){var e,i,n,r,o=1;if((e=ue.exec(t))?(i=we(e[1],360),n=we(e[2],100),r=we(e[3],100)):(e=de.exec(t))&&(i=we(e[1],360),n=we(e[2],100),r=we(e[3],100),o=we(e[4],1)),!e)throw new Error("Invalid hsl string");this.hsl={h:i,s:n,l:r,a:o}}},{key:"hslaString",get:function(){var t=this.hsla;return"hsla("+t.h+", "+t.s+"%, "+t.l+"%, "+t.a+")"},set:function(t){this.hslString=t}}])&&re(e.prototype,i),n&&re(e,n),t}();function Se(t){var e,i=t.width,n=t.sliderSize,r=t.borderWidth,o=t.handleRadius,s=t.padding,a=t.sliderShape,l="horizontal"===t.layoutDirection;return n=null!=(e=n)?e:2*s+2*o,"circle"===a?{handleStart:t.padding+t.handleRadius,handleRange:i-2*s-2*o,width:i,height:i,cx:i/2,cy:i/2,radius:i/2-r/2}:{handleStart:n/2,handleRange:i-n,radius:n/2,x:0,y:0,width:l?n:i,height:l?i:n}}function Ae(t,e){var i=Se(t),n=i.width,r=i.height,o=i.handleRange,s=i.handleStart,a="horizontal"===t.layoutDirection,l=a?n/2:r/2,c=s+function(t,e){var i=e.hsva,n=e.rgb;switch(t.sliderType){case"red":return n.r/2.55;case"green":return n.g/2.55;case"blue":return n.b/2.55;case"alpha":return 100*i.a;case"kelvin":var r=t.minTemperature,o=t.maxTemperature-r,s=(e.kelvin-r)/o*100;return Math.max(0,Math.min(s,100));case"hue":return i.h/=3.6;case"saturation":return i.s;case"value":default:return i.v}}(t,e)/100*o;return a&&(c=-1*c+o+2*s),{x:a?l:c,y:a?c:l}}var Ee,Ce=2*Math.PI,Oe=function(t,e){return Math.sqrt(t*t+e*e)};function Pe(t){return t.width/2-t.padding-t.handleRadius-t.borderWidth}function Te(t){var e=t.width/2;return{width:t.width,radius:e-t.borderWidth,cx:e,cy:e}}function je(t,e,i){var n=t.wheelAngle,r=t.wheelDirection;return i&&"clockwise"===r?e=n+e:"clockwise"===r?e=360-n+e:i&&"anticlockwise"===r?e=n+180-e:"anticlockwise"===r&&(e=n-e),function(t,e){return(t%e+e)%e}(e,360)}function Re(t,e,i){var n=Te(t),r=n.cx,o=n.cy,s=Pe(t);e=r-e,i=o-i;var a=je(t,Math.atan2(-i,-e)*(360/Ce)),l=Math.min(Oe(e,i),s);return{h:Math.round(a),s:Math.round(100/s*l)}}function Ie(t){var e=t.width,i=t.boxHeight;return{width:e,height:null!=i?i:e,radius:t.padding+t.handleRadius}}function Me(t,e,i){var n=Ie(t),r=n.width,o=n.height,s=n.radius,a=(e-s)/(r-2*s)*100,l=(i-s)/(o-2*s)*100;return{s:Math.max(0,Math.min(a,100)),v:Math.max(0,Math.min(100-l,100))}}function He(t){Ee||(Ee=document.getElementsByTagName("base"));var e=window.navigator.userAgent,i=/^((?!chrome|android).)*safari/i.test(e),n=/iPhone|iPod|iPad/i.test(e),r=window.location;return(i||n)&&Ee.length>0?r.protocol+"//"+r.host+r.pathname+r.search+t:t}function Ne(t,e,i,n){for(var r=0;r0&&(o[n?"marginLeft":"marginTop"]=r),Dt(Bt,null,t.children(this.uid,i,o))},e.prototype.handleEvent=function(t){var e=this,i=this.props.onInput,n=this.base.getBoundingClientRect();t.preventDefault();var r=t.touches?t.changedTouches[0]:t,o=r.clientX-n.left,s=r.clientY-n.top;switch(t.type){case"mousedown":case"touchstart":!1!==i(o,s,0)&&We.forEach((function(t){document.addEventListener(t,e,{passive:!1})}));break;case"mousemove":case"touchmove":i(o,s,1);break;case"mouseup":case"touchend":i(o,s,2),We.forEach((function(t){document.removeEventListener(t,e,{passive:!1})}))}},e}(Ft);function Fe(t){var e=t.r,i=t.url,n=e,r=e;return Dt("svg",{className:"IroHandle IroHandle--"+t.index+" "+(t.isActive?"IroHandle--isActive":""),style:{"-webkit-tap-highlight-color":"rgba(0, 0, 0, 0);",transform:"translate("+De(t.x)+", "+De(t.y)+")",willChange:"transform",top:De(-e),left:De(-e),width:De(2*e),height:De(2*e),position:"absolute",overflow:"visible"}},i&&Dt("use",Object.assign({xlinkHref:He(i)},t.props)),!i&&Dt("circle",{cx:n,cy:r,r:e,fill:"none","stroke-width":2,stroke:"#000"}),!i&&Dt("circle",{cx:n,cy:r,r:e-2,fill:t.fill,"stroke-width":2,stroke:"#fff"}))}function ze(t){var e=t.activeIndex,i=void 0!==e&&e0?e.colors:[e.color]).forEach((function(t){return i.addColor(t)})),this.setActiveColor(0),this.state=Object.assign({},e,{color:this.color,colors:this.colors,layout:e.layout})}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.addColor=function(t,e){void 0===e&&(e=this.colors.length);var i=new xe(t,this.onColorChange.bind(this));this.colors.splice(e,0,i),this.colors.forEach((function(t,e){return t.index=e})),this.state&&this.setState({colors:this.colors}),this.deferredEmit("color:init",i)},e.prototype.removeColor=function(t){var e=this.colors.splice(t,1)[0];e.unbind(),this.colors.forEach((function(t,e){return t.index=e})),this.state&&this.setState({colors:this.colors}),e.index===this.color.index&&this.setActiveColor(0),this.emit("color:remove",e)},e.prototype.setActiveColor=function(t){this.color=this.colors[t],this.state&&this.setState({color:this.color}),this.emit("color:setActive",this.color)},e.prototype.setColors=function(t,e){var i=this;void 0===e&&(e=0),this.colors.forEach((function(t){return t.unbind()})),this.colors=[],t.forEach((function(t){return i.addColor(t)})),this.setActiveColor(e),this.emit("color:setAll",this.colors)},e.prototype.on=function(t,e){var i=this,n=this.events;(Array.isArray(t)?t:[t]).forEach((function(t){(n[t]||(n[t]=[])).push(e),i.deferredEvents[t]&&(i.deferredEvents[t].forEach((function(t){e.apply(null,t)})),i.deferredEvents[t]=[])}))},e.prototype.off=function(t,e){var i=this;(Array.isArray(t)?t:[t]).forEach((function(t){var n=i.events[t];n&&n.splice(n.indexOf(e),1)}))},e.prototype.emit=function(t){for(var e=this,i=[],n=arguments.length-1;n-- >0;)i[n]=arguments[n+1];var r=this.activeEvents,o=!!r.hasOwnProperty(t)&&r[t];if(!o){r[t]=!0;var s=this.events[t]||[];s.forEach((function(t){return t.apply(e,i)})),r[t]=!1}},e.prototype.deferredEmit=function(t){for(var e,i=[],n=arguments.length-1;n-- >0;)i[n]=arguments[n+1];var r=this.deferredEvents;(e=this).emit.apply(e,[t].concat(i)),(r[t]||(r[t]=[])).push(i)},e.prototype.setOptions=function(t){this.setState(t)},e.prototype.resize=function(t){this.setOptions({width:t})},e.prototype.reset=function(){this.colors.forEach((function(t){return t.reset()})),this.setState({colors:this.colors})},e.prototype.onMount=function(t){this.el=t,this.deferredEmit("mount",this)},e.prototype.onColorChange=function(t,e){this.setState({color:this.color}),this.inputActive&&(this.inputActive=!1,this.emit("input:change",t,e)),this.emit("color:change",t,e)},e.prototype.emitInputEvent=function(t,e){0===t?this.emit("input:start",this.color,e):1===t?this.emit("input:move",this.color,e):2===t&&this.emit("input:end",this.color,e)},e.prototype.render=function(t,e){var i=this,n=e.layout;return Array.isArray(n)||(n=[{component:qe},{component:ze}],e.transparency&&n.push({component:ze,options:{sliderType:"alpha"}})),Dt("div",{class:"IroColorPicker",id:e.id,style:{display:e.display}},n.map((function(t,n){var r=t.component,o=t.options;return Dt(r,Object.assign({},e,o,{ref:void 0,onInput:i.emitInputEvent.bind(i),parent:i,index:n}))})))},e}(Ft);Ke.defaultProps=Object.assign({},{width:300,height:300,color:"#fff",colors:[],padding:6,layoutDirection:"vertical",borderColor:"#fff",borderWidth:0,handleRadius:8,activeHandleRadius:null,handleSvg:null,handleProps:{x:0,y:0},wheelLightness:!0,wheelAngle:0,wheelDirection:"anticlockwise",sliderSize:null,sliderMargin:12,boxHeight:null},{colors:[],display:"block",id:null,layout:"default",margin:null});var Ge,Je,Ze,Xe=((Je=function(t,e){var i,n=document.createElement("div");function r(){var e=t instanceof Element?t:document.querySelector(t);e.appendChild(i.base),i.onMount(e)}return function(t,e,i){var n,r,o;Pt.__p&&Pt.__p(t,e),r=(n=i===It)?null:i&&i.__k||e.__k,t=Dt(Bt,null,[t]),o=[],Qt(e,n?e.__k=t:(i||e).__k=t,r||Mt,Mt,void 0!==e.ownerSVGElement,i&&!n?[i]:r?null:Ht.slice.call(e.childNodes),o,!1,i||Mt,n),te(o,t)}(Dt(Ge,Object.assign({},{ref:function(t){return i=t}},e)),n),"loading"!==document.readyState?r():document.addEventListener("DOMContentLoaded",r),i}).prototype=(Ge=Ke).prototype,Object.assign(Je,Ge),Je.__component=Ge,Je);!function(t){t.version="5.5.2",t.Color=xe,t.ColorPicker=Xe,function(t){t.h=Dt,t.ComponentBase=Be,t.Handle=Fe,t.Slider=ze,t.Wheel=qe,t.Box=Ve}(t.ui||(t.ui={}))}(Ze||(Ze={}));var Ye=Ze; 35 | /** 36 | * @license 37 | * Copyright 2019 Google LLC 38 | * SPDX-License-Identifier: BSD-3-Clause 39 | */ 40 | const Qe=window,ti=Qe.ShadowRoot&&(void 0===Qe.ShadyCSS||Qe.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype;Symbol(),new WeakMap; 41 | /** 42 | * @license 43 | * Copyright 2021 Google LLC 44 | * SPDX-License-Identifier: BSD-3-Clause 45 | */ 46 | function ei(t){return class extends t{createRenderRoot(){const t=this.constructor,{registry:e,elementDefinitions:i,shadowRootOptions:n}=t;i&&!e&&(t.registry=new CustomElementRegistry,Object.entries(i).forEach(([e,i])=>t.registry.define(e,i)));const r=this.renderOptions.creationScope=this.attachShadow({...n,customElements:t.registry});return((t,e)=>{ti?t.adoptedStyleSheets=e.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet):e.forEach(e=>{const i=document.createElement("style"),n=Qe.litNonce;void 0!==n&&i.setAttribute("nonce",n),i.textContent=e.cssText,t.appendChild(i)})})(r,this.constructor.elementStyles),r}}}var ii=l` 47 | .IroSlider { 48 | display: none !important; 49 | } 50 | 51 | .light-entity-card { 52 | padding: 16px; 53 | } 54 | 55 | .light-entity-child-card { 56 | box-shadow: none !important; 57 | padding: 0 !important; 58 | } 59 | 60 | .light-entity-card.group { 61 | padding-bottom: 5; 62 | padding-top: 0; 63 | } 64 | 65 | .ha-slider-full-width ha-slider { 66 | width: 100%; 67 | } 68 | 69 | .percent-slider { 70 | color: var(--primary-text-color); 71 | display: flex; 72 | justify-content: center; 73 | align-items: center; 74 | } 75 | 76 | .light-entity-card__header { 77 | display: flex; 78 | justify-content: space-between; 79 | @apply --paper-font-headline; 80 | line-height: 40px; 81 | color: var(--primary-text-color); 82 | } 83 | 84 | .group .light-entity-card__header { 85 | } 86 | 87 | .light-entity-card-sliders > div { 88 | margin-top: 10px; 89 | } 90 | 91 | .group .light-entity-card-sliders > div { 92 | margin-top: 0px; 93 | } 94 | 95 | .light-entity-card__toggle { 96 | display: flex; 97 | cursor: pointer; 98 | } 99 | 100 | .light-entity-card__color-picker { 101 | display: flex; 102 | justify-content: space-around; 103 | margin-top: 10px; 104 | } 105 | 106 | .light-entity-card-color_temp { 107 | background-image: var(--ha-slider-background); 108 | } 109 | 110 | .light-entity-card-effectlist { 111 | padding-top: 10px; 112 | padding-bottom: 10px; 113 | } 114 | 115 | .group .light-entity-card-effectlist { 116 | padding-bottom: 20px; 117 | } 118 | 119 | .light-entity-card-center { 120 | display: flex; 121 | justify-content: center; 122 | cursor: pointer; 123 | } 124 | 125 | .hidden { 126 | display: none; 127 | } 128 | 129 | .icon-container { 130 | display: flex; 131 | justify-content: center; 132 | align-items: center; 133 | } 134 | `,ni={shorten_cards:!1,consolidate_entities:!1,child_card:!1,hide_header:!1,show_header_icon:!1,header:"",color_wheel:!0,persist_features:!1,brightness:!0,color_temp:!0,white_value:!0,color_picker:!0,speed:!0,intensity:!0,force_features:!1,show_slider_percent:!1,full_width_sliders:!1,brightness_icon:"weather-sunny",white_icon:"file-word-box",temperature_icon:"thermometer",speed_icon:"speedometer",intensity_icon:"transit-connection-horizontal"};var ri=l` 135 | .entities { 136 | padding-top: 10px; 137 | padding-bottom: 10px; 138 | display: flex; 139 | } 140 | 141 | .entities ha-formfield { 142 | display: block; 143 | margin-bottom: 10px; 144 | margin-left: 10px; 145 | } 146 | 147 | .checkbox-options { 148 | display: flex; 149 | } 150 | 151 | mwc-select { 152 | width: 100%; 153 | } 154 | 155 | .checkbox-options ha-formfield, 156 | .entities mwc-switch, 157 | .entities ha-form-string { 158 | padding-right: 2%; 159 | width: 48%; 160 | } 161 | 162 | .checkbox-options ha-formfield { 163 | margin-top: 10px; 164 | } 165 | 166 | .overall-config { 167 | margin-bottom: 20px; 168 | } 169 | `;var oi=(t,e)=>t.reduce((t,i)=>(i.defineId?t[i.defineId]=i:i.promise.then(t=>{void 0===e.registry.get(i.name)&&e.registry.define(i.name,t)}),t),{});var si=t=>({name:t,promise:customElements.whenDefined(t).then(()=>customElements.get(t))});const ai=(t,e,i={},n={})=>{const r=new Event(e,{bubbles:void 0===n.bubbles||n.bubbles,cancelable:Boolean(n.cancelable),composed:void 0===n.composed||n.composed});return r.detail=i,t.dispatchEvent(r),r};class li extends(ei(Ct)){static get elementDefinitions(){return oi([si("ha-checkbox"),si("ha-formfield"),si("ha-form-string"),si("ha-select"),si("mwc-list-item")],li)}static get styles(){return ri}static get properties(){return{hass:{},_config:{}}}setConfig(t){this._config={...ni,...t}}get entityOptions(){const t=Object.keys(this.hass.states).filter(t=>["switch","light","group"].includes(t.substr(0,t.indexOf("."))));return t.sort(),t}firstUpdated(){this._firstRendered=!0}render(){if(!this.hass)return Q``;let{header:t}=this._config;if(!t&&this._config.entity){let e=this._config.entity.split(".")[1]||"";e&&(e=e.charAt(0).toUpperCase()+e.slice(1),t=e)}const e=this.entityOptions.map(t=>Q`${t}`);return Q` 170 |
171 | 172 |
173 | 180 |
181 | 182 |
183 | 189 | ${e} 190 | 191 | 198 |
199 | 200 |
201 | 208 | 215 |
216 | 217 |
218 |
219 | 220 | 225 | 226 | 227 | 232 | 233 |
234 | 235 |
236 | 237 | 242 | 243 | 244 | 249 | 250 |
251 | 252 |
253 | 254 | 259 | 260 | 261 | 266 | 267 |
268 | 269 |
270 | 271 | 276 | 277 | 278 | 283 | 284 |
285 | 286 |
287 | 288 | 293 | 294 | 295 | 300 | 301 |
302 | 303 |
304 | 305 | 310 | 311 | 312 | 317 | 318 |
319 | 320 |
321 | 322 | 327 | 328 | 329 | 334 | 335 | 336 | 341 | 342 |
343 | 344 |
345 | 346 | 351 | 352 |
353 | 354 |
355 | 356 | 361 | 362 |
363 |
364 |
365 | `}configChanged(t){if(!this._config||!this.hass||!this._firstRendered)return;const{target:{configValue:e,value:i},detail:{value:n}}=t;this._config=null!=n?{...this._config,[e]:n}:{...this._config,[e]:i},ai(this,"config-changed",{config:this._config})}checkboxConfigChanged(t){if(!this._config||!this.hass||!this._firstRendered)return;const{target:{value:e,checked:i}}=t;this._config={...this._config,[e]:i},ai(this,"config-changed",{config:this._config})}}var ci=i(0);customElements.define("light-entity-card-editor",li),console.info("light-entity-card v"+ci.version);class hi extends(ei(Ct)){static get elementDefinitions(){return oi([si("ha-card"),si("more-info-light"),si("ha-switch"),si("ha-icon"),si("state-badge"),si("ha-slider"),si("ha-color-picker"),si("ha-select"),si("mwc-list-item")],hi)}static get properties(){return{hass:{},config:{}}}async firstUpdated(){this.setColorWheels(),this._firstUpdate=!0}async updated(){this.setColorWheels()}setColorWheels(){if(!this._shownStateObjects)return;const t=this.getColorPickerWidth();for(const e of this._shownStateObjects){const i=this.renderRoot.getElementById("picker-"+e.entity_id);if(!i)continue;i.innerHTML="";let n={h:0,s:0,l:50};if(e.attributes.hs_color){n={h:parseInt(e.attributes.hs_color[0]),s:parseInt(e.attributes.hs_color[1]),l:50}}new Ye.ColorPicker(i,{sliderSize:0,color:n,width:t,wheelLightness:!1}).on("input:end",t=>this.setColorPicker(t.hsl,e))}}getColorPickerWidth(){const t=this.shadowRoot.querySelector(".light-entity-card").offsetWidth,e=this.config.shorten_cards,i=t-(e?100:50),n=e?200:300;return n>i?i:n}setConfig(t){if(!t.entity)throw Error("entity required.");this.config={...ni,...t}}static async getConfigElement(){return document.createElement("light-entity-card-editor")}static get featureNames(){return{brightness:1,colorTemp:2,effectList:4,color:16,whiteValue:128}}static get cmdToggle(){return{on:"turn_on",off:"turn_off"}}static get entityLength(){return{light:10,switch:1}}getCardSize(){if(!this.config||!this.__hass||!this.__hass.states[this.config.entity])return 1;let t=0;const e=this.__hass.states[this.config.entity];return Array.isArray(e.attributes.entity_id)?e.attributes.entity_id.forEach(e=>t+=this.getEntityLength(e)):t+=this.getEntityLength(e.attributes.entity_id),this.config.group&&(t*=.8),parseInt(t,1)}getEntityLength(t){return/^light\./.test(t)?hi.entityLength.light:/^switch\./.test(t)?hi.entityLength.switch:0}get styles(){return ii}get language(){return this.__hass.resources[this.__hass.language]}isEntityOn(t){return"on"===t.state}render(){const t=this.hass.states[this.config.entity];if(!t)return Q` 366 | 369 | ${"Invalid entity: "+this.config.entity} 370 | `;this._stateObjects=this.getEntitiesToShow(t),this.config.consolidate_entities?this._shownStateObjects=[t]:this._shownStateObjects=[...this._stateObjects];const e=this._shownStateObjects.reduce((t,e)=>Q`${t}${this.createEntityTemplate(e)}`,""),i=`light-entity-card ${this.config.shorten_cards?" group":""} ${this.config.child_card?" light-entity-child-card":""}`;return setTimeout(()=>{this.setColorWheels()},100),Q` 371 | 374 | 375 | ${e} 376 | 377 | `}getEntitiesToShow(t){return t.attributes.entity_id&&Array.isArray(t.attributes.entity_id)?t.attributes.entity_id.map(t=>this.hass.states[t]).filter(Boolean):[t]}createEntityTemplate(t){const e=this.config.full_width_sliders?"ha-slider-full-width":"";return Q` 378 | ${this.createHeader(t)} 379 |
380 | ${this.createBrightnessSlider(t)} ${this.createSpeedSlider(t)} 381 | ${this.createIntensitySlider(t)} ${this.createColorTemperature(t)} 382 | ${this.createWhiteValue(t)} 383 |
384 | ${this.createColorPicker(t)} ${this.createEffectList(t)} 385 | `}createHeader(t){if(this.config.hide_header)return Q``;const e=this.config.header||t.attributes.friendly_name||t.entity_id;return Q` 386 |
387 | ${this.showHeaderIcon(t)} 388 |
${e}
389 |
390 | this.setToggle(e,t)}> 391 |
392 |
393 | `}showHeaderIcon(t){return this.config.show_header_icon?Q` 394 |
395 | 396 |
397 | `:Q``}createBrightnessSlider(t){return!1===this.config.brightness||this.dontShowFeature("brightness",t)?Q``:Q` 398 |
399 |
400 | 401 |
402 | 408 | ${this.showPercent(t.attributes.brightness,0,254)} 409 |
410 | `}createSpeedSlider(t){return!1===this.config.speed||this.dontShowFeature("speed",t)?Q``:Q` 411 |
412 |
413 | 414 |
415 | 421 | ${this.showPercent(t.attributes.speed,0,254)} 422 |
423 | `}createIntensitySlider(t){return!1===this.config.speed||this.dontShowFeature("intensity",t)?Q``:Q` 424 |
425 |
426 | 427 |
428 | 434 | ${this.showPercent(t.attributes.intensity,0,254)} 435 |
436 | `}showPercent(t,e,i){if(!this.config.show_slider_percent)return Q``;let n=parseInt(100*(t-e)/(i-e),0);return isNaN(n)&&(n=0),Q`
${n}%
`}createColorTemperature(t){if(!1===this.config.color_temp)return Q``;if(this.dontShowFeature("colorTemp",t))return Q``;const e=this.showPercent(t.attributes.color_temp,t.attributes.min_mireds-1,t.attributes.max_mireds-1);return Q` 437 |
438 |
439 | 440 |
441 | 448 | 449 | ${e} 450 |
451 | `}createWhiteValue(t){return!1===this.config.white_value||this.dontShowFeature("whiteValue",t)?Q``:Q` 452 |
453 |
454 | 455 |
456 | 461 | 462 | ${this.showPercent(t.attributes.white_value,0,254)} 463 |
464 | `}createEffectList(t){if(!1===this.config.effects_list)return Q``;if(!this.config.persist_features&&!this.isEntityOn(t))return Q``;let e=t.attributes.effect_list||[];if(this.config.effects_list&&Array.isArray(this.config.effects_list))e=this.config.effects_list;else if(this.config.effects_list&&this.hass.states[this.config.effects_list]){const t=this.hass.states[this.config.effects_list];e=t.attributes&&t.attributes.options||[]}else if(this.dontShowFeature("effectList",t))return Q``;const i=e.map(e=>this.createListItem(t,e)),n=this.language["ui.card.light.effect"];return Q` 465 |
466 | this.setEffect(e,t)} 469 | label="${n}" 470 | > 471 | ${i} 472 | 473 |
474 | `}createListItem(t,e){return Q`${e}`}createColorPicker(t){return!1===this.config.color_picker||this.dontShowFeature("color",t)?Q``:Q` 477 |
478 |
479 |
480 | `}dontShowFeature(t,e){if(this.config.force_features)return!1;if("speed"===t&&"speed"in e.attributes)return!0;if("intensity"===t&&"intensity"in e.attributes)return!0;let i=hi.featureNames[t]&e.attributes.supported_features;const n=e.attributes.supported_color_modes||[];if(!i)switch(t){case"brightness":if(i=Object.prototype.hasOwnProperty.call(e.attributes,"brightness"),!i){const t=["hs","rgb","rgbw","rgbww","white","brightness","color_temp","xy"];i=[...new Set(n.filter(e=>t.includes(e)))].length>0}break;case"colorTemp":if(n){const t=["color_temp"];i=[...new Set(n.filter(e=>t.includes(e)))].length>0}break;case"effectList":i=e.attributes.effect_list&&e.attributes.effect_list.length;break;case"color":if(!i){const t=["hs","rgb","rgbw","rgbww","xy"];i=[...new Set(n.filter(e=>t.includes(e)))].length>0}break;case"whiteValue":i=Object.prototype.hasOwnProperty.call(e.attributes,"white_value");break;default:i=!1}return!i||(!this.config.persist_features&&!this.isEntityOn(e)||void 0)}setColorPicker(t,e){this.callEntityService({hs_color:[t.h,t.s]},e)}_setValue(t,e,i){const n=parseInt(t.target.value,0);isNaN(n)||parseInt(e.attributes[i],0)===n||this.callEntityService({[i]:n},e)}setToggle(t,e){const i=this.isEntityOn(e)?hi.cmdToggle.off:hi.cmdToggle.on;this.callEntityService({},e,i)}setEffect(t,e){t.target.value&&this.callEntityService({effect:t.target.value},e)}callEntityService(t,e,i){if(!this._firstUpdate)return;let n=e.entity_id.split(".")[0];"group"===n&&(n="homeassistant"),this.hass.callService(n,i||hi.cmdToggle.on,{entity_id:e.entity_id,...t})}}customElements.define("light-entity-card",hi),window.customCards=window.customCards||[],window.customCards.push({type:"light-entity-card",name:"Light Entity Card",description:"Control lights and switches"})}]); 481 | //# sourceMappingURL=light-entity-card.js.map --------------------------------------------------------------------------------