Repository: ljmerza/light-entity-card
Branch: master
Commit: ac7a40642c3a
Files: 29
Total size: 134.2 KB
Directory structure:
gitextract_6su_67oe/
├── .babelrc
├── .eslintrc.yaml
├── .gitattributes
├── .github/
│ └── workflows/
│ ├── deploy.yaml
│ └── validate.yaml
├── .gitignore
├── .prettierrc.json
├── LICENSE
├── README.md
├── babel.config.js
├── dist/
│ ├── light-entity-card.js
│ └── light-entity-card.js.LICENSE.txt
├── hacs.json
├── package.json
├── rollup-plugins/
│ └── ignore.js
├── src/
│ ├── buildElementDefinitions.js
│ ├── color-wheel/
│ │ ├── color-picker.js
│ │ ├── convert-color.js
│ │ ├── events-mixin.js
│ │ └── fire_event.js
│ ├── defaults.js
│ ├── globalElementLoader.js
│ ├── index-editor.js
│ ├── index.js
│ ├── style-editor.js
│ └── style.js
└── webpack/
├── config.common.js
├── config.dev.js
└── config.prod.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": [
[
"@babel/preset-env",
{
"modules": false,
"targets": {
"esmodules": true
}
}
],
["minify"]
],
"comments": false,
"plugins": [
[
"@babel/plugin-proposal-decorators",
{ "legacy": true }
],
[
"@babel/plugin-proposal-class-properties",
{ "loose": true }
],
["@babel/plugin-transform-template-literals"],
["iife-wrap"]
]
}
================================================
FILE: .eslintrc.yaml
================================================
extends: airbnb-base
rules:
no-else-return: 0
no-underscore-dangle: 0
nonblock-statement-body-position: 0
curly: 0
no-return-assign: 0
consistent-return: 0
no-mixed-operators: 0
class-methods-use-this: 0
no-nested-ternary: 0
camelcase: 0
no-bitwise: 0
max-len: 0
no-useless-constructor: 0
no-restricted-globals: 0
globals:
window: true
Event: true
customElements: true
================================================
FILE: .gitattributes
================================================
*.js text eol=lf
================================================
FILE: .github/workflows/deploy.yaml
================================================
name: Build and Release on Tag
on:
workflow_dispatch:
push:
tags:
- '*'
jobs:
build-and-release:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Install Dependencies
run: npm install
- name: Run Build
run: npm run build
- name: Commit dist folder
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add dist/
git commit -m "Add dist folder"
- name: Push changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
force: true
directory: dist
- name: Get current and previous tags
id: tags
run: |
current_tag=$(git describe --tags --abbrev=0)
previous_tag=$(git describe --tags --abbrev=0 $(git rev-list --tags --skip=1 --max-count=1))
echo ::set-output name=current_tag::${current_tag}
echo ::set-output name=previous_tag::${previous_tag}
- name: Generate release notes
id: releasenotes
run: |
echo "Release Notes:" > release_notes.md
git log ${previous_tag}..${current_tag} --pretty=format:"%h - %s" >> release_notes.md
echo ::set-output name=notes::$(cat release_notes.md)
- name: Create Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.tags.outputs.current_tag }}
release_name: Release ${{ steps.tags.outputs.current_tag }}
body: ${{ steps.releasenotes.outputs.notes }}
draft: false
prerelease: false
- name: Upload light-entity-card.js to Release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./dist/light-entity-card.js
asset_name: light-entity-card.js
asset_content_type: application/javascript
================================================
FILE: .github/workflows/validate.yaml
================================================
name: Validate
on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"
jobs:
validate:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v2"
- name: HACS validation
uses: "hacs/action@main"
with:
category: "plugin"
================================================
FILE: .gitignore
================================================
/node_modules/
================================================
FILE: .prettierrc.json
================================================
{
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 120,
"arrowParens": "avoid"
}
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 Leonardo Merza
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Light Entity Card
Control any light/switch entity through lovelace
## Support
For help, visit the light entity support thread [here](https://community.home-assistant.io/t/light-entity-card/96146)
<img src='https://raw.githubusercontent.com/ljmerza/light-entity-card/master/card.png' />
[![GitHub Release][releases-shield]][releases]
[![License][license-shield]](LICENSE.md)
![Project Maintenance][maintenance-shield]
[](https://github.com/hacs/integration)
## Features
* Works on any light and switch based entity
* Toggle on/off
* HS Color wheel
* Color temperature and white value support
* Support for configured language
* Compact card support for grouped entities
* use `persist_features: true` to always show entity features
* use `effects_list` to add custom effects list or use `input_select` entity
* always show or hide header and each input
## Installation
Add through [HACS](https://github.com/custom-components/hacs)
Issues with the installation should be asked in the [Home Assistant forums](https://community.home-assistant.io/t/light-entity-card/96146)
## Configurations
```yaml
type: custom:light-entity-card
entity: light.downstairs
```
```yaml
type: custom:light-entity-card
entity: light.downstairs
effects_list:
- effect1
- effect2
```
```yaml
type: custom:light-entity-card
entity: light.downstairs
effects_list: input_select.custom_effect_list
```
```yaml
type: custom:light-entity-card
entity: light.downstairs
group: true
```
## Options
| Name | Type | Requirement | `Default value` Description |
| -------------------- | ------------------- | ------------ | --------------------------------------------------------------------------- |
| type | string | **Required** | `custom:light-entity-card` |
| entity | string | **Required** | The entity name of the light entity to control |
| shorten_cards | boolean | **Optional** | `false` show a compact version of the card |
| consolidate_entities | boolean | **Optional** | `false` if entity is a group you can consolidate all entities into one |
| persist_features | boolean | **Optional** | `false` always show entity features |
| effects_list | list/string/boolean | **Optional** | custom list of effects, an input_select entity, or set false to always hide |
| header | string | **Optional** | custom header name |
| hide_header | boolean | **Optional** | `false` hides the entity header of the card including toggle |
| show_header_icon | boolean | **Optional** | `false` shows the entity icon of the card including toggle |
| brightness | boolean | **Optional** | `true` show brightness slider if available |
| color_temp | boolean | **Optional** | `true` show color temp slider if available |
| white_value | boolean | **Optional** | `true` show white value slider if available |
| color_picker | boolean | **Optional** | `true` show color picker wheel if available |
| speed | boolean | **Optional** | `false` show speed slider if available |
| intensity | boolean | **Optional** | `false` show intensity slider if available |
| force_features | boolean | **Optional** | `false` force showing all features in card |
| full_width_sliders | boolean | **Optional** | `false` makes slider the full width of card |
| brightness_icon | string | **Optional** | `weather-sunny` change the brightness slider icon |
| white_icon | string | **Optional** | `file-word-box` change the white slider icon |
| temperature_icon | string | **Optional** | `thermometer` change the temperature slider icon |
| speed_icon | string | **Optional** | `speedometer` change the speed slider icon |
| intensity_icon | string | **Optional** | `transit-connection-horizontal` change the intensity slider icon |
| show_slider_percent | boolean | **Optional** | `false` show percent next to sliders |
| child_card | boolean | **Optional** | `false` remove padding/margin to make this card within another card |
---
Enjoy my card? Help me out for a couple of :beers: or a :coffee:!
[](https://www.buymeacoffee.com/JMISm06AD)
[commits-shield]: https://img.shields.io/github/commit-activity/y/ljmerza/light-entity-card.svg?style=for-the-badge
[commits]: https://github.com/ljmerza/light-entity-card/commits/master
[license-shield]: https://img.shields.io/github/license/ljmerza/light-entity-card.svg?style=for-the-badge
[maintenance-shield]: https://img.shields.io/badge/maintainer-Leonardo%20Merza%20%40ljmerza-blue.svg?style=for-the-badge
[releases-shield]: https://img.shields.io/github/release/ljmerza/light-entity-card.svg?style=for-the-badge
[releases]: https://github.com/ljmerza/light-entity-card/releases
================================================
FILE: babel.config.js
================================================
module.exports = {
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"debug": true,
"targets": "> 0.25%, not dead",
"shippedProposals": true
}
]
]
}
================================================
FILE: dist/light-entity-card.js
================================================
/*! For license information please see light-entity-card.js.LICENSE.txt */
(()=>{"use strict";const t=window,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,i=Symbol(),n=new WeakMap;class r{constructor(t,e,n){if(this._$cssResult$=!0,n!==i)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const i=this.t;if(e&&void 0===t){const e=void 0!==i&&1===i.length;e&&(t=n.get(i)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&n.set(i,t))}return t}toString(){return this.cssText}}const o=(t,...e)=>{const n=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 r(n,t,i)},s=(i,n)=>{e?i.adoptedStyleSheets=n.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet):n.forEach(e=>{const n=document.createElement("style"),r=t.litNonce;void 0!==r&&n.setAttribute("nonce",r),n.textContent=e.cssText,i.appendChild(n)})},a=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new r("string"==typeof t?t:t+"",void 0,i))(e)})(t):t;var c;const h=window,l=h.trustedTypes,d=l?l.emptyScript:"",u=h.reactiveElementPolyfillSupport,f={toAttribute(t,e){switch(e){case Boolean:t=t?d: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}},p=(t,e)=>e!==t&&(e==e||t==t),g={attribute:!0,type:String,converter:f,reflect:!1,hasChanged:p},_="finalized";class v extends HTMLElement{constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$El=null,this._$Eu()}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=g){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)||g}static finalize(){if(this.hasOwnProperty(_))return!1;this[_]=!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(a(t))}else void 0!==t&&e.push(a(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}_$Eu(){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 s(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=g){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:f).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:f;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||p)(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 y(t,e,i){return e=b(e),function(t,e){if(e&&("object"==O(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)}(t,m()?Reflect.construct(e,i||[],b(t).constructor):e.apply(t,i))}function m(){try{var t=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(t){}return(m=function(){return!!t})()}function b(t){return b=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(t){return t.__proto__||Object.getPrototypeOf(t)},b(t)}function $(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&&w(t,e)}function w(t,e){return w=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},w(t,e)}function k(t,e){var i="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(!i){if(Array.isArray(t)||(i=S(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,e){if(t){if("string"==typeof t)return x(t,e);var i={}.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)?x(t,e):void 0}}function x(t,e){(null==e||e>t.length)&&(e=t.length);for(var i=0,n=Array(e);i<e;i++)n[i]=t[i];return n}function C(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function A(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,P(n.key),n)}}function E(t,e,i){return e&&A(t.prototype,e),i&&A(t,i),Object.defineProperty(t,"prototype",{writable:!1}),t}function P(t){var e=function(t){if("object"!=O(t)||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,"string");if("object"!=O(i))return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(t)}(t);return"symbol"==O(e)?e:e+""}function O(t){return O="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},O(t)}var T;v[_]=!0,v.elementProperties=new Map,v.elementStyles=[],v.shadowRootOptions={mode:"open"},null==u||u({ReactiveElement:v}),(null!==(c=h.reactiveElementVersions)&&void 0!==c?c:h.reactiveElementVersions=[]).push("1.6.3");var j=window,U=j.trustedTypes,I=U?U.createPolicy("lit-html",{createHTML:function(t){return t}}):void 0,N="$lit$",R="lit$".concat((Math.random()+"").slice(9),"$"),H="?"+R,V="<".concat(H,">"),M=document,W=function(){return M.createComment("")},L=function(t){return null===t||"object"!=O(t)&&"function"!=typeof t},B=Array.isArray,D="[ \t\n\f\r]",F=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,z=/-->/g,q=/>/g,K=RegExp(">|".concat(D,"(?:([^\\s\"'>=/]+)(").concat(D,"*=").concat(D,"*(?:[^ \t\n\f\r\"'`<>=]|(\"|')|))|$)"),"g"),J=/'/g,Z=/"/g,G=/^(?:script|style|textarea|title)$/i,Q=function(t){return function(e){for(var i=arguments.length,n=new Array(i>1?i-1:0),r=1;r<i;r++)n[r-1]=arguments[r];return{_$litType$:t,strings:e,values:n}}},X=Q(1),Y=(Q(2),Symbol.for("lit-noChange")),tt=Symbol.for("lit-nothing"),et=new WeakMap,it=M.createTreeWalker(M,129,null,!1);function nt(t,e){if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==I?I.createHTML(e):e}var rt=function(t,e){for(var i,n=t.length-1,r=[],o=2===e?"<svg>":"",s=F,a=0;a<n;a++){for(var c=t[a],h=void 0,l=void 0,d=-1,u=0;u<c.length&&(s.lastIndex=u,null!==(l=s.exec(c)));)u=s.lastIndex,s===F?"!--"===l[1]?s=z:void 0!==l[1]?s=q:void 0!==l[2]?(G.test(l[2])&&(i=RegExp("</"+l[2],"g")),s=K):void 0!==l[3]&&(s=K):s===K?">"===l[0]?(s=null!=i?i:F,d=-1):void 0===l[1]?d=-2:(d=s.lastIndex-l[2].length,h=l[1],s=void 0===l[3]?K:'"'===l[3]?Z:J):s===Z||s===J?s=K:s===z||s===q?s=F:(s=K,i=void 0);var f=s===K&&t[a+1].startsWith("/>")?" ":"";o+=s===F?c+V:d>=0?(r.push(h),c.slice(0,d)+N+c.slice(d)+R+f):c+R+(-2===d?(r.push(void 0),a):f)}return[nt(t,o+(t[n]||"<?>")+(2===e?"</svg>":"")),r]},ot=function(){return E(function t(e,i){var n,r=e.strings,o=e._$litType$;C(this,t),this.parts=[];var s=0,a=0,c=r.length-1,h=this.parts,l=function(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){var n,r,o,s,a=[],c=!0,h=!1;try{if(o=(i=i.call(t)).next,0===e){if(Object(i)!==i)return;c=!1}else for(;!(c=(n=o.call(i)).done)&&(a.push(n.value),a.length!==e);c=!0);}catch(t){h=!0,r=t}finally{try{if(!c&&null!=i.return&&(s=i.return(),Object(s)!==s))return}finally{if(h)throw r}}return a}}(t,e)||S(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.")}()}(rt(r,o),2),d=l[0],u=l[1];if(this.el=t.createElement(d,i),it.currentNode=this.el.content,2===o){var f=this.el.content,p=f.firstChild;p.remove(),f.append.apply(f,function(t){return function(t){if(Array.isArray(t))return x(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||S(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.")}()}(p.childNodes))}for(;null!==(n=it.nextNode())&&h.length<c;){if(1===n.nodeType){if(n.hasAttributes()){var g,_=[],v=k(n.getAttributeNames());try{for(v.s();!(g=v.n()).done;){var y=g.value;if(y.endsWith(N)||y.startsWith(R)){var m=u[a++];if(_.push(y),void 0!==m){var b=n.getAttribute(m.toLowerCase()+N).split(R),$=/([.?@])?(.*)/.exec(m);h.push({type:1,index:s,name:$[2],strings:b,ctor:"."===$[1]?ut:"?"===$[1]?pt:"@"===$[1]?gt:dt})}else h.push({type:6,index:s})}}}catch(t){v.e(t)}finally{v.f()}for(var w=0,A=_;w<A.length;w++){var E=A[w];n.removeAttribute(E)}}if(G.test(n.tagName)){var P=n.textContent.split(R),O=P.length-1;if(O>0){n.textContent=U?U.emptyScript:"";for(var T=0;T<O;T++)n.append(P[T],W()),it.nextNode(),h.push({type:2,index:++s});n.append(P[O],W())}}}else if(8===n.nodeType)if(n.data===H)h.push({type:2,index:s});else for(var j=-1;-1!==(j=n.data.indexOf(R,j+1));)h.push({type:7,index:s}),j+=R.length-1;s++}},null,[{key:"createElement",value:function(t,e){var i=M.createElement("template");return i.innerHTML=t,i}}])}();function st(t,e){var i,n,r,o,s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:t,a=arguments.length>3?arguments[3]:void 0;if(e===Y)return e;var c=void 0!==a?null===(i=s._$Co)||void 0===i?void 0:i[a]:s._$Cl,h=L(e)?void 0:e._$litDirective$;return(null==c?void 0:c.constructor)!==h&&(null===(n=null==c?void 0:c._$AO)||void 0===n||n.call(c,!1),void 0===h?c=void 0:(c=new h(t))._$AT(t,s,a),void 0!==a?(null!==(r=(o=s)._$Co)&&void 0!==r?r:o._$Co=[])[a]=c:s._$Cl=c),void 0!==c&&(e=st(t,c._$AS(t,e.values),c,a)),e}var at,ct,ht=function(){return E(function t(e,i){C(this,t),this._$AV=[],this._$AN=void 0,this._$AD=e,this._$AM=i},[{key:"parentNode",get:function(){return this._$AM.parentNode}},{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"u",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:M).importNode(n,!0);it.currentNode=o;for(var s=it.nextNode(),a=0,c=0,h=r[0];void 0!==h;){if(a===h.index){var l=void 0;2===h.type?l=new lt(s,s.nextSibling,this,t):1===h.type?l=new h.ctor(s,h.name,h.strings,this,t):6===h.type&&(l=new _t(s,this,t)),this._$AV.push(l),h=r[++c]}a!==(null==h?void 0:h.index)&&(s=it.nextNode(),a++)}return it.currentNode=M,o}},{key:"v",value:function(t){var e,i=0,n=k(this._$AV);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()}}}])}(),lt=function(){function t(e,i,n,r){var o;C(this,t),this.type=2,this._$AH=tt,this._$AN=void 0,this._$AA=e,this._$AB=i,this._$AM=n,this.options=r,this._$Cp=null===(o=null==r?void 0:r.isConnected)||void 0===o||o}return E(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._$Cp}},{key:"parentNode",get:function(){var t=this._$AA.parentNode,e=this._$AM;return void 0!==e&&11===(null==t?void 0: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){t=st(this,t,arguments.length>1&&void 0!==arguments[1]?arguments[1]:this),L(t)?t===tt||null==t||""===t?(this._$AH!==tt&&this._$AR(),this._$AH=tt):t!==this._$AH&&t!==Y&&this._(t):void 0!==t._$litType$?this.g(t):void 0!==t.nodeType?this.$(t):function(t){return B(t)||"function"==typeof(null==t?void 0:t[Symbol.iterator])}(t)?this.T(t):this._(t)}},{key:"k",value:function(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}},{key:"$",value:function(t){this._$AH!==t&&(this._$AR(),this._$AH=this.k(t))}},{key:"_",value:function(t){this._$AH!==tt&&L(this._$AH)?this._$AA.nextSibling.data=t:this.$(M.createTextNode(t)),this._$AH=t}},{key:"g",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(nt(n.h,n.h[0]),this.options)),n);if((null===(e=this._$AH)||void 0===e?void 0:e._$AD)===r)this._$AH.v(i);else{var o=new ht(r,this),s=o.u(this.options);o.v(i),this.$(s),this._$AH=o}}},{key:"_$AC",value:function(t){var e=et.get(t.strings);return void 0===e&&et.set(t.strings,e=new ot(t)),e}},{key:"T",value:function(e){B(this._$AH)||(this._$AH=[],this._$AR());var i,n,r=this._$AH,o=0,s=k(e);try{for(s.s();!(n=s.n()).done;){var a=n.value;o===r.length?r.push(i=new t(this.k(W()),this.k(W()),this,this.options)):i=r[o],i._$AI(a),o++}}catch(t){s.e(t)}finally{s.f()}o<r.length&&(this._$AR(i&&i._$AB.nextSibling,o),r.length=o)}},{key:"_$AR",value:function(){var t,e=arguments.length>0&&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._$Cp=t,null===(e=this._$AP)||void 0===e||e.call(this,t))}}])}(),dt=function(){return E(function t(e,i,n,r,o){C(this,t),this.type=1,this._$AH=tt,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=tt},[{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=!L(t)||t!==this._$AH&&t!==Y)&&(this._$AH=t);else{var s,a,c=t;for(t=r[0],s=0;s<r.length-1;s++)(a=st(this,c[i+s],e,s))===Y&&(a=this._$AH[s]),o||(o=!L(a)||a!==this._$AH[s]),a===tt?t=tt:t!==tt&&(t+=(null!=a?a:"")+r[s+1]),this._$AH[s]=a}o&&!n&&this.j(t)}},{key:"j",value:function(t){t===tt?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,null!=t?t:"")}}])}(),ut=function(t){function e(){var t;return C(this,e),(t=y(this,e,arguments)).type=3,t}return $(e,t),E(e,[{key:"j",value:function(t){this.element[this.name]=t===tt?void 0:t}}])}(dt),ft=U?U.emptyScript:"",pt=function(t){function e(){var t;return C(this,e),(t=y(this,e,arguments)).type=4,t}return $(e,t),E(e,[{key:"j",value:function(t){t&&t!==tt?this.element.setAttribute(this.name,ft):this.element.removeAttribute(this.name)}}])}(dt),gt=function(t){function e(t,i,n,r,o){var s;return C(this,e),(s=y(this,e,[t,i,n,r,o])).type=5,s}return $(e,t),E(e,[{key:"_$AI",value:function(t){var e;if((t=null!==(e=st(this,t,arguments.length>1&&void 0!==arguments[1]?arguments[1]:this,0))&&void 0!==e?e:tt)!==Y){var i=this._$AH,n=t===tt&&i!==tt||t.capture!==i.capture||t.once!==i.once||t.passive!==i.passive,r=t!==tt&&(i===tt||n);n&&this.element.removeEventListener(this.name,this,i),r&&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)}}])}(dt),_t=function(){return E(function t(e,i,n){C(this,t),this.element=e,this.type=6,this._$AN=void 0,this._$AM=i,this.options=n},[{key:"_$AU",get:function(){return this._$AM._$AU}},{key:"_$AI",value:function(t){st(this,t)}}])}(),vt=j.litHtmlPolyfillSupport;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},yt(t)}function mt(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,bt(n.key),n)}}function bt(t){var e=function(t){if("object"!=yt(t)||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,"string");if("object"!=yt(i))return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(t)}(t);return"symbol"==yt(e)?e:e+""}function $t(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function wt(){try{var t=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(t){}return(wt=function(){return!!t})()}function kt(t,e,i,n){var r=St(xt(1&n?t.prototype:t),e,i);return 2&n&&"function"==typeof r?function(t){return r.apply(i,t)}:r}function St(){return St="undefined"!=typeof Reflect&&Reflect.get?Reflect.get.bind():function(t,e,i){var n=function(t,e){for(;!{}.hasOwnProperty.call(t,e)&&null!==(t=xt(t)););return t}(t,e);if(n){var r=Object.getOwnPropertyDescriptor(n,e);return r.get?r.get.call(arguments.length<3?t:i):r.value}},St.apply(null,arguments)}function xt(t){return xt=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(t){return t.__proto__||Object.getPrototypeOf(t)},xt(t)}function Ct(t,e){return Ct=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},Ct(t,e)}null==vt||vt(ot,lt),(null!==(T=j.litHtmlVersions)&&void 0!==T?T:j.litHtmlVersions=[]).push("2.8.0");var At=function(t){function e(){var t;return function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,e),(t=function(t,e,i){return e=xt(e),function(t,e){if(e&&("object"==yt(e)||"function"==typeof e))return e;if(void 0!==e)throw new TypeError("Derived constructors may only return object or undefined");return $t(t)}(t,wt()?Reflect.construct(e,i||[],xt(t).constructor):e.apply(t,i))}(this,e,arguments)).renderOptions={host:$t(t)},t._$Do=void 0,t}return function(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&&Ct(t,e)}(e,t),function(t,e){return e&&mt(t.prototype,e),Object.defineProperty(t,"prototype",{writable:!1}),t}(e,[{key:"createRenderRoot",value:function(){var t,i,n=kt(e,"createRenderRoot",this,3)([]);return null!==(t=(i=this.renderOptions).renderBefore)&&void 0!==t||(i.renderBefore=n.firstChild),n}},{key:"update",value:function(t){var i=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),kt(e,"update",this,3)([t]),this._$Do=function(t,e,i){var n,r,o=null!==(n=null==i?void 0:i.renderBefore)&&void 0!==n?n:e,s=o._$litPart$;if(void 0===s){var a=null!==(r=null==i?void 0:i.renderBefore)&&void 0!==r?r:null;o._$litPart$=s=new lt(e.insertBefore(W(),a),a,void 0,null!=i?i:{})}return s._$AI(t),s}(i,this.renderRoot,this.renderOptions)}},{key:"connectedCallback",value:function(){var t;kt(e,"connectedCallback",this,3)([]),null===(t=this._$Do)||void 0===t||t.setConnected(!0)}},{key:"disconnectedCallback",value:function(){var t;kt(e,"disconnectedCallback",this,3)([]),null===(t=this._$Do)||void 0===t||t.setConnected(!1)}},{key:"render",value:function(){return Y}}])}(v);At.finalized=!0,At._$litElement$=!0,null===(at=globalThis.litElementHydrateSupport)||void 0===at||at.call(globalThis,{LitElement:At});var Et=globalThis.litElementPolyfillSupport;null==Et||Et({LitElement:At}),(null!==(ct=globalThis.litElementVersions)&&void 0!==ct?ct:globalThis.litElementVersions=[]).push("3.3.3");const Pt=globalThis,Ot=Pt.ShadowRoot&&(void 0===Pt.ShadyCSS||Pt.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype;function Tt(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)=>{if(Ot)t.adoptedStyleSheets=e.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const i of e){const e=document.createElement("style"),n=Pt.litNonce;void 0!==n&&e.setAttribute("nonce",n),e.textContent=i.cssText,t.appendChild(e)}})(r,this.constructor.elementStyles),r}}}Symbol(),new WeakMap;const jt=o`
.light-entity-card {
padding: 16px;
}
.light-entity-child-card {
box-shadow: none !important;
padding: 0 !important;
}
.light-entity-card.group {
padding-bottom: 5;
padding-top: 0;
}
.ha-slider-full-width ha-slider {
width: 100%;
}
.percent-slider {
color: var(--primary-text-color);
display: flex;
justify-content: center;
align-items: center;
margin-left: 8px;
min-width: 40px;
text-align: right;
}
.light-entity-card__header {
display: flex;
justify-content: space-between;
@apply --paper-font-headline;
line-height: 40px;
color: var(--primary-text-color);
}
.group .light-entity-card__header {
}
.light-entity-card-sliders > div {
margin-top: 10px;
}
.group .light-entity-card-sliders > div {
margin-top: 0px;
}
.light-entity-card__toggle {
display: flex;
cursor: pointer;
}
.light-entity-card__color-picker {
display: flex;
justify-content: center;
margin-top: 10px;
}
.light-entity-card__color-picker ha-hs-color-picker {
max-width: 300px;
width: 100%;
}
.light-entity-card-color_temp {
background-image: var(--ha-slider-background, linear-gradient(to right, #a6d1ff, #ffb74d));
border-radius: 4px;
}
.light-entity-card-color_temp--kelvin {
background-image: var(--ha-slider-background, linear-gradient(to right, #ffb74d, #a6d1ff));
}
.light-entity-card-effectlist {
padding-top: 10px;
padding-bottom: 10px;
}
.group .light-entity-card-effectlist {
padding-bottom: 20px;
}
.light-entity-card-center {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.hidden {
display: none;
}
.icon-container {
display: flex;
justify-content: center;
align-items: center;
margin-right: 8px;
}
`,Ut={shorten_cards:!1,consolidate_entities:!1,child_card:!1,hide_header:!1,show_header_icon:!1,header:"",persist_features:!1,brightness:!0,color_temp:!0,white_value:!0,warm_white_value:!0,color_picker:!0,effects_list:!0,speed:!0,intensity:!0,force_features:!1,show_slider_percent:!1,full_width_sliders:!1,color_temp_in_kelvin:!1,transition:0,brightness_icon:"weather-sunny",white_icon:"file-word-box",warm_white_icon:"weather-sunset",temperature_icon:"thermometer",speed_icon:"speedometer",intensity_icon:"transit-connection-horizontal"},It=o`
.entities {
padding-top: 10px;
padding-bottom: 10px;
display: flex;
}
.entities ha-formfield {
display: block;
margin-bottom: 10px;
margin-left: 10px;
}
.checkbox-options {
display: flex;
}
ha-entity-picker {
width: 100%;
}
.checkbox-options ha-formfield,
.entities mwc-switch,
.entities ha-form-string {
padding-right: 2%;
width: 48%;
}
.checkbox-options ha-formfield {
margin-top: 10px;
}
.overall-config {
margin-bottom: 20px;
}
`,Nt=(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),{}),Rt=t=>({name:t,promise:customElements.whenDefined(t).then(()=>customElements.get(t))}),Ht=(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 Vt extends(Tt(At)){static get elementDefinitions(){return Nt([Rt("ha-checkbox"),Rt("ha-formfield"),Rt("ha-form-string"),Rt("ha-entity-picker")],Vt)}static get styles(){return It}static get properties(){return{hass:{},_config:{}}}setConfig(t){this._config={...Ut,...t}}get entityDomains(){return["switch","light","group"]}firstUpdated(){this._firstRendered=!0}render(){if(!this.hass||!this._config)return X``;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)}return X`
<div class="card-config">
<div class=overall-config'>
<ha-form-string
.schema=${{name:"header",type:"string"}}
label="Header"
.data="${t}"
.configValue="${"header"}"
@changed="${this.configChanged}"
></ha-form-string>
</div>
<div class='entities'>
<ha-entity-picker
.hass=${this.hass}
.value=${this._config.entity}
.includeDomains=${this.entityDomains}
label="Entity"
@value-changed=${this.entityChanged}
allow-custom-entity
></ha-entity-picker>
<ha-form-string
.schema=${{name:"brightness_icon",type:"string"}}
label="Brightness Icon"
.data="${this._config.brightness_icon}"
.configValue="${"brightness_icon"}"
@changed="${this.configChanged}"
></ha-form-string>
</div>
<div class='entities'>
<ha-form-string
.schema=${{name:"white_icon",type:"string"}}
label="White Icon"
.data="${this._config.white_icon}"
.configValue="${"white_icon"}"
@changed="${this.configChanged}"
></ha-form-string>
<ha-form-string
.schema=${{name:"warm_white_icon",type:"string"}}
label="Warm White Icon"
.data="${this._config.warm_white_icon}"
.configValue="${"warm_white_icon"}"
@changed="${this.configChanged}"
></ha-form-string>
</div>
<div class='entities'>
<ha-form-string
.schema=${{name:"temperature_icon",type:"string"}}
label="Temperature Icon"
.data="${this._config.temperature_icon}"
.configValue="${"temperature_icon"}"
@changed="${this.configChanged}"
></ha-form-string>
<ha-form-string
.schema=${{name:"transition",type:"string"}}
label="Transition (seconds)"
.data="${String(this._config.transition||"")}"
.configValue="${"transition"}"
@changed="${this.configChanged}"
></ha-form-string>
</div>
<div class='overall-config'>
<div class='checkbox-options'>
<ha-formfield label="Shorten Cards">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.shorten_cards}
.value="${"shorten_cards"}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Persist Features">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.persist_features}
.value="${"persist_features"}"
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Show Brightness">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.brightness}
.value="${"brightness"}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Show Color Temp">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.color_temp}
.value="${"color_temp"}"
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Color Temp in Kelvin">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.color_temp_in_kelvin}
.value="${"color_temp_in_kelvin"}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Show White Channel">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.white_value}
.value="${"white_value"}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Show Warm White">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.warm_white_value}
.value="${"warm_white_value"}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Show Speed">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.speed}
.value="${"speed"}"
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Show Intensity">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.intensity}
.value="${"intensity"}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Show Color Picker">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.color_picker}
.value="${"color_picker"}"
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Show Effects List">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.effects_list}
.value="${"effects_list"}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Full Width Sliders">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.full_width_sliders}
.value="${"full_width_sliders"}"
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Show Slider Percent">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.show_slider_percent}
.value="${"show_slider_percent"}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Show Brightness %">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.show_brightness_percent}
.value="${"show_brightness_percent"}"
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Show Color Temp %">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.show_color_temp_percent}
.value="${"show_color_temp_percent"}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Hide Header">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.hide_header}
.value="${"hide_header"}"
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Show Header Icon">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.show_header_icon}
.value="${"show_header_icon"}"
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Child Card">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.child_card}
.value="${"child_card"}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Force Features">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.force_features}
.value="${"force_features"}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Consolidate Entities">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.consolidate_entities}
.value="${"consolidate_entities"}"
></ha-checkbox>
</ha-formfield>
</div>
</div>
</div>
`}entityChanged(t){if(!this._config||!this.hass||!this._firstRendered)return;const e=t.detail.value;e!==this._config.entity&&(this._config={...this._config,entity:e},Ht(this,"config-changed",{config:this._config}))}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},Ht(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},Ht(this,"config-changed",{config:this._config})}}const Mt="light-entity-card-editor";customElements.define(Mt,Vt),console.info("light-entity-card v6.3.1");class Wt extends(Tt(At)){static get elementDefinitions(){return Nt([Rt("ha-card"),Rt("more-info-light"),Rt("ha-switch"),Rt("ha-icon"),Rt("state-badge"),Rt("ha-slider"),Rt("ha-hs-color-picker"),Rt("ha-select"),Rt("mwc-list-item")],Wt)}static get properties(){return{hass:{},config:{},_colorPickerValues:{state:!0}}}async firstUpdated(){if(this._firstUpdate=!0,!1!==this.config.color_picker&&this.config.entity.startsWith("light.")&&!customElements.get("ha-hs-color-picker")){const t=document.querySelector("home-assistant");if(t){const e=document.createElement("style");e.textContent="ha-more-info-dialog { display: none !important; }",t.shadowRoot.appendChild(e);const i=new CustomEvent("hass-more-info",{detail:{entityId:this.config.entity},bubbles:!0,composed:!0});t.dispatchEvent(i);try{await Promise.race([customElements.whenDefined("ha-hs-color-picker"),new Promise((t,e)=>setTimeout(()=>e(new Error("timeout")),5e3))])}catch(t){}finally{const i=new CustomEvent("hass-more-info",{detail:{entityId:""},bubbles:!0,composed:!0});t.dispatchEvent(i),e.remove()}}this.requestUpdate()}}setConfig(t){if(!t.entity)throw Error("entity required.");this.config={...Ut,...t}}static async getConfigElement(){return document.createElement(Mt)}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,10)}getEntityLength(t){return/^light\./.test(t)?Wt.entityLength.light:/^switch\./.test(t)?Wt.entityLength.switch:0}get styles(){return jt}isEntityOn(t){return"on"===t.state}render(){const t=this.hass.states[this.config.entity];if(!t)return X`
<style>
${this.styles}
</style>
<ha-card> ${`Invalid entity: ${this.config.entity}`} </ha-card>
`;this._stateObjects=this.getEntitiesToShow(t),this.config.consolidate_entities?this._shownStateObjects=[t]:this._shownStateObjects=[...this._stateObjects];const e=this._shownStateObjects.reduce((t,e)=>X`${t}${this.createEntityTemplate(e)}`,""),i=`light-entity-card ${this.config.shorten_cards?" group":""} ${this.config.child_card?" light-entity-child-card":""}`;return X`
<style>
${this.styles}
</style>
<ha-card class="${i}">
${e}
</ha-card>
`}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 X`
${this.createHeader(t)}
<div class="light-entity-card-sliders ${e}">
${this.createBrightnessSlider(t)} ${this.createSpeedSlider(t)}
${this.createIntensitySlider(t)} ${this.createColorTemperature(t)}
${this.createWhiteValue(t)}
${this.createWarmWhiteValue(t)}
</div>
${this.createColorPicker(t)} ${this.createEffectList(t)}
`}createHeader(t){if(this.config.hide_header)return X``;const e=this.config.header||t.attributes.friendly_name||t.entity_id;return X`
<div class="light-entity-card__header">
${this.showHeaderIcon(t)}
<div class="light-entity-card__title">${e}</div>
<div class="light-entity-card-toggle">
<ha-switch .checked=${this.isEntityOn(t)} @change=${e=>this.setToggle(e,t)}></ha-switch>
</div>
</div>
`}showHeaderIcon(t){return this.config.show_header_icon?X`
<div class="icon-container">
<state-badge .stateObj=${t}></state-badge>
</div>
`:X``}createBrightnessSlider(t){return!1===this.config.brightness||this.dontShowFeature("brightness",t)?X``:X`
<div class="control light-entity-card-center">
<div class="icon-container" title="Brightness">
<ha-icon icon="hass:${this.config.brightness_icon}"></ha-icon>
</div>
<ha-slider
.value="${t.attributes.brightness||0}"
@change="${e=>this._setValue(e,t,"brightness")}"
min="1"
max="255"
></ha-slider>
${this.showPercent(t.attributes.brightness,0,254,"brightness")}
</div>
`}createSpeedSlider(t){return!1===this.config.speed||this.dontShowFeature("speed",t)?X``:X`
<div class="control light-entity-card-center">
<div class="icon-container" title="Speed">
<ha-icon icon="hass:${this.config.speed_icon}"></ha-icon>
</div>
<ha-slider
.value="${t.attributes.speed||0}"
@change="${e=>this._setValue(e,t,"speed")}"
min="1"
max="255"
></ha-slider>
${this.showPercent(t.attributes.speed,0,254)}
</div>
`}createIntensitySlider(t){return!1===this.config.intensity||this.dontShowFeature("intensity",t)?X``:X`
<div class="control light-entity-card-center">
<div class="icon-container" title="Intensity">
<ha-icon icon="hass:${this.config.intensity_icon}"></ha-icon>
</div>
<ha-slider
.value="${t.attributes.intensity||0}"
@change="${e=>this._setValue(e,t,"intensity")}"
min="1"
max="255"
></ha-slider>
${this.showPercent(t.attributes.intensity,0,254)}
</div>
`}showPercent(t,e,i,n){if("brightness"===n&&void 0!==this.config.show_brightness_percent){if(!this.config.show_brightness_percent)return X``}else if("color_temp"===n&&void 0!==this.config.show_color_temp_percent){if(!this.config.show_color_temp_percent)return X``}else if("color_temp"===n&&this.config.color_temp_in_kelvin);else if(!this.config.show_slider_percent)return X``;if("color_temp"===n&&this.config.color_temp_in_kelvin)return X` <div class="percent-slider">${t}K</div> `;let r=parseInt(100*(t-e)/(i-e),10);return isNaN(r)&&(r=0),X` <div class="percent-slider">${r}%</div> `}createColorTemperature(t){if(!1===this.config.color_temp)return X``;if(this.dontShowFeature("colorTemp",t))return X``;const e=void 0!==t.attributes.min_color_temp_kelvin;let i,n,r,o,s,a;if(this.config.color_temp_in_kelvin){let o,s,a;if(e?(a=t.attributes.color_temp_kelvin,o=t.attributes.min_color_temp_kelvin,s=t.attributes.max_color_temp_kelvin):(a=t.attributes.color_temp?Math.round(1e6/t.attributes.color_temp):null,o=t.attributes.max_mireds?Math.round(1e6/t.attributes.max_mireds):null,s=t.attributes.min_mireds?Math.round(1e6/t.attributes.min_mireds):null),!o||!s)return X``;const c=Math.round((o+s)/2);i="number"==typeof a&&Number.isFinite(a)&&a>0?a:null,n=o,r=s;const h=i||c,l=this.showPercent(h,n,r,"color_temp");return X`
<div class="control light-entity-card-center">
<div class="icon-container" title="Color Temperature">
<ha-icon icon="hass:${this.config.temperature_icon}"></ha-icon>
</div>
<ha-slider
class="light-entity-card-color_temp light-entity-card-color_temp--kelvin"
min="${n}"
max="${r}"
.value=${h}
@change="${i=>this._setColorTemp(i,t,e,!0)}"
>
</ha-slider>
${l}
</div>
`}if(e){const e=t.attributes.color_temp_kelvin,i=t.attributes.min_color_temp_kelvin,n=t.attributes.max_color_temp_kelvin;if(!i||!n)return X``;a="number"==typeof e&&Number.isFinite(e)&&e>0?Math.round(1e6/e):null,o=Math.round(1e6/n),s=Math.round(1e6/i)}else if(a=t.attributes.color_temp,o=t.attributes.min_mireds,s=t.attributes.max_mireds,!o||!s)return X``;const c=s&&o?s-o:0,h=c>0&&null!=a?Math.round((a-o)/c*100):50,l=this.showPercent(h,0,100,"color_temp");return X`
<div class="control light-entity-card-center">
<div class="icon-container" title="Color Temperature">
<ha-icon icon="hass:${this.config.temperature_icon}"></ha-icon>
</div>
<ha-slider
class="light-entity-card-color_temp"
min="0"
max="100"
.value=${h}
@change="${i=>this._setColorTemp(i,t,e,!1,o,s)}"
>
</ha-slider>
${l}
</div>
`}getWhiteValue(t,e=3){const i=t.attributes.rgbw_color,n=t.attributes.rgbww_color;return i&&3===e?i[3]||0:n&&e<n.length?n[e]||0:3===e&&void 0!==t.attributes.white_value?t.attributes.white_value??0:0}createWhiteValue(t){if(!1===this.config.white_value)return X``;if(this.dontShowFeature("whiteValue",t))return X``;const e=this.getWhiteValue(t,3);return X`
<div class="control light-entity-card-center">
<div class="icon-container" title="White">
<ha-icon icon="hass:${this.config.white_icon}"></ha-icon>
</div>
<ha-slider
max="255"
.value="${e}"
@change="${e=>this._setWhiteValue(e,t,3)}"
>
</ha-slider>
${this.showPercent(e,0,254)}
</div>
`}createWarmWhiteValue(t){if(!1===this.config.warm_white_value)return X``;if(this.dontShowFeature("warmWhiteValue",t))return X``;const e=this.getWhiteValue(t,4);return X`
<div class="control light-entity-card-center">
<div class="icon-container" title="Warm White">
<ha-icon icon="hass:${this.config.warm_white_icon}"></ha-icon>
</div>
<ha-slider
max="255"
.value="${e}"
@change="${e=>this._setWhiteValue(e,t,4)}"
>
</ha-slider>
${this.showPercent(e,0,254)}
</div>
`}_setWhiteValue(t,e,i){const n=parseInt(t.target.value,10);if(isNaN(n))return;const r=e.attributes.supported_color_modes||[],o=e.attributes.rgbw_color,s=e.attributes.rgbww_color;if(r.includes("rgbw")&&3===i){const t=o?o.slice(0,3):e.attributes.rgb_color||[255,255,255];this.callEntityService({rgbw_color:[t[0],t[1],t[2],n]},e)}else if(r.includes("rgbww")){let t;if(s)t=s;else if(o)t=[o[0],o[1],o[2],o[3],0];else{const i=e.attributes.rgb_color||[255,255,255];t=[i[0],i[1],i[2],0,0]}const r=[...t];r[i]=n,this.callEntityService({rgbww_color:r},e)}else this.callEntityService({white_value:n},e)}createEffectList(t){if(!1===this.config.effects_list)return X``;if(!this.config.persist_features&&!this.isEntityOn(t))return X``;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 X``;const i=e.map(e=>this.createListItem(t,e)),n=this.hass.localize("ui.card.light.effect")||"Effect";return X`
<div class="control light-entity-card-center light-entity-card-effectlist">
<ha-select
@closed="${t=>t.stopPropagation()}"
@selected=${e=>this.setEffect(e,t)}
label="${n}"
>
${i}
</ha-select>
</div>
`}createListItem(t,e){return X`<mwc-list-item value="${e}" ?selected=${e===t.attributes.effect}
>${e}</mwc-list-item
>`}createColorPicker(t){if(!1===this.config.color_picker)return X``;if(this.dontShowFeature("color",t))return X``;const e=t.attributes.hs_color||[0,0],i=this._colorPickerValues&&this._colorPickerValues[t.entity_id]||[e[0],e[1]/100];return X`
<div class="light-entity-card__color-picker">
<ha-hs-color-picker
.value=${i}
@cursor-moved=${e=>{this._colorPickerValues={...this._colorPickerValues,[t.entity_id]:e.detail.value}}}
@value-changed=${e=>this._onColorPickerChanged(e.detail.value,t)}
></ha-hs-color-picker>
</div>
`}dontShowFeature(t,e){if(this.config.force_features)return!1;if("speed"===t&&"speed"in e.attributes)return!1;if("intensity"===t&&"intensity"in e.attributes)return!1;let i=Wt.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":{const t=["hs","rgb","rgbw","rgbww","xy"];i=[...new Set(n.filter(e=>t.includes(e)))].length>0;break}case"whiteValue":if(i=Object.prototype.hasOwnProperty.call(e.attributes,"white_value"),!i){const t=["rgbw","rgbww"];i=n.some(e=>t.includes(e))}break;case"warmWhiteValue":{const t=["rgbww"];i=n.some(e=>t.includes(e));break}default:i=!1}return!i||!this.config.persist_features&&!this.isEntityOn(e)}_onColorPickerChanged(t,e){if(this._colorPickerValues){const{[e.entity_id]:t,...i}=this._colorPickerValues;this._colorPickerValues=i}this.setColorPicker(t,e)}setColorPicker(t,e){t&&this.callEntityService({hs_color:[t[0],100*t[1]]},e)}_setValue(t,e,i){const n=parseInt(t.target.value,10);isNaN(n)||parseInt(e.attributes[i],10)===n||this.callEntityService({[i]:n},e)}_setColorTemp(t,e,i,n,r,o){const s=parseInt(t.target.value,10);if(!isNaN(s))if(n)if(i){if(s===parseInt(e.attributes.color_temp_kelvin,10))return;this.callEntityService({color_temp_kelvin:s},e)}else{const t=Math.round(1e6/s);if(t===parseInt(e.attributes.color_temp,10))return;this.callEntityService({color_temp:t},e)}else{if(!Number.isFinite(r)||!Number.isFinite(o)||o<=r)return;const t=Math.round(r+s/100*(o-r));if(i){const i=Math.round(1e6/t);if(i===parseInt(e.attributes.color_temp_kelvin,10))return;this.callEntityService({color_temp_kelvin:i},e)}else{if(t===parseInt(e.attributes.color_temp,10))return;this.callEntityService({color_temp:t},e)}}}setToggle(t,e){const i=this.isEntityOn(e)?Wt.cmdToggle.off:Wt.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");const r=parseFloat(this.config.transition)||0;r>0&&"light"===n&&(t={...t,transition:r}),this.hass.callService(n,i||Wt.cmdToggle.on,{entity_id:e.entity_id,...t})}}customElements.define("light-entity-card",Wt),window.customCards=window.customCards||[],window.customCards.push({type:"light-entity-card",name:"Light Entity Card",description:"Control lights and switches"})})();
================================================
FILE: dist/light-entity-card.js.LICENSE.txt
================================================
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
================================================
FILE: hacs.json
================================================
{
"name": "Light Entity Card",
"render_readme": true,
"filename": "light-entity-card.js"
}
================================================
FILE: package.json
================================================
{
"name": "light-entity-card",
"version": "6.3.1",
"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 <ljmerza@gmail.com>",
"license": "MIT",
"dependencies": {
"@babel/polyfill": "^7.4.4",
"@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": "^5.95.0",
"webpack-cli": "^5.1.4",
"webpack-merge": "^6.0.1"
},
"scripts": {
"lint": "eslint --fix ./src",
"start": "webpack --watch --config webpack/config.dev.js",
"build": "webpack --config webpack/config.prod.js"
}
}
================================================
FILE: rollup-plugins/ignore.js
================================================
export default function (userOptions = {}) {
// Files need to be absolute paths.
// This only works if the file has no exports
// and only is imported for its side effects
const files = userOptions.files || [];
if (files.length === 0) {
return {
name: 'ignore',
};
}
return {
name: 'ignore',
load(id) {
return files.some(toIgnorePath => id.startsWith(toIgnorePath))
? {
code: '',
}
: null;
},
};
}
================================================
FILE: src/buildElementDefinitions.js
================================================
const buildElementDefinitions = (elements, constructor) => elements.reduce((aggregate, element) => {
if (element.defineId) {
// eslint-disable-next-line no-param-reassign
aggregate[element.defineId] = element;
} else {
element.promise.then((clazz) => {
if (constructor.registry.get(element.name) === undefined) {
constructor.registry.define(element.name, clazz);
}
});
}
return aggregate;
}, {});
export default buildElementDefinitions;
================================================
FILE: src/color-wheel/color-picker.js
================================================
import { LitElement, html } from 'lit';
import { ScopedRegistryHost } from '@lit-labs/scoped-registry-mixin';
import { hs2rgb, rgb2hs } from "./convert-color";
class LmeColorPicker extends ScopedRegistryHost(LitElement) {
static get properties() {
return {
hsColor: {
type: Object,
},
// use these properties to update the state via attributes
desiredHsColor: {
type: Object,
observer: "applyHsColor",
},
// use these properties to update the state via attributes
desiredRgbColor: {
type: Object,
observer: "applyRgbColor",
},
// width, height and radius apply to the coordinates of
// of the canvas.
// border width are relative to these numbers
// the onscreen displayed size should be controlled with css
// and should be the same or smaller
width: {
type: Number,
value: 500,
},
height: {
type: Number,
value: 500,
},
radius: {
type: Number,
value: 225,
},
// the amount segments for the hue
// 0 = continuous gradient
// other than 0 gives 'pie-pieces'
hueSegments: {
type: Number,
value: 0,
observer: "segmentationChange",
},
// the amount segments for the hue
// 0 = continuous gradient
// 1 = only fully saturated
// > 1 = segments from white to fully saturated
saturationSegments: {
type: Number,
value: 0,
observer: "segmentationChange",
},
// set to true to make the segments purely esthetical
// this allows selection off all collors, also
// interpolated between the segments
ignoreSegments: {
type: Boolean,
value: false,
},
// throttle te amount of 'colorselected' events fired
// value is timeout in milliseconds
throttle: {
type: Number,
value: 500,
},
};
}
render() {
console.log('test')
return html`
<style>
:host {
user-select: none;
-webkit-user-select: none;
}
#canvas {
position: relative;
width: 100%;
max-width: 330px;
}
#canvas > * {
display: block;
}
#interactionLayer {
color: white;
position: absolute;
cursor: crosshair;
width: 100%;
height: 100%;
overflow: visible;
}
#backgroundLayer {
width: 100%;
overflow: visible;
--wheel-bordercolor: var(--ha-color-picker-wheel-bordercolor, white);
--wheel-borderwidth: var(--ha-color-picker-wheel-borderwidth, 3);
--wheel-shadow: var(
--ha-color-picker-wheel-shadow,
rgb(15, 15, 15) 10px 5px 5px 0px
);
}
#marker {
fill: currentColor;
stroke: var(--ha-color-picker-marker-bordercolor, white);
stroke-width: var(--ha-color-picker-marker-borderwidth, 3);
filter: url(#marker-shadow);
}
.dragging #marker {
}
#colorTooltip {
display: none;
fill: currentColor;
stroke: var(--ha-color-picker-tooltip-bordercolor, white);
stroke-width: var(--ha-color-picker-tooltip-borderwidth, 3);
}
.touch.dragging #colorTooltip {
display: inherit;
}
</style>
<div id="canvas">
<svg id="interactionLayer">
<defs>
<filter
id="marker-shadow"
x="-50%"
y="-50%"
width="200%"
height="200%"
filterUnits="objectBoundingBox"
>
<feOffset
result="offOut"
in="SourceAlpha"
dx="2"
dy="2"
></feOffset>
<feGaussianBlur
result="blurOut"
in="offOut"
stdDeviation="2"
></feGaussianBlur>
<feComponentTransfer in="blurOut" result="alphaOut">
<feFuncA type="linear" slope="0.3"></feFuncA>
</feComponentTransfer>
<feBlend
in="SourceGraphic"
in2="alphaOut"
mode="normal"
></feBlend>
</filter>
</defs>
</svg>
<canvas id="backgroundLayer"></canvas>
</div>
`;
}
firstUpdated() {
super.firstUpdated();
this.setupLayers();
this.drawColorWheel();
this.drawMarker();
if (this.desiredHsColor) {
this.applyHsColor(this.desiredHsColor);
}
if (this.desiredRgbColor) {
this.applyRgbColor(this.desiredRgbColor);
}
this.interactionLayer.addEventListener("mousedown", (ev) =>
this.onMouseDown(ev)
);
this.interactionLayer.addEventListener("touchstart", (ev) =>
this.onTouchStart(ev)
);
}
// converts browser coordinates to canvas canvas coordinates
// origin is wheel center
// returns {x: X, y: Y} object
convertToCanvasCoordinates(clientX, clientY) {
const svgPoint = this.interactionLayer.createSVGPoint();
svgPoint.x = clientX;
svgPoint.y = clientY;
const cc = svgPoint.matrixTransform(
this.interactionLayer.getScreenCTM().inverse()
);
return { x: cc.x, y: cc.y };
}
// Mouse events
onMouseDown(ev) {
const cc = this.convertToCanvasCoordinates(ev.clientX, ev.clientY);
// return if we're not on the wheel
if (!this.isInWheel(cc.x, cc.y)) {
return;
}
// a mousedown in wheel is always a color select action
this.onMouseSelect(ev);
// allow dragging
this.canvas.classList.add("mouse", "dragging");
this.addEventListener("mousemove", this.onMouseSelect);
this.addEventListener("mouseup", this.onMouseUp);
}
onMouseUp() {
this.canvas.classList.remove("mouse", "dragging");
this.removeEventListener("mousemove", this.onMouseSelect);
}
onMouseSelect(ev) {
requestAnimationFrame(() => this.processUserSelect(ev));
}
// Touch events
onTouchStart(ev) {
const touch = ev.changedTouches[0];
const cc = this.convertToCanvasCoordinates(touch.clientX, touch.clientY);
// return if we're not on the wheel
if (!this.isInWheel(cc.x, cc.y)) {
return;
}
if (ev.target === this.marker) {
// drag marker
ev.preventDefault();
this.canvas.classList.add("touch", "dragging");
this.addEventListener("touchmove", this.onTouchSelect);
this.addEventListener("touchend", this.onTouchEnd);
return;
}
// don't fire color selection immediately,
// wait for touchend and invalidate when we scroll
this.tapBecameScroll = false;
this.addEventListener("touchend", this.onTap);
this.addEventListener(
"touchmove",
() => {
this.tapBecameScroll = true;
},
{ passive: true }
);
}
onTap(ev) {
if (this.tapBecameScroll) {
return;
}
ev.preventDefault();
this.onTouchSelect(ev);
}
onTouchEnd() {
this.canvas.classList.remove("touch", "dragging");
this.removeEventListener("touchmove", this.onTouchSelect);
}
onTouchSelect(ev) {
requestAnimationFrame(() => this.processUserSelect(ev.changedTouches[0]));
}
/*
* General event/selection handling
*/
// Process user input to color
processUserSelect(ev) {
const canvasXY = this.convertToCanvasCoordinates(ev.clientX, ev.clientY);
const hs = this.getColor(canvasXY.x, canvasXY.y);
let rgb;
if (!this.isInWheel(canvasXY.x, canvasXY.y)) {
const [r, g, b] = hs2rgb([hs.h, hs.s]);
rgb = { r, g, b };
} else {
rgb = this.getRgbColor(canvasXY.x, canvasXY.y);
}
this.onColorSelect(hs, rgb);
}
// apply color to marker position and canvas
onColorSelect(hs, rgb) {
this.setMarkerOnColor(hs); // marker always follows mouse 'raw' hs value (= mouse position)
if (!this.ignoreSegments) {
// apply segments if needed
hs = this.applySegmentFilter(hs);
}
// always apply the new color to the interface / canvas
this.applyColorToCanvas(hs);
// throttling is applied to updating the exposed colors (properties)
// and firing of events
if (this.colorSelectIsThrottled) {
// make sure we apply the last selected color
// eventually after throttle limit has passed
clearTimeout(this.ensureFinalSelect);
this.ensureFinalSelect = setTimeout(() => {
this.fireColorSelected(hs, rgb); // do it for the final time
}, this.throttle);
return;
}
this.fireColorSelected(hs, rgb); // do it
this.colorSelectIsThrottled = true;
setTimeout(() => {
this.colorSelectIsThrottled = false;
}, this.throttle);
}
// set color values and fire colorselected event
fireColorSelected(hs, rgb) {
this.hsColor = hs;
this.fire("colorselected", { hs, rgb });
}
/*
* Interface updating
*/
// set marker position to the given color
setMarkerOnColor(hs) {
if (!this.marker || !this.tooltip) {
return;
}
const dist = hs.s * this.radius;
const theta = ((hs.h - 180) / 180) * Math.PI;
const markerdX = -dist * Math.cos(theta);
const markerdY = -dist * Math.sin(theta);
const translateString = `translate(${markerdX},${markerdY})`;
this.marker.setAttribute("transform", translateString);
this.tooltip.setAttribute("transform", translateString);
}
// apply given color to interface elements
applyColorToCanvas(hs) {
if (!this.interactionLayer) {
return;
}
// we're not really converting hs to hsl here, but we keep it cheap
// setting the color on the interactionLayer, the svg elements can inherit
this.interactionLayer.style.color = `hsl(${hs.h}, 100%, ${
100 - hs.s * 50
}%)`;
}
applyHsColor(hs) {
// do nothing is we already have the same color
if (this.hsColor && this.hsColor.h === hs.h && this.hsColor.s === hs.s) {
return;
}
this.setMarkerOnColor(hs); // marker is always set on 'raw' hs position
if (!this.ignoreSegments) {
// apply segments if needed
hs = this.applySegmentFilter(hs);
}
this.hsColor = hs;
// always apply the new color to the interface / canvas
this.applyColorToCanvas(hs);
}
applyRgbColor(rgb) {
const [h, s] = rgb2hs(rgb);
this.applyHsColor({ h, s });
}
/*
* input processing helpers
*/
// get angle (degrees)
getAngle(dX, dY) {
const theta = Math.atan2(-dY, -dX); // radians from the left edge, clockwise = positive
const angle = (theta / Math.PI) * 180 + 180; // degrees, clockwise from right
return angle;
}
// returns true when coordinates are in the colorwheel
isInWheel(x, y) {
return this.getDistance(x, y) <= 1;
}
// returns distance from wheel center, 0 = center, 1 = edge, >1 = outside
getDistance(dX, dY) {
return Math.sqrt(dX * dX + dY * dY) / this.radius;
}
/*
* Getting colors
*/
getColor(x, y) {
const hue = this.getAngle(x, y); // degrees, clockwise from right
const relativeDistance = this.getDistance(x, y); // edge of radius = 1
const sat = Math.min(relativeDistance, 1); // Distance from center
return { h: hue, s: sat };
}
getRgbColor(x, y) {
// get current pixel
const imageData = this.backgroundLayer
.getContext("2d")
.getImageData(x + 250, y + 250, 1, 1);
const pixel = imageData.data;
return { r: pixel[0], g: pixel[1], b: pixel[2] };
}
applySegmentFilter(hs) {
// apply hue segment steps
if (this.hueSegments) {
const angleStep = 360 / this.hueSegments;
const halfAngleStep = angleStep / 2;
hs.h -= halfAngleStep; // take the 'centered segemnts' into account
if (hs.h < 0) {
hs.h += 360;
} // don't end up below 0
const rest = hs.h % angleStep;
hs.h -= rest - angleStep;
}
// apply saturation segment steps
if (this.saturationSegments) {
if (this.saturationSegments === 1) {
hs.s = 1;
} else {
const segmentSize = 1 / this.saturationSegments;
const saturationStep = 1 / (this.saturationSegments - 1);
const calculatedSat = Math.floor(hs.s / segmentSize) * saturationStep;
hs.s = Math.min(calculatedSat, 1);
}
}
return hs;
}
/*
* Drawing related stuff
*/
setupLayers() {
this.canvas = this.$.canvas;
this.backgroundLayer = this.$.backgroundLayer;
this.interactionLayer = this.$.interactionLayer;
// coordinate origin position (center of the wheel)
this.originX = this.width / 2;
this.originY = this.originX;
// synchronise width/height coordinates
this.backgroundLayer.width = this.width;
this.backgroundLayer.height = this.height;
this.interactionLayer.setAttribute(
"viewBox",
`${-this.originX} ${-this.originY} ${this.width} ${this.height}`
);
}
drawColorWheel() {
/*
* Setting up all paremeters
*/
let shadowColor;
let shadowOffsetX;
let shadowOffsetY;
let shadowBlur;
const context = this.backgroundLayer.getContext("2d");
// postioning and sizing
const cX = this.originX;
const cY = this.originY;
const radius = this.radius;
const counterClockwise = false;
// styling of the wheel
const wheelStyle = window.getComputedStyle(this.backgroundLayer, null);
const borderWidth = parseInt(
wheelStyle.getPropertyValue("--wheel-borderwidth"),
10
);
const borderColor = wheelStyle
.getPropertyValue("--wheel-bordercolor")
.trim();
const wheelShadow = wheelStyle.getPropertyValue("--wheel-shadow").trim();
// extract shadow properties from CSS variable
// the shadow should be defined as: "10px 5px 5px 0px COLOR"
if (wheelShadow !== "none") {
const values = wheelShadow.split("px ");
shadowColor = values.pop();
shadowOffsetX = parseInt(values[0], 10);
shadowOffsetY = parseInt(values[1], 10);
shadowBlur = parseInt(values[2], 10) || 0;
}
const borderRadius = radius + borderWidth / 2;
const wheelRadius = radius;
const shadowRadius = radius + borderWidth;
/*
* Drawing functions
*/
function drawCircle(hueSegments, saturationSegments) {
hueSegments = hueSegments || 360; // reset 0 segments to 360
const angleStep = 360 / hueSegments;
const halfAngleStep = angleStep / 2; // center segments on color
for (let angle = 0; angle <= 360; angle += angleStep) {
const startAngle = (angle - halfAngleStep) * (Math.PI / 180);
const endAngle = (angle + halfAngleStep + 1) * (Math.PI / 180);
context.beginPath();
context.moveTo(cX, cY);
context.arc(
cX,
cY,
wheelRadius,
startAngle,
endAngle,
counterClockwise
);
context.closePath();
// gradient
const gradient = context.createRadialGradient(
cX,
cY,
0,
cX,
cY,
wheelRadius
);
let lightness = 100;
// first gradient stop
gradient.addColorStop(0, `hsl(${angle}, 100%, ${lightness}%)`);
// segment gradient stops
if (saturationSegments > 0) {
const ratioStep = 1 / saturationSegments;
let ratio = 0;
for (let stop = 1; stop < saturationSegments; stop += 1) {
const prevLighness = lightness;
ratio = stop * ratioStep;
lightness = 100 - 50 * ratio;
gradient.addColorStop(
ratio,
`hsl(${angle}, 100%, ${prevLighness}%)`
);
gradient.addColorStop(ratio, `hsl(${angle}, 100%, ${lightness}%)`);
}
gradient.addColorStop(ratio, `hsl(${angle}, 100%, 50%)`);
}
// last gradient stop
gradient.addColorStop(1, `hsl(${angle}, 100%, 50%)`);
context.fillStyle = gradient;
context.fill();
}
}
function drawShadow() {
context.save();
context.beginPath();
context.arc(cX, cY, shadowRadius, 0, 2 * Math.PI, false);
context.shadowColor = shadowColor;
context.shadowOffsetX = shadowOffsetX;
context.shadowOffsetY = shadowOffsetY;
context.shadowBlur = shadowBlur;
context.fillStyle = "white";
context.fill();
context.restore();
}
function drawBorder() {
context.beginPath();
context.arc(cX, cY, borderRadius, 0, 2 * Math.PI, false);
context.lineWidth = borderWidth;
context.strokeStyle = borderColor;
context.stroke();
}
/*
* Call the drawing functions
* draws the shadow, wheel and border
*/
if (wheelStyle.shadow !== "none") {
drawShadow();
}
drawCircle(this.hueSegments, this.saturationSegments);
if (borderWidth > 0) {
drawBorder();
}
}
/*
* Draw the (draggable) marker and tooltip
* on the interactionLayer)
*/
drawMarker() {
const svgElement = this.interactionLayer;
const markerradius = this.radius * 0.08;
const tooltipradius = this.radius * 0.15;
const TooltipOffsetY = -(tooltipradius * 3);
const TooltipOffsetX = 0;
svgElement.marker = document.createElementNS(
"http://www.w3.org/2000/svg",
"circle"
);
svgElement.marker.setAttribute("id", "marker");
svgElement.marker.setAttribute("r", markerradius);
this.marker = svgElement.marker;
svgElement.appendChild(svgElement.marker);
svgElement.tooltip = document.createElementNS(
"http://www.w3.org/2000/svg",
"circle"
);
svgElement.tooltip.setAttribute("id", "colorTooltip");
svgElement.tooltip.setAttribute("r", tooltipradius);
svgElement.tooltip.setAttribute("cx", TooltipOffsetX);
svgElement.tooltip.setAttribute("cy", TooltipOffsetY);
this.tooltip = svgElement.tooltip;
svgElement.appendChild(svgElement.tooltip);
}
segmentationChange() {
if (this.backgroundLayer) {
this.drawColorWheel();
}
}
}
export default LmeColorPicker;
================================================
FILE: src/color-wheel/convert-color.js
================================================
export const rgb2hsv = (rgb) => {
const [r, g, b] = rgb;
const v = Math.max(r, g, b);
const c = v - Math.min(r, g, b);
const h =
c && (v === r ? (g - b) / c : v === g ? 2 + (b - r) / c : 4 + (r - g) / c);
return [60 * (h < 0 ? h + 6 : h), v && c / v, v];
};
export const hsv2rgb = (hsv) => {
const [h, s, v] = hsv;
const f = (n) => {
const k = (n + h / 60) % 6;
return v - v * s * Math.max(Math.min(k, 4 - k, 1), 0);
};
return [f(5), f(3), f(1)];
};
export const rgb2hs = (rgb) => rgb2hsv(rgb).slice(0, 2);
export const hs2rgb = (hs) => hsv2rgb([hs[0], hs[1], 255]);
================================================
FILE: src/color-wheel/events-mixin.js
================================================
import { dedupingMixin } from "@polymer/polymer/lib/utils/mixin";
import { fireEvent } from "./fire_event";
// Polymer legacy event helpers used courtesy of the Polymer project.
//
// Copyright (c) 2017 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/* @polymerMixin */
export const EventsMixin = dedupingMixin(
(superClass) =>
class extends superClass {
/**
* Dispatches a custom event with an optional detail value.
*
* @param {string} type Name of event type.
* @param {*=} detail Detail value containing event-specific
* payload.
* @param {{ bubbles: (boolean|undefined),
cancelable: (boolean|undefined),
composed: (boolean|undefined) }=}
* options Object specifying options. These may include:
* `bubbles` (boolean, defaults to `true`),
* `cancelable` (boolean, defaults to false), and
* `node` on which to fire the event (HTMLElement, defaults to `this`).
* @return {Event} The new event that was fired.
*/
fire(type, detail, options) {
options = options || {};
return fireEvent(options.node || this, type, detail, options);
}
}
);
================================================
FILE: src/color-wheel/fire_event.js
================================================
// Polymer legacy event helpers used courtesy of the Polymer project.
//
// Copyright (c) 2017 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/**
* Dispatches a custom event with an optional detail value.
*
* @param {string} type Name of event type.
* @param {*=} detail Detail value containing event-specific
* payload.
* @param {{ bubbles: (boolean|undefined),
* cancelable: (boolean|undefined),
* composed: (boolean|undefined) }=}
* options Object specifying options. These may include:
* `bubbles` (boolean, defaults to `true`),
* `cancelable` (boolean, defaults to false), and
* `node` on which to fire the event (HTMLElement, defaults to `this`).
* @return {Event} The new event that was fired.
*/
export const fireEvent = (
node,
type,
detail,
options
) => {
options = options || {};
detail = detail === null || detail === undefined ? {} : detail;
const event = new Event(type, {
bubbles: options.bubbles === undefined ? true : options.bubbles,
cancelable: Boolean(options.cancelable),
composed: options.composed === undefined ? true : options.composed,
});
event.detail = detail;
node.dispatchEvent(event);
return event;
};
================================================
FILE: src/defaults.js
================================================
export default {
shorten_cards: false,
consolidate_entities: false,
child_card: false,
hide_header: false,
show_header_icon: false,
header: '',
persist_features: false,
brightness: true,
color_temp: true,
white_value: true,
warm_white_value: true,
color_picker: true,
effects_list: true,
speed: true,
intensity: true,
force_features: false,
show_slider_percent: false,
full_width_sliders: false,
color_temp_in_kelvin: false,
transition: 0,
brightness_icon: 'weather-sunny',
white_icon: 'file-word-box',
warm_white_icon: 'weather-sunset',
temperature_icon: 'thermometer',
speed_icon: 'speedometer',
intensity_icon: 'transit-connection-horizontal',
};
================================================
FILE: src/globalElementLoader.js
================================================
const globalElementLoader = name => ({
name,
promise: customElements.whenDefined(name).then(() => customElements.get(name)),
});
export default globalElementLoader;
================================================
FILE: src/index-editor.js
================================================
import { LitElement, html } from 'lit';
import { ScopedRegistryHost } from '@lit-labs/scoped-registry-mixin';
import style from './style-editor';
import defaultConfig from './defaults';
import buildElementDefinitions from './buildElementDefinitions';
import globalElementLoader from './globalElementLoader';
export const fireEvent = (node, type, detail = {}, options = {}) => {
const event = new Event(type, {
bubbles: options.bubbles === undefined ? true : options.bubbles,
cancelable: Boolean(options.cancelable),
composed: options.composed === undefined ? true : options.composed,
});
event.detail = detail;
node.dispatchEvent(event);
return event;
};
export default class LightEntityCardEditor extends ScopedRegistryHost(LitElement) {
static get elementDefinitions() {
return buildElementDefinitions([
globalElementLoader('ha-checkbox'),
globalElementLoader('ha-formfield'),
globalElementLoader('ha-form-string'),
globalElementLoader('ha-entity-picker'),
], LightEntityCardEditor);
}
static get styles() {
return style;
}
static get properties() {
return { hass: {}, _config: {} };
}
setConfig(config) {
this._config = {
...defaultConfig,
...config,
};
}
get entityDomains() {
return ['switch', 'light', 'group'];
}
firstUpdated() {
this._firstRendered = true;
}
render() {
if (!this.hass || !this._config) {
return html``;
}
// get header name
let { header } = this._config;
if (!header && this._config.entity) {
let name = this._config.entity.split('.')[1] || '';
if (name) {
name = name.charAt(0).toUpperCase() + name.slice(1);
header = name;
}
}
return html`
<div class="card-config">
<div class=overall-config'>
<ha-form-string
.schema=${{ name: 'header', type: 'string' }}
label="Header"
.data="${header}"
.configValue="${'header'}"
@changed="${this.configChanged}"
></ha-form-string>
</div>
<div class='entities'>
<ha-entity-picker
.hass=${this.hass}
.value=${this._config.entity}
.includeDomains=${this.entityDomains}
label="Entity"
@value-changed=${this.entityChanged}
allow-custom-entity
></ha-entity-picker>
<ha-form-string
.schema=${{ name: 'brightness_icon', type: 'string' }}
label="Brightness Icon"
.data="${this._config.brightness_icon}"
.configValue="${'brightness_icon'}"
@changed="${this.configChanged}"
></ha-form-string>
</div>
<div class='entities'>
<ha-form-string
.schema=${{ name: 'white_icon', type: 'string' }}
label="White Icon"
.data="${this._config.white_icon}"
.configValue="${'white_icon'}"
@changed="${this.configChanged}"
></ha-form-string>
<ha-form-string
.schema=${{ name: 'warm_white_icon', type: 'string' }}
label="Warm White Icon"
.data="${this._config.warm_white_icon}"
.configValue="${'warm_white_icon'}"
@changed="${this.configChanged}"
></ha-form-string>
</div>
<div class='entities'>
<ha-form-string
.schema=${{ name: 'temperature_icon', type: 'string' }}
label="Temperature Icon"
.data="${this._config.temperature_icon}"
.configValue="${'temperature_icon'}"
@changed="${this.configChanged}"
></ha-form-string>
<ha-form-string
.schema=${{ name: 'transition', type: 'string' }}
label="Transition (seconds)"
.data="${String(this._config.transition || '')}"
.configValue="${'transition'}"
@changed="${this.configChanged}"
></ha-form-string>
</div>
<div class='overall-config'>
<div class='checkbox-options'>
<ha-formfield label="Shorten Cards">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.shorten_cards}
.value="${'shorten_cards'}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Persist Features">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.persist_features}
.value="${'persist_features'}"
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Show Brightness">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.brightness}
.value="${'brightness'}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Show Color Temp">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.color_temp}
.value="${'color_temp'}"
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Color Temp in Kelvin">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.color_temp_in_kelvin}
.value="${'color_temp_in_kelvin'}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Show White Channel">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.white_value}
.value="${'white_value'}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Show Warm White">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.warm_white_value}
.value="${'warm_white_value'}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Show Speed">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.speed}
.value="${'speed'}"
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Show Intensity">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.intensity}
.value="${'intensity'}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Show Color Picker">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.color_picker}
.value="${'color_picker'}"
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Show Effects List">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.effects_list}
.value="${'effects_list'}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Full Width Sliders">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.full_width_sliders}
.value="${'full_width_sliders'}"
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Show Slider Percent">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.show_slider_percent}
.value="${'show_slider_percent'}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Show Brightness %">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.show_brightness_percent}
.value="${'show_brightness_percent'}"
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Show Color Temp %">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.show_color_temp_percent}
.value="${'show_color_temp_percent'}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Hide Header">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.hide_header}
.value="${'hide_header'}"
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Show Header Icon">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.show_header_icon}
.value="${'show_header_icon'}"
></ha-checkbox>
</ha-formfield>
<ha-formfield label="Child Card">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.child_card}
.value="${'child_card'}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Force Features">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.force_features}
.value="${'force_features'}"
></ha-checkbox>
</ha-formfield>
</div>
<div class='checkbox-options'>
<ha-formfield label="Consolidate Entities">
<ha-checkbox
@change="${this.checkboxConfigChanged}"
.checked=${this._config.consolidate_entities}
.value="${'consolidate_entities'}"
></ha-checkbox>
</ha-formfield>
</div>
</div>
</div>
`;
}
entityChanged(ev) {
if (!this._config || !this.hass || !this._firstRendered) return;
const entity = ev.detail.value;
if (entity === this._config.entity) return;
this._config = { ...this._config, entity };
fireEvent(this, 'config-changed', { config: this._config });
}
configChanged(ev) {
if (!this._config || !this.hass || !this._firstRendered) return;
const {
target: { configValue, value },
detail: { value: checkedValue },
} = ev;
if (checkedValue !== undefined && checkedValue !== null) {
this._config = { ...this._config, [configValue]: checkedValue };
} else {
this._config = { ...this._config, [configValue]: value };
}
fireEvent(this, 'config-changed', { config: this._config });
}
checkboxConfigChanged(ev) {
if (!this._config || !this.hass || !this._firstRendered) return;
const {
target: { value, checked },
} = ev;
this._config = { ...this._config, [value]: checked };
fireEvent(this, 'config-changed', { config: this._config });
}
}
================================================
FILE: src/index.js
================================================
import { LitElement, html } from 'lit';
import { ScopedRegistryHost } from '@lit-labs/scoped-registry-mixin';
import style from './style';
import defaultConfig from './defaults';
import LightEntityCardEditor from './index-editor';
import packageJson from '../package.json';
import buildElementDefinitions from './buildElementDefinitions';
import globalElementLoader from './globalElementLoader';
const editorName = 'light-entity-card-editor';
customElements.define(editorName, LightEntityCardEditor);
console.info(`light-entity-card v${packageJson.version}`);
class LightEntityCard extends ScopedRegistryHost(LitElement) {
static get elementDefinitions() {
return buildElementDefinitions(
[
globalElementLoader('ha-card'),
globalElementLoader('more-info-light'),
globalElementLoader('ha-switch'),
globalElementLoader('ha-icon'),
globalElementLoader('state-badge'),
globalElementLoader('ha-slider'),
globalElementLoader('ha-hs-color-picker'),
globalElementLoader('ha-select'),
globalElementLoader('mwc-list-item'),
],
LightEntityCard
);
}
static get properties() {
return {
hass: {},
config: {},
_colorPickerValues: { state: true },
};
}
async firstUpdated() {
this._firstUpdate = true;
// ha-hs-color-picker is lazy-loaded by HA (only when more-info dialog opens).
// Force it to load by opening a more-info dialog hidden via CSS, then closing it.
const needsColorPicker = this.config.color_picker !== false
&& this.config.entity.startsWith('light.');
if (needsColorPicker && !customElements.get('ha-hs-color-picker')) {
const ha = document.querySelector('home-assistant');
if (ha) {
// Hide the dialog to prevent visible flash
const hideStyle = document.createElement('style');
hideStyle.textContent = 'ha-more-info-dialog { display: none !important; }';
ha.shadowRoot.appendChild(hideStyle);
const ev = new CustomEvent('hass-more-info', {
detail: { entityId: this.config.entity },
bubbles: true, composed: true,
});
ha.dispatchEvent(ev);
try {
await Promise.race([
customElements.whenDefined('ha-hs-color-picker'),
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000)),
]);
} catch (e) {
// Timed out — color picker element not available, continue gracefully
} finally {
// Always close the hidden dialog and remove the style
const closeEv = new CustomEvent('hass-more-info', {
detail: { entityId: '' },
bubbles: true, composed: true,
});
ha.dispatchEvent(closeEv);
hideStyle.remove();
}
}
this.requestUpdate();
}
}
/**
* checks and saves config of entity
* @param {*} config
*/
setConfig(config) {
if (!config.entity) throw Error('entity required.');
this.config = {
...defaultConfig,
...config,
};
}
static async getConfigElement() {
// eslint-disable-next-line no-undef
return document.createElement(editorName);
}
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,
};
}
/**
* get the current size of the card
* @return {Number}
*/
getCardSize() {
if (!this.config || !this.__hass || !this.__hass.states[this.config.entity]) {
return 1;
}
let cardLength = 0;
const entities = this.__hass.states[this.config.entity];
// if given a group entity then sum length of each entity by type
// else just get the sible entity length
if (Array.isArray(entities.attributes.entity_id)) {
entities.attributes.entity_id.forEach(entity_id => (cardLength += this.getEntityLength(entity_id)));
} else {
cardLength += this.getEntityLength(entities.attributes.entity_id);
}
// if we are compacting the card account for that
if (this.config.group) {
cardLength *= 0.8;
}
return parseInt(cardLength, 10);
}
/**
* determines the UI length of an entity
* @param {string} entity_id
*/
getEntityLength(entity_id) {
if (/^light\./.test(entity_id)) {
return LightEntityCard.entityLength.light;
} else if (/^switch\./.test(entity_id)) {
return LightEntityCard.entityLength.switch;
} else {
return 0;
}
}
/**
* generates the CSS styles for this card
* @return {TemplateResult}
*/
get styles() {
return style;
}
/**
* check if the given entity is on or off
* @param {LightEntity} stateObj
* @return {Boolean}
*/
isEntityOn(stateObj) {
return stateObj.state === 'on';
}
/**
* generates a card for each given entiy in the config
* @return {TemplateResult}
*/
render() {
const entity = this.hass.states[this.config.entity];
if (!entity) {
return html`
<style>
${this.styles}
</style>
<ha-card> ${`Invalid entity: ${this.config.entity}`} </ha-card>
`;
}
this._stateObjects = this.getEntitiesToShow(entity);
// need to find what state objects are actually going to be shown
if (this.config.consolidate_entities) {
this._shownStateObjects = [entity];
} else {
this._shownStateObjects = [...this._stateObjects];
}
const templates = this._shownStateObjects.reduce(
(htmlTemplate, stateObj) => html`${htmlTemplate}${this.createEntityTemplate(stateObj)}`,
// eslint-disable-next-line comma-dangle
''
);
const css = `light-entity-card ${this.config.shorten_cards ? ' group' : ''} ${
this.config.child_card ? ' light-entity-child-card' : ''
}`;
return html`
<style>
${this.styles}
</style>
<ha-card class="${css}">
${templates}
</ha-card>
`;
}
/**
* gets all the entities we need to build this card for
* @param {LightEntity|GroupEntity} entities
* @return {Array<LightEntity>}
*/
getEntitiesToShow(entities) {
if (entities.attributes.entity_id && Array.isArray(entities.attributes.entity_id))
return entities.attributes.entity_id.map(entity_id => this.hass.states[entity_id]).filter(Boolean);
return [entities];
}
/**
* creates an entity's template
* @param {LightEntity} stateObj
* @return {TemplateResult}
*/
createEntityTemplate(stateObj) {
const sliderClass = this.config.full_width_sliders ? 'ha-slider-full-width' : '';
return html`
${this.createHeader(stateObj)}
<div class="light-entity-card-sliders ${sliderClass}">
${this.createBrightnessSlider(stateObj)} ${this.createSpeedSlider(stateObj)}
${this.createIntensitySlider(stateObj)} ${this.createColorTemperature(stateObj)}
${this.createWhiteValue(stateObj)}
${this.createWarmWhiteValue(stateObj)}
</div>
${this.createColorPicker(stateObj)} ${this.createEffectList(stateObj)}
`;
}
/**
* creates card header with state toggle for a given entity
* @param {LightEntity} stateObj
* @return {TemplateResult}
*/
createHeader(stateObj) {
if (this.config.hide_header) return html``;
const title = this.config.header || stateObj.attributes.friendly_name || stateObj.entity_id;
return html`
<div class="light-entity-card__header">
${this.showHeaderIcon(stateObj)}
<div class="light-entity-card__title">${title}</div>
<div class="light-entity-card-toggle">
<ha-switch .checked=${this.isEntityOn(stateObj)} @change=${e => this.setToggle(e, stateObj)}></ha-switch>
</div>
</div>
`;
}
showHeaderIcon(stateObj) {
if (!this.config.show_header_icon) return html``;
return html`
<div class="icon-container">
<state-badge .stateObj=${stateObj}></state-badge>
</div>
`;
}
/**
* creates brightness slider
* @param {LightEntity} stateObj
* @return {TemplateResult}
*/
createBrightnessSlider(stateObj) {
if (this.config.brightness === false) return html``;
if (this.dontShowFeature('brightness', stateObj)) return html``;
return html`
<div class="control light-entity-card-center">
<div class="icon-container" title="Brightness">
<ha-icon icon="hass:${this.config.brightness_icon}"></ha-icon>
</div>
<ha-slider
.value="${stateObj.attributes.brightness || 0}"
@change="${event => this._setValue(event, stateObj, 'brightness')}"
min="1"
max="255"
></ha-slider>
${this.showPercent(stateObj.attributes.brightness, 0, 254, 'brightness')}
</div>
`;
}
/**
* creates speed slider
* @param {LightEntity} stateObj
* @return {TemplateResult}
*/
createSpeedSlider(stateObj) {
if (this.config.speed === false) return html``;
if (this.dontShowFeature('speed', stateObj)) return html``;
return html`
<div class="control light-entity-card-center">
<div class="icon-container" title="Speed">
<ha-icon icon="hass:${this.config.speed_icon}"></ha-icon>
</div>
<ha-slider
.value="${stateObj.attributes.speed || 0}"
@change="${event => this._setValue(event, stateObj, 'speed')}"
min="1"
max="255"
></ha-slider>
${this.showPercent(stateObj.attributes.speed, 0, 254)}
</div>
`;
}
/**
* creates intensity slider
* @param {LightEntity} stateObj
* @return {TemplateResult}
*/
createIntensitySlider(stateObj) {
if (this.config.intensity === false) return html``;
if (this.dontShowFeature('intensity', stateObj)) return html``;
return html`
<div class="control light-entity-card-center">
<div class="icon-container" title="Intensity">
<ha-icon icon="hass:${this.config.intensity_icon}"></ha-icon>
</div>
<ha-slider
.value="${stateObj.attributes.intensity || 0}"
@change="${event => this._setValue(event, stateObj, 'intensity')}"
min="1"
max="255"
></ha-slider>
${this.showPercent(stateObj.attributes.intensity, 0, 254)}
</div>
`;
}
/**
* shows slider value label if config is set
* @param {number} value
* @param {number} min
* @param {number} max
* @param {string} [sliderType] - 'brightness' or 'color_temp' for per-slider overrides
* @return {TemplateResult}
*/
showPercent(value, min, max, sliderType) {
// Per-slider visibility overrides
if (sliderType === 'brightness' && this.config.show_brightness_percent !== undefined) {
if (!this.config.show_brightness_percent) return html``;
} else if (sliderType === 'color_temp' && this.config.show_color_temp_percent !== undefined) {
if (!this.config.show_color_temp_percent) return html``;
} else if (sliderType === 'color_temp' && this.config.color_temp_in_kelvin) {
// color_temp_in_kelvin implies showing the value label
} else if (!this.config.show_slider_percent) {
return html``;
}
// Show kelvin for color temp if configured (slider value is already in kelvin)
if (sliderType === 'color_temp' && this.config.color_temp_in_kelvin) {
return html` <div class="percent-slider">${value}K</div> `;
}
let percent = parseInt(((value - min) * 100) / (max - min), 10);
if (isNaN(percent)) percent = 0;
return html` <div class="percent-slider">${percent}%</div> `;
}
/**
* creates color temperature slider for a given entity
* @param {LightEntity} stateObj
* @return {TemplateResult}
*/
createColorTemperature(stateObj) {
if (this.config.color_temp === false) return html``;
if (this.dontShowFeature('colorTemp', stateObj)) return html``;
// HA 2026.3+ uses kelvin-based attributes; fall back to mireds for older HA
const usesKelvin = stateObj.attributes.min_color_temp_kelvin !== undefined;
const showInKelvin = this.config.color_temp_in_kelvin;
let currentTemp, minTemp, maxTemp;
if (showInKelvin) {
// Slider works in kelvin so the native popup shows kelvin values.
// Kelvin: low=warm, high=cool — slider left=warm, right=cool.
let minK, maxK, kelvin;
if (usesKelvin) {
kelvin = stateObj.attributes.color_temp_kelvin;
minK = stateObj.attributes.min_color_temp_kelvin;
maxK = stateObj.attributes.max_color_temp_kelvin;
} else {
kelvin = stateObj.attributes.color_temp
? Math.round(1000000 / stateObj.attributes.color_temp) : null;
minK = stateObj.attributes.max_mireds
? Math.round(1000000 / stateObj.attributes.max_mireds) : null;
maxK = stateObj.attributes.min_mireds
? Math.round(1000000 / stateObj.attributes.min_mireds) : null;
}
if (!minK || !maxK) return html``;
const midpoint = Math.round((minK + maxK) / 2);
currentTemp = (typeof kelvin === 'number' && Number.isFinite(kelvin) && kelvin > 0)
? kelvin : null;
minTemp = minK;
maxTemp = maxK;
const sliderValue = currentTemp || midpoint;
const label = this.showPercent(sliderValue, minTemp, maxTemp, 'color_temp');
return html`
<div class="control light-entity-card-center">
<div class="icon-container" title="Color Temperature">
<ha-icon icon="hass:${this.config.temperature_icon}"></ha-icon>
</div>
<ha-slider
class="light-entity-card-color_temp light-entity-card-color_temp--kelvin"
min="${minTemp}"
max="${maxTemp}"
.value=${sliderValue}
@change="${event => this._setColorTemp(event, stateObj, usesKelvin, true)}"
>
</ha-slider>
${label}
</div>
`;
}
// Compute mired range for percentage-based slider.
// Slider works in 0–100 so the native popup shows a percentage value.
let minMired, maxMired, currentMired;
if (usesKelvin) {
const kelvin = stateObj.attributes.color_temp_kelvin;
const minK = stateObj.attributes.min_color_temp_kelvin;
const maxK = stateObj.attributes.max_color_temp_kelvin;
if (!minK || !maxK) return html``;
currentMired = (typeof kelvin === 'number' && Number.isFinite(kelvin) && kelvin > 0)
? Math.round(1000000 / kelvin) : null;
minMired = Math.round(1000000 / maxK);
maxMired = Math.round(1000000 / minK);
} else {
currentMired = stateObj.attributes.color_temp;
minMired = stateObj.attributes.min_mireds;
maxMired = stateObj.attributes.max_mireds;
if (!minMired || !maxMired) return html``;
}
const miredRange = (maxMired && minMired) ? maxMired - minMired : 0;
const percentValue = (miredRange > 0 && currentMired != null)
? Math.round(((currentMired - minMired) / miredRange) * 100) : 50;
const label = this.showPercent(percentValue, 0, 100, 'color_temp');
return html`
<div class="control light-entity-card-center">
<div class="icon-container" title="Color Temperature">
<ha-icon icon="hass:${this.config.temperature_icon}"></ha-icon>
</div>
<ha-slider
class="light-entity-card-color_temp"
min="0"
max="100"
.value=${percentValue}
@change="${event => this._setColorTemp(event, stateObj, usesKelvin, false, minMired, maxMired)}"
>
</ha-slider>
${label}
</div>
`;
}
/**
* gets the current white value from entity state
* supports modern rgbw_color/rgbww_color and legacy white_value
* @param {LightEntity} stateObj
* @param {number} index - index in the color tuple (3 for white/cool white, 4 for warm white)
* @return {number}
*/
getWhiteValue(stateObj, index = 3) {
const rgbwColor = stateObj.attributes.rgbw_color;
const rgbwwColor = stateObj.attributes.rgbww_color;
if (rgbwColor && index === 3) return rgbwColor[3] || 0;
if (rgbwwColor && index < rgbwwColor.length) return rgbwwColor[index] || 0;
// Legacy fallback
if (index === 3 && stateObj.attributes.white_value !== undefined) {
return stateObj.attributes.white_value ?? 0;
}
return 0;
}
/**
* creates white value slider for a given entity
* supports modern RGBW/RGBWW color modes and legacy white_value
* @param {LightEntity} stateObj
* @return {TemplateResult}
*/
createWhiteValue(stateObj) {
if (this.config.white_value === false) return html``;
if (this.dontShowFeature('whiteValue', stateObj)) return html``;
const whiteValue = this.getWhiteValue(stateObj, 3);
return html`
<div class="control light-entity-card-center">
<div class="icon-container" title="White">
<ha-icon icon="hass:${this.config.white_icon}"></ha-icon>
</div>
<ha-slider
max="255"
.value="${whiteValue}"
@change="${event => this._setWhiteValue(event, stateObj, 3)}"
>
</ha-slider>
${this.showPercent(whiteValue, 0, 254)}
</div>
`;
}
/**
* creates warm white value slider for RGBWW entities
* @param {LightEntity} stateObj
* @return {TemplateResult}
*/
createWarmWhiteValue(stateObj) {
if (this.config.warm_white_value === false) return html``;
if (this.dontShowFeature('warmWhiteValue', stateObj)) return html``;
const warmWhiteValue = this.getWhiteValue(stateObj, 4);
return html`
<div class="control light-entity-card-center">
<div class="icon-container" title="Warm White">
<ha-icon icon="hass:${this.config.warm_white_icon}"></ha-icon>
</div>
<ha-slider
max="255"
.value="${warmWhiteValue}"
@change="${event => this._setWhiteValue(event, stateObj, 4)}"
>
</ha-slider>
${this.showPercent(warmWhiteValue, 0, 254)}
</div>
`;
}
/**
* sets the white value for RGBW/RGBWW lights using modern color mode API
* falls back to legacy white_value for older HA installations
* @param {CustomEvent} event
* @param {LightEntity} stateObj
* @param {number} index - 3 for white/cool white, 4 for warm white
*/
_setWhiteValue(event, stateObj, index) {
const newValue = parseInt(event.target.value, 10);
if (isNaN(newValue)) return;
const colorModes = stateObj.attributes.supported_color_modes || [];
const rgbwColor = stateObj.attributes.rgbw_color;
const rgbwwColor = stateObj.attributes.rgbww_color;
if (colorModes.includes('rgbw') && index === 3) {
const rgb = rgbwColor ? rgbwColor.slice(0, 3) : (stateObj.attributes.rgb_color || [255, 255, 255]);
this.callEntityService({ rgbw_color: [rgb[0], rgb[1], rgb[2], newValue] }, stateObj);
} else if (colorModes.includes('rgbww')) {
let base;
if (rgbwwColor) {
base = rgbwwColor;
} else if (rgbwColor) {
base = [rgbwColor[0], rgbwColor[1], rgbwColor[2], rgbwColor[3], 0];
} else {
const rgb = stateObj.attributes.rgb_color || [255, 255, 255];
base = [rgb[0], rgb[1], rgb[2], 0, 0];
}
const newColor = [...base];
newColor[index] = newValue;
this.callEntityService({ rgbww_color: newColor }, stateObj);
} else {
// Legacy fallback
this.callEntityService({ white_value: newValue }, stateObj);
}
}
/**
* creates effect list dropdown for a given entity
* @param {LightEntity} stateObj
* @return {TemplateResult}
*/
createEffectList(stateObj) {
// do we disable effect list always?
if (this.config.effects_list === false) return html``;
// need to check state and persist_features here because if given custom effect list we may
// want to sho that even if the feature doesn't exist so dont check that part to move forward just persist_features/state
if (!this.config.persist_features && !this.isEntityOn(stateObj)) return html``;
let effect_list = stateObj.attributes.effect_list || [];
// if we were given a custom list then use that
if (this.config.effects_list && Array.isArray(this.config.effects_list)) {
effect_list = this.config.effects_list;
} else if (this.config.effects_list && this.hass.states[this.config.effects_list]) {
// else if given an input_select entity use that as effect list
const inputSelect = this.hass.states[this.config.effects_list];
effect_list = (inputSelect.attributes && inputSelect.attributes.options) || [];
} else if (this.dontShowFeature('effectList', stateObj)) {
// finally if no custom list nor feature exists then dont show effect list
return html``;
}
const listItems = effect_list.map(effect => this.createListItem(stateObj, effect));
const caption = this.hass.localize('ui.card.light.effect') || 'Effect';
return html`
<div class="control light-entity-card-center light-entity-card-effectlist">
<ha-select
@closed="${e => e.stopPropagation()}"
@selected=${e => this.setEffect(e, stateObj)}
label="${caption}"
>
${listItems}
</ha-select>
</div>
`;
}
createListItem(stateObj, effect) {
return html`<mwc-list-item value="${effect}" ?selected=${effect === stateObj.attributes.effect}
>${effect}</mwc-list-item
>`;
}
/**
* creates color picker wheel for a given entity
* @param {LightEntity} stateObj
* @return {TemplateResult}
*/
createColorPicker(stateObj) {
if (this.config.color_picker === false) return html``;
if (this.dontShowFeature('color', stateObj)) return html``;
// ha-hs-color-picker uses saturation 0-1, HA uses 0-100
const haHs = stateObj.attributes.hs_color || [0, 0];
const pickerValue = (this._colorPickerValues && this._colorPickerValues[stateObj.entity_id])
|| [haHs[0], haHs[1] / 100];
return html`
<div class="light-entity-card__color-picker">
<ha-hs-color-picker
.value=${pickerValue}
@cursor-moved=${(e) => { this._colorPickerValues = { ...this._colorPickerValues, [stateObj.entity_id]: e.detail.value }; }}
@value-changed=${(e) => this._onColorPickerChanged(e.detail.value, stateObj)}
></ha-hs-color-picker>
</div>
`;
}
/**
* do we show a feature or not?
* @param {string} featureName
* @param {LightEntity} stateObj
* @return {boolean}
*/
dontShowFeature(featureName, stateObj) {
// show all feature if this is set to true
if (this.config.force_features) return false;
// WLED support
if (featureName === 'speed' && 'speed' in stateObj.attributes) return false;
if (featureName === 'intensity' && 'intensity' in stateObj.attributes) return false;
// old deprecated way to seeing if supported feature
let featureSupported = LightEntityCard.featureNames[featureName] & stateObj.attributes.supported_features;
// support new color modes https://developers.home-assistant.io/docs/core/entity/light/#color-modes
const colorModes = stateObj.attributes.supported_color_modes || [];
if (!featureSupported) {
switch (featureName) {
case 'brightness':
featureSupported = Object.prototype.hasOwnProperty.call(stateObj.attributes, 'brightness');
if (!featureSupported) {
const supportedModes = ['hs', 'rgb', 'rgbw', 'rgbww', 'white', 'brightness', 'color_temp', 'xy'];
featureSupported = [...new Set(colorModes.filter(mode => supportedModes.includes(mode)))].length > 0;
}
break;
case 'colorTemp':
if (colorModes) {
const supportedModes = ['color_temp'];
featureSupported = [...new Set(colorModes.filter(mode => supportedModes.includes(mode)))].length > 0;
}
break;
case 'effectList':
featureSupported = stateObj.attributes.effect_list && stateObj.attributes.effect_list.length;
break;
case 'color': {
const supportedModes = ['hs', 'rgb', 'rgbw', 'rgbww', 'xy'];
featureSupported = [...new Set(colorModes.filter(mode => supportedModes.includes(mode)))].length > 0;
break;
}
case 'whiteValue':
featureSupported = Object.prototype.hasOwnProperty.call(stateObj.attributes, 'white_value');
if (!featureSupported) {
const supportedModes = ['rgbw', 'rgbww'];
featureSupported = colorModes.some(mode => supportedModes.includes(mode));
}
break;
case 'warmWhiteValue': {
const supportedModes = ['rgbww'];
featureSupported = colorModes.some(mode => supportedModes.includes(mode));
break;
}
default:
featureSupported = false;
break;
}
}
if (!featureSupported) return true;
if (!this.config.persist_features && !this.isEntityOn(stateObj)) return true;
return false;
}
/**
* change to hs color for a given entity
* @param {HSV} hsv
* @param {LightEntity} stateObj
*/
_onColorPickerChanged(value, stateObj) {
if (this._colorPickerValues) {
const { [stateObj.entity_id]: _, ...rest } = this._colorPickerValues;
this._colorPickerValues = rest;
}
this.setColorPicker(value, stateObj);
}
setColorPicker(value, stateObj) {
if (!value) return;
// Convert saturation back from 0-1 to 0-100 for HA
this.callEntityService({ hs_color: [value[0], value[1] * 100] }, stateObj);
}
_setValue(event, stateObj, valueName) {
const newValue = parseInt(event.target.value, 10);
if (isNaN(newValue) || parseInt(stateObj.attributes[valueName], 10) === newValue) return;
this.callEntityService({ [valueName]: newValue }, stateObj);
}
/**
* handles color temperature slider changes, converting to the correct unit for HA
* @param {CustomEvent} event
* @param {LightEntity} stateObj
* @param {boolean} usesKelvin - whether the HA entity uses kelvin-based attributes
* @param {boolean} sliderInKelvin - whether the slider value is in kelvin
* @param {number} [minMired] - min mired value (needed for percentage→mired conversion)
* @param {number} [maxMired] - max mired value (needed for percentage→mired conversion)
*/
_setColorTemp(event, stateObj, usesKelvin, sliderInKelvin, minMired, maxMired) {
const rawValue = parseInt(event.target.value, 10);
if (isNaN(rawValue)) return;
if (sliderInKelvin) {
// Slider value is kelvin
if (usesKelvin) {
if (rawValue === parseInt(stateObj.attributes.color_temp_kelvin, 10)) return;
this.callEntityService({ color_temp_kelvin: rawValue }, stateObj);
} else {
const miredValue = Math.round(1000000 / rawValue);
if (miredValue === parseInt(stateObj.attributes.color_temp, 10)) return;
this.callEntityService({ color_temp: miredValue }, stateObj);
}
} else {
// Slider value is percentage (0–100), convert back to mireds
if (!Number.isFinite(minMired) || !Number.isFinite(maxMired) || maxMired <= minMired) return;
const miredValue = Math.round(minMired + (rawValue / 100) * (maxMired - minMired));
if (usesKelvin) {
const kelvinValue = Math.round(1000000 / miredValue);
if (kelvinValue === parseInt(stateObj.attributes.color_temp_kelvin, 10)) return;
this.callEntityService({ color_temp_kelvin: kelvinValue }, stateObj);
} else {
if (miredValue === parseInt(stateObj.attributes.color_temp, 10)) return;
this.callEntityService({ color_temp: miredValue }, stateObj);
}
}
}
/**
* sets the toggle state based on the given entity state
* @param {CustomEvent} event
* @param {LightEntity} stateObj
*/
setToggle(event, stateObj) {
const newState = this.isEntityOn(stateObj) ? LightEntityCard.cmdToggle.off : LightEntityCard.cmdToggle.on;
this.callEntityService({}, stateObj, newState);
}
/**
* sets the current effect selected for an entity
* @param {CustomEvent} event
* @param {LightEntity} entity
*/
setEffect(event, stateObj) {
if(!event.target.value ) return;
this.callEntityService({ effect: event.target.value }, stateObj);
}
/**
* call light service to update a state of an entity
* @param {Object} payload
* @param {LightEntity} entity
* @param {String} state
*/
callEntityService(payload, stateObj, state) {
if(!this._firstUpdate) return;
let entityType = stateObj.entity_id.split('.')[0];
if (entityType === 'group') entityType = 'homeassistant';
const transition = parseFloat(this.config.transition) || 0;
if (transition > 0 && entityType === 'light') {
payload = { ...payload, transition };
}
this.hass.callService(entityType, state || LightEntityCard.cmdToggle.on, {
entity_id: stateObj.entity_id,
...payload,
});
}
}
customElements.define('light-entity-card', LightEntityCard);
window.customCards = window.customCards || [];
window.customCards.push({
type: 'light-entity-card',
name: 'Light Entity Card',
description: 'Control lights and switches',
});
================================================
FILE: src/style-editor.js
================================================
import { css } from 'lit';
const style = css`
.entities {
padding-top: 10px;
padding-bottom: 10px;
display: flex;
}
.entities ha-formfield {
display: block;
margin-bottom: 10px;
margin-left: 10px;
}
.checkbox-options {
display: flex;
}
ha-entity-picker {
width: 100%;
}
.checkbox-options ha-formfield,
.entities mwc-switch,
.entities ha-form-string {
padding-right: 2%;
width: 48%;
}
.checkbox-options ha-formfield {
margin-top: 10px;
}
.overall-config {
margin-bottom: 20px;
}
`;
export default style;
================================================
FILE: src/style.js
================================================
import { css } from 'lit';
const style = css`
.light-entity-card {
padding: 16px;
}
.light-entity-child-card {
box-shadow: none !important;
padding: 0 !important;
}
.light-entity-card.group {
padding-bottom: 5;
padding-top: 0;
}
.ha-slider-full-width ha-slider {
width: 100%;
}
.percent-slider {
color: var(--primary-text-color);
display: flex;
justify-content: center;
align-items: center;
margin-left: 8px;
min-width: 40px;
text-align: right;
}
.light-entity-card__header {
display: flex;
justify-content: space-between;
@apply --paper-font-headline;
line-height: 40px;
color: var(--primary-text-color);
}
.group .light-entity-card__header {
}
.light-entity-card-sliders > div {
margin-top: 10px;
}
.group .light-entity-card-sliders > div {
margin-top: 0px;
}
.light-entity-card__toggle {
display: flex;
cursor: pointer;
}
.light-entity-card__color-picker {
display: flex;
justify-content: center;
margin-top: 10px;
}
.light-entity-card__color-picker ha-hs-color-picker {
max-width: 300px;
width: 100%;
}
.light-entity-card-color_temp {
background-image: var(--ha-slider-background, linear-gradient(to right, #a6d1ff, #ffb74d));
border-radius: 4px;
}
.light-entity-card-color_temp--kelvin {
background-image: var(--ha-slider-background, linear-gradient(to right, #ffb74d, #a6d1ff));
}
.light-entity-card-effectlist {
padding-top: 10px;
padding-bottom: 10px;
}
.group .light-entity-card-effectlist {
padding-bottom: 20px;
}
.light-entity-card-center {
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.hidden {
display: none;
}
.icon-container {
display: flex;
justify-content: center;
align-items: center;
margin-right: 8px;
}
`;
export default style;
================================================
FILE: webpack/config.common.js
================================================
const path = require("path");
module.exports = {
entry: "./src/index.js",
devtool: "source-map",
output: {
filename: "light-entity-card.js",
path: path.resolve(__dirname, "../dist"),
},
module: {
rules: [
{
test: /\.js$/,
include: [/node_modules(?:\/|\\)lit-element|lit-html/],
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
},
],
},
};
================================================
FILE: webpack/config.dev.js
================================================
const { merge } = require('webpack-merge');
const commonConfig = require('./config.common');
module.exports = merge(commonConfig, {
mode: 'development'
});
================================================
FILE: webpack/config.prod.js
================================================
const { merge } = require('webpack-merge');
const commonConfig = require('./config.common');
module.exports = merge(commonConfig, {
mode: 'production',
devtool: false,
optimization: {
minimize: true
},
output: {
publicPath: '/local/'
},
});
gitextract_6su_67oe/
├── .babelrc
├── .eslintrc.yaml
├── .gitattributes
├── .github/
│ └── workflows/
│ ├── deploy.yaml
│ └── validate.yaml
├── .gitignore
├── .prettierrc.json
├── LICENSE
├── README.md
├── babel.config.js
├── dist/
│ ├── light-entity-card.js
│ └── light-entity-card.js.LICENSE.txt
├── hacs.json
├── package.json
├── rollup-plugins/
│ └── ignore.js
├── src/
│ ├── buildElementDefinitions.js
│ ├── color-wheel/
│ │ ├── color-picker.js
│ │ ├── convert-color.js
│ │ ├── events-mixin.js
│ │ └── fire_event.js
│ ├── defaults.js
│ ├── globalElementLoader.js
│ ├── index-editor.js
│ ├── index.js
│ ├── style-editor.js
│ └── style.js
└── webpack/
├── config.common.js
├── config.dev.js
└── config.prod.js
SYMBOL INDEX (199 symbols across 6 files)
FILE: dist/light-entity-card.js
class r (line 2) | class r{constructor(t,e,n){if(this._$cssResult$=!0,n!==i)throw Error("CS...
method constructor (line 2) | constructor(t,e,n){if(this._$cssResult$=!0,n!==i)throw Error("CSSResul...
method styleSheet (line 2) | get styleSheet(){let t=this.o;const i=this.t;if(e&&void 0===t){const e...
method toString (line 2) | toString(){return this.cssText}
method toAttribute (line 2) | toAttribute(t,e){switch(e){case Boolean:t=t?d:null;break;case Object:cas...
method fromAttribute (line 2) | fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case ...
class v (line 2) | class v extends HTMLElement{constructor(){super(),this._$Ei=new Map,this...
method constructor (line 2) | constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.h...
method addInitializer (line 2) | static addInitializer(t){var e;this.finalize(),(null!==(e=this.h)&&voi...
method observedAttributes (line 2) | static get observedAttributes(){this.finalize();const t=[];return this...
method createProperty (line 2) | static createProperty(t,e=g){if(e.state&&(e.attribute=!1),this.finaliz...
method getPropertyDescriptor (line 2) | static getPropertyDescriptor(t,e,i){return{get(){return this[e]},set(n...
method getPropertyOptions (line 2) | static getPropertyOptions(t){return this.elementProperties.get(t)||g}
method finalize (line 2) | static finalize(){if(this.hasOwnProperty(_))return!1;this[_]=!0;const ...
method finalizeStyles (line 2) | static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new S...
method _$Ep (line 2) | static _$Ep(t,e){const i=e.attribute;return!1===i?void 0:"string"==typ...
method _$Eu (line 2) | _$Eu(){var t;this._$E_=new Promise(t=>this.enableUpdating=t),this._$AL...
method addController (line 2) | addController(t){var e,i;(null!==(e=this._$ES)&&void 0!==e?e:this._$ES...
method removeController (line 2) | removeController(t){var e;null===(e=this._$ES)||void 0===e||e.splice(t...
method _$Eg (line 2) | _$Eg(){this.constructor.elementProperties.forEach((t,e)=>{this.hasOwnP...
method createRenderRoot (line 2) | createRenderRoot(){var t;const e=null!==(t=this.shadowRoot)&&void 0!==...
method connectedCallback (line 2) | connectedCallback(){var t;void 0===this.renderRoot&&(this.renderRoot=t...
method enableUpdating (line 2) | enableUpdating(t){}
method disconnectedCallback (line 2) | disconnectedCallback(){var t;null===(t=this._$ES)||void 0===t||t.forEa...
method attributeChangedCallback (line 2) | attributeChangedCallback(t,e,i){this._$AK(t,i)}
method _$EO (line 2) | _$EO(t,e,i=g){var n;const r=this.constructor._$Ep(t,i);if(void 0!==r&&...
method _$AK (line 2) | _$AK(t,e){var i;const n=this.constructor,r=n._$Ev.get(t);if(void 0!==r...
method requestUpdate (line 2) | requestUpdate(t,e,i){let n=!0;void 0!==t&&(((i=i||this.constructor.get...
method _$Ej (line 2) | async _$Ej(){this.isUpdatePending=!0;try{await this._$E_}catch(t){Prom...
method scheduleUpdate (line 2) | scheduleUpdate(){return this.performUpdate()}
method performUpdate (line 2) | performUpdate(){var t;if(!this.isUpdatePending)return;this.hasUpdated,...
method willUpdate (line 2) | willUpdate(t){}
method _$AE (line 2) | _$AE(t){var e;null===(e=this._$ES)||void 0===e||e.forEach(t=>{var e;re...
method _$Ek (line 2) | _$Ek(){this._$AL=new Map,this.isUpdatePending=!1}
method updateComplete (line 2) | get updateComplete(){return this.getUpdateComplete()}
method getUpdateComplete (line 2) | getUpdateComplete(){return this._$E_}
method shouldUpdate (line 2) | shouldUpdate(t){return!0}
method update (line 2) | update(t){void 0!==this._$EC&&(this._$EC.forEach((t,e)=>this._$EO(e,th...
method updated (line 2) | updated(t){}
method firstUpdated (line 2) | firstUpdated(t){}
function y (line 2) | function y(t,e,i){return e=b(e),function(t,e){if(e&&("object"==O(e)||"fu...
function m (line 2) | function m(){try{var t=!Boolean.prototype.valueOf.call(Reflect.construct...
function b (line 2) | function b(t){return b=Object.setPrototypeOf?Object.getPrototypeOf.bind(...
function $ (line 2) | function $(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
function w (line 2) | function w(t,e){return w=Object.setPrototypeOf?Object.setPrototypeOf.bin...
function k (line 2) | function k(t,e){var i="undefined"!=typeof Symbol&&t[Symbol.iterator]||t[...
function S (line 2) | function S(t,e){if(t){if("string"==typeof t)return x(t,e);var i={}.toStr...
function x (line 2) | function x(t,e){(null==e||e>t.length)&&(e=t.length);for(var i=0,n=Array(...
function C (line 2) | function C(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
function A (line 2) | function A(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.en...
function E (line 2) | function E(t,e,i){return e&&A(t.prototype,e),i&&A(t,i),Object.defineProp...
function P (line 2) | function P(t){var e=function(t){if("object"!=O(t)||!t)return t;var e=t[S...
function O (line 2) | function O(t){return O="function"==typeof Symbol&&"symbol"==typeof Symbo...
function nt (line 2) | function nt(t,e){if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Er...
function st (line 2) | function st(t,e){var i,n,r,o,s=arguments.length>2&&void 0!==arguments[2]...
function t (line 2) | function t(e,i,n,r){var o;C(this,t),this.type=2,this._$AH=tt,this._$AN=v...
function e (line 2) | function e(){var t;return C(this,e),(t=y(this,e,arguments)).type=3,t}
function e (line 2) | function e(){var t;return C(this,e),(t=y(this,e,arguments)).type=4,t}
function e (line 2) | function e(t,i,n,r,o){var s;return C(this,e),(s=y(this,e,[t,i,n,r,o])).t...
function yt (line 2) | function yt(t){return yt="function"==typeof Symbol&&"symbol"==typeof Sym...
function mt (line 2) | function mt(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.e...
function bt (line 2) | function bt(t){var e=function(t){if("object"!=yt(t)||!t)return t;var e=t...
function $t (line 2) | function $t(t){if(void 0===t)throw new ReferenceError("this hasn't been ...
function wt (line 2) | function wt(){try{var t=!Boolean.prototype.valueOf.call(Reflect.construc...
function kt (line 2) | function kt(t,e,i,n){var r=St(xt(1&n?t.prototype:t),e,i);return 2&n&&"fu...
function St (line 2) | function St(){return St="undefined"!=typeof Reflect&&Reflect.get?Reflect...
function xt (line 2) | function xt(t){return xt=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function Ct (line 2) | function Ct(t,e){return Ct=Object.setPrototypeOf?Object.setPrototypeOf.b...
function e (line 2) | function e(){var t;return function(t,e){if(!(t instanceof e))throw new T...
function Tt (line 2) | function Tt(t){return class extends t{createRenderRoot(){const t=this.co...
class Vt (line 137) | class Vt extends(Tt(At)){static get elementDefinitions(){return Nt([Rt("...
method elementDefinitions (line 137) | static get elementDefinitions(){return Nt([Rt("ha-checkbox"),Rt("ha-fo...
method styles (line 137) | static get styles(){return It}
method properties (line 137) | static get properties(){return{hass:{},_config:{}}}
method setConfig (line 137) | setConfig(t){this._config={...Ut,...t}}
method entityDomains (line 137) | get entityDomains(){return["switch","light","group"]}
method firstUpdated (line 137) | firstUpdated(){this._firstRendered=!0}
method render (line 137) | render(){if(!this.hass||!this._config)return X``;let{header:t}=this._c...
method entityChanged (line 380) | entityChanged(t){if(!this._config||!this.hass||!this._firstRendered)re...
method configChanged (line 380) | configChanged(t){if(!this._config||!this.hass||!this._firstRendered)re...
method checkboxConfigChanged (line 380) | checkboxConfigChanged(t){if(!this._config||!this.hass||!this._firstRen...
class Wt (line 380) | class Wt extends(Tt(At)){static get elementDefinitions(){return Nt([Rt("...
method elementDefinitions (line 380) | static get elementDefinitions(){return Nt([Rt("ha-card"),Rt("more-info...
method properties (line 380) | static get properties(){return{hass:{},config:{},_colorPickerValues:{s...
method firstUpdated (line 380) | async firstUpdated(){if(this._firstUpdate=!0,!1!==this.config.color_pi...
method setConfig (line 380) | setConfig(t){if(!t.entity)throw Error("entity required.");this.config=...
method getConfigElement (line 380) | static async getConfigElement(){return document.createElement(Mt)}
method featureNames (line 380) | static get featureNames(){return{brightness:1,colorTemp:2,effectList:4...
method cmdToggle (line 380) | static get cmdToggle(){return{on:"turn_on",off:"turn_off"}}
method entityLength (line 380) | static get entityLength(){return{light:10,switch:1}}
method getCardSize (line 380) | getCardSize(){if(!this.config||!this.__hass||!this.__hass.states[this....
method getEntityLength (line 380) | getEntityLength(t){return/^light\./.test(t)?Wt.entityLength.light:/^sw...
method styles (line 380) | get styles(){return jt}
method isEntityOn (line 380) | isEntityOn(t){return"on"===t.state}
method render (line 380) | render(){const t=this.hass.states[this.config.entity];if(!t)return X`
method getEntitiesToShow (line 392) | getEntitiesToShow(t){return t.attributes.entity_id&&Array.isArray(t.at...
method createEntityTemplate (line 392) | createEntityTemplate(t){const e=this.config.full_width_sliders?"ha-sli...
method createHeader (line 401) | createHeader(t){if(this.config.hide_header)return X``;const e=this.con...
method showHeaderIcon (line 409) | showHeaderIcon(t){return this.config.show_header_icon?X`
method createBrightnessSlider (line 413) | createBrightnessSlider(t){return!1===this.config.brightness||this.dont...
method createSpeedSlider (line 426) | createSpeedSlider(t){return!1===this.config.speed||this.dontShowFeatur...
method createIntensitySlider (line 439) | createIntensitySlider(t){return!1===this.config.intensity||this.dontSh...
method showPercent (line 452) | showPercent(t,e,i,n){if("brightness"===n&&void 0!==this.config.show_br...
method createColorTemperature (line 452) | createColorTemperature(t){if(!1===this.config.color_temp)return X``;if...
method getWhiteValue (line 482) | getWhiteValue(t,e=3){const i=t.attributes.rgbw_color,n=t.attributes.rg...
method createWhiteValue (line 482) | createWhiteValue(t){if(!1===this.config.white_value)return X``;if(this...
method createWarmWhiteValue (line 495) | createWarmWhiteValue(t){if(!1===this.config.warm_white_value)return X`...
method _setWhiteValue (line 508) | _setWhiteValue(t,e,i){const n=parseInt(t.target.value,10);if(isNaN(n))...
method createEffectList (line 508) | createEffectList(t){if(!1===this.config.effects_list)return X``;if(!th...
method createListItem (line 518) | createListItem(t,e){return X`<mwc-list-item value="${e}" ?selected=${e...
method createColorPicker (line 520) | createColorPicker(t){if(!1===this.config.color_picker)return X``;if(th...
method dontShowFeature (line 528) | dontShowFeature(t,e){if(this.config.force_features)return!1;if("speed"...
method _onColorPickerChanged (line 528) | _onColorPickerChanged(t,e){if(this._colorPickerValues){const{[e.entity...
method setColorPicker (line 528) | setColorPicker(t,e){t&&this.callEntityService({hs_color:[t[0],100*t[1]...
method _setValue (line 528) | _setValue(t,e,i){const n=parseInt(t.target.value,10);isNaN(n)||parseIn...
method _setColorTemp (line 528) | _setColorTemp(t,e,i,n,r,o){const s=parseInt(t.target.value,10);if(!isN...
method setToggle (line 528) | setToggle(t,e){const i=this.isEntityOn(e)?Wt.cmdToggle.off:Wt.cmdToggl...
method setEffect (line 528) | setEffect(t,e){t.target.value&&this.callEntityService({effect:t.target...
method callEntityService (line 528) | callEntityService(t,e,i){if(!this._firstUpdate)return;let n=e.entity_i...
FILE: rollup-plugins/ignore.js
method load (line 16) | load(id) {
FILE: src/color-wheel/color-picker.js
class LmeColorPicker (line 6) | class LmeColorPicker extends ScopedRegistryHost(LitElement) {
method properties (line 7) | static get properties() {
method render (line 81) | render() {
method firstUpdated (line 177) | firstUpdated() {
method convertToCanvasCoordinates (line 202) | convertToCanvasCoordinates(clientX, clientY) {
method onMouseDown (line 214) | onMouseDown(ev) {
method onMouseUp (line 228) | onMouseUp() {
method onMouseSelect (line 233) | onMouseSelect(ev) {
method onTouchStart (line 238) | onTouchStart(ev) {
method onTap (line 266) | onTap(ev) {
method onTouchEnd (line 274) | onTouchEnd() {
method onTouchSelect (line 279) | onTouchSelect(ev) {
method processUserSelect (line 288) | processUserSelect(ev) {
method onColorSelect (line 302) | onColorSelect(hs, rgb) {
method fireColorSelected (line 329) | fireColorSelected(hs, rgb) {
method setMarkerOnColor (line 339) | setMarkerOnColor(hs) {
method applyColorToCanvas (line 353) | applyColorToCanvas(hs) {
method applyHsColor (line 364) | applyHsColor(hs) {
method applyRgbColor (line 379) | applyRgbColor(rgb) {
method getAngle (line 389) | getAngle(dX, dY) {
method isInWheel (line 396) | isInWheel(x, y) {
method getDistance (line 401) | getDistance(dX, dY) {
method getColor (line 409) | getColor(x, y) {
method getRgbColor (line 416) | getRgbColor(x, y) {
method applySegmentFilter (line 425) | applySegmentFilter(hs) {
method setupLayers (line 456) | setupLayers() {
method drawColorWheel (line 474) | drawColorWheel() {
method drawMarker (line 607) | drawMarker() {
method segmentationChange (line 635) | segmentationChange() {
FILE: src/color-wheel/events-mixin.js
method fire (line 53) | fire(type, detail, options) {
FILE: src/index-editor.js
class LightEntityCardEditor (line 20) | class LightEntityCardEditor extends ScopedRegistryHost(LitElement) {
method elementDefinitions (line 21) | static get elementDefinitions() {
method styles (line 30) | static get styles() {
method properties (line 34) | static get properties() {
method setConfig (line 38) | setConfig(config) {
method entityDomains (line 45) | get entityDomains() {
method firstUpdated (line 49) | firstUpdated() {
method render (line 53) | render() {
method entityChanged (line 314) | entityChanged(ev) {
method configChanged (line 322) | configChanged(ev) {
method checkboxConfigChanged (line 338) | checkboxConfigChanged(ev) {
FILE: src/index.js
class LightEntityCard (line 17) | class LightEntityCard extends ScopedRegistryHost(LitElement) {
method elementDefinitions (line 18) | static get elementDefinitions() {
method properties (line 35) | static get properties() {
method firstUpdated (line 43) | async firstUpdated() {
method setConfig (line 89) | setConfig(config) {
method getConfigElement (line 98) | static async getConfigElement() {
method featureNames (line 103) | static get featureNames() {
method cmdToggle (line 113) | static get cmdToggle() {
method entityLength (line 120) | static get entityLength() {
method getCardSize (line 131) | getCardSize() {
method getEntityLength (line 159) | getEntityLength(entity_id) {
method styles (line 173) | get styles() {
method isEntityOn (line 183) | isEntityOn(stateObj) {
method render (line 191) | render() {
method getEntitiesToShow (line 236) | getEntitiesToShow(entities) {
method createEntityTemplate (line 248) | createEntityTemplate(stateObj) {
method createHeader (line 268) | createHeader(stateObj) {
method showHeaderIcon (line 283) | showHeaderIcon(stateObj) {
method createBrightnessSlider (line 298) | createBrightnessSlider(stateObj) {
method createSpeedSlider (line 323) | createSpeedSlider(stateObj) {
method createIntensitySlider (line 348) | createIntensitySlider(stateObj) {
method showPercent (line 376) | showPercent(value, min, max, sliderType) {
method createColorTemperature (line 404) | createColorTemperature(stateObj) {
method getWhiteValue (line 505) | getWhiteValue(stateObj, index = 3) {
method createWhiteValue (line 526) | createWhiteValue(stateObj) {
method createWarmWhiteValue (line 553) | createWarmWhiteValue(stateObj) {
method _setWhiteValue (line 582) | _setWhiteValue(event, stateObj, index) {
method createEffectList (line 617) | createEffectList(stateObj) {
method createListItem (line 655) | createListItem(stateObj, effect) {
method createColorPicker (line 666) | createColorPicker(stateObj) {
method dontShowFeature (line 692) | dontShowFeature(featureName, stateObj) {
method _onColorPickerChanged (line 758) | _onColorPickerChanged(value, stateObj) {
method setColorPicker (line 766) | setColorPicker(value, stateObj) {
method _setValue (line 772) | _setValue(event, stateObj, valueName) {
method _setColorTemp (line 788) | _setColorTemp(event, stateObj, usesKelvin, sliderInKelvin, minMired, m...
method setToggle (line 822) | setToggle(event, stateObj) {
method setEffect (line 832) | setEffect(event, stateObj) {
method callEntityService (line 843) | callEntityService(payload, stateObj, state) {
Condensed preview — 29 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (144K chars).
[
{
"path": ".babelrc",
"chars": 456,
"preview": "{\n \"presets\": [\n [\n \"@babel/preset-env\",\n {\n \"modules\": false,\n \"targets\": {\n \"esmo"
},
{
"path": ".eslintrc.yaml",
"chars": 421,
"preview": "extends: airbnb-base\r\nrules:\r\n no-else-return: 0\r\n no-underscore-dangle: 0\r\n nonblock-statement-body-position: 0\r\n c"
},
{
"path": ".gitattributes",
"chars": 16,
"preview": "*.js text eol=lf"
},
{
"path": ".github/workflows/deploy.yaml",
"chars": 2184,
"preview": "name: Build and Release on Tag\n\non:\n workflow_dispatch:\n push:\n tags:\n - '*'\n\njobs:\n build-and-release:\n r"
},
{
"path": ".github/workflows/validate.yaml",
"chars": 281,
"preview": "name: Validate\n\non:\n push:\n pull_request:\n schedule:\n - cron: \"0 0 * * *\"\n\njobs:\n validate:\n runs-on: \"ubuntu-"
},
{
"path": ".gitignore",
"chars": 16,
"preview": "/node_modules/\r\n"
},
{
"path": ".prettierrc.json",
"chars": 99,
"preview": "{\n \"trailingComma\": \"es5\",\n \"singleQuote\": true,\n \"printWidth\": 120,\n \"arrowParens\": \"avoid\"\n}\n"
},
{
"path": "LICENSE",
"chars": 1071,
"preview": "MIT License\n\nCopyright (c) 2019 Leonardo Merza\n\nPermission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "README.md",
"chars": 6097,
"preview": "# Light Entity Card\r\n\r\nControl any light/switch entity through lovelace\r\n\r\n## Support\r\n\r\nFor help, visit the light entit"
},
{
"path": "babel.config.js",
"chars": 284,
"preview": "module.exports = {\n \"presets\": [\n [\n \"@babel/preset-env\",\n {\n \"useBuiltIn"
},
{
"path": "dist/light-entity-card.js",
"chars": 53588,
"preview": "/*! For license information please see light-entity-card.js.LICENSE.txt */\n(()=>{\"use strict\";const t=window,e=t.ShadowR"
},
{
"path": "dist/light-entity-card.js.LICENSE.txt",
"chars": 272,
"preview": "/**\n * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * @license\n * Copyright "
},
{
"path": "hacs.json",
"chars": 102,
"preview": "{\n \"name\": \"Light Entity Card\",\n \"render_readme\": true,\n \"filename\": \"light-entity-card.js\"\n}"
},
{
"path": "package.json",
"chars": 1261,
"preview": "{\r\n \"name\": \"light-entity-card\",\r\n \"version\": \"6.3.1\",\r\n \"description\": \"A light-entity card for Home Assistant Lovel"
},
{
"path": "rollup-plugins/ignore.js",
"chars": 483,
"preview": "export default function (userOptions = {}) {\n // Files need to be absolute paths.\n // This only works if the file has "
},
{
"path": "src/buildElementDefinitions.js",
"chars": 482,
"preview": "const buildElementDefinitions = (elements, constructor) => elements.reduce((aggregate, element) => {\n if (element.defin"
},
{
"path": "src/color-wheel/color-picker.js",
"chars": 18320,
"preview": "import { LitElement, html } from 'lit';\nimport { ScopedRegistryHost } from '@lit-labs/scoped-registry-mixin';\n\nimport { "
},
{
"path": "src/color-wheel/convert-color.js",
"chars": 600,
"preview": "export const rgb2hsv = (rgb) => {\n const [r, g, b] = rgb;\n const v = Math.max(r, g, b);\n const c = v - Math.min(r, g,"
},
{
"path": "src/color-wheel/events-mixin.js",
"chars": 2657,
"preview": "import { dedupingMixin } from \"@polymer/polymer/lib/utils/mixin\";\nimport { fireEvent } from \"./fire_event\";\n\n// Polymer "
},
{
"path": "src/color-wheel/fire_event.js",
"chars": 2704,
"preview": "// Polymer legacy event helpers used courtesy of the Polymer project.\n//\n// Copyright (c) 2017 The Polymer Authors. All "
},
{
"path": "src/defaults.js",
"chars": 706,
"preview": "export default {\n shorten_cards: false,\n consolidate_entities: false,\n child_card: false,\n hide_header: false,\n sho"
},
{
"path": "src/globalElementLoader.js",
"chars": 170,
"preview": "const globalElementLoader = name => ({\n name,\n promise: customElements.whenDefined(name).then(() => customElements.get"
},
{
"path": "src/index-editor.js",
"chars": 12064,
"preview": "import { LitElement, html } from 'lit';\nimport { ScopedRegistryHost } from '@lit-labs/scoped-registry-mixin';\nimport sty"
},
{
"path": "src/index.js",
"chars": 29586,
"preview": "import { LitElement, html } from 'lit';\n\nimport { ScopedRegistryHost } from '@lit-labs/scoped-registry-mixin';\n\nimport s"
},
{
"path": "src/style-editor.js",
"chars": 593,
"preview": "import { css } from 'lit';\n\nconst style = css`\n .entities {\n padding-top: 10px;\n padding-bottom: 10px;\n displa"
},
{
"path": "src/style.js",
"chars": 1952,
"preview": "import { css } from 'lit';\n\nconst style = css`\n .light-entity-card {\n padding: 16px;\n }\n\n .light-entity-child-card"
},
{
"path": "webpack/config.common.js",
"chars": 485,
"preview": "const path = require(\"path\");\n\nmodule.exports = {\n entry: \"./src/index.js\",\n devtool: \"source-map\",\n output: {\n fi"
},
{
"path": "webpack/config.dev.js",
"chars": 161,
"preview": "const { merge } = require('webpack-merge');\nconst commonConfig = require('./config.common');\n\n\nmodule.exports = merge(co"
},
{
"path": "webpack/config.prod.js",
"chars": 282,
"preview": "const { merge } = require('webpack-merge');\nconst commonConfig = require('./config.common');\n\n\nmodule.exports = merge(co"
}
]
About this extraction
This page contains the full source code of the ljmerza/light-entity-card GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 29 files (134.2 KB), approximately 36.7k tokens, and a symbol index with 199 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.