[
  {
    "path": ".github/workflows/build.yaml",
    "content": "name: \"Build\"\r\n\r\non:\r\n  push:\r\n    branches:\r\n      - master\r\n  pull_request:\r\n    branches:\r\n      - master\r\n\r\njobs:\r\n  build:\r\n    name: Test build\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n      - uses: actions/checkout@v2\r\n      - uses: pnpm/action-setup@v4\r\n        with:\r\n          version: latest\r\n      - name: Build\r\n        run: |\r\n          pnpm install\r\n          pnpm run build"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: Release\r\n\r\non:\r\n  release:\r\n    types: [published]\r\n\r\njobs:\r\n  release:\r\n    name: Prepare release\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n      - uses: actions/checkout@v2\r\n      - uses: pnpm/action-setup@v4\r\n        with:\r\n          version: latest\r\n      #Building the Js-File\r\n      - name: Build the file\r\n        run: |\r\n          pnpm install\r\n          pnpm run build\r\n      # Upload build file to the releas as an asset.\r\n      - name: Upload zip to release\r\n        uses: svenstaro/upload-release-action@v1-release\r\n\r\n        with:\r\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\r\n          file: /home/runner/work/power-distribution-card/power-distribution-card/dist/power-distribution-card.js\r\n          asset_name: power-distribution-card.js\r\n          tag: ${{ github.ref }}\r\n          overwrite: true"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*"
  },
  {
    "path": ".prettierrc.js",
    "content": "module.exports = {\r\n  semi: true,\r\n  trailingComma: 'all',\r\n  singleQuote: true,\r\n  printWidth: 120,\r\n  tabWidth: 2,\r\n  endOfLine: 'auto',\r\n};\r\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"typescript.tsdk\": \"node_modules/typescript/lib\"\n}"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 JonahKr\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# power-distribution-card\n[![GitHub package.json version](https://img.shields.io/github/package-json/v/JonahKr/power-distribution-card)](https://github.com/JonahKr/power-distribution-card/blob/master/package.json)\n[![Actions Status](https://github.com/JonahKr/power-distribution-card/workflows/Build/badge.svg)](https://github.com/JonahKr/power-distribution-card/actions)\n[![GitHub license](https://img.shields.io/github/license/JonahKr/power-distribution-card)](https://img.shields.io/github/license/JonahKr/power-distribution-card/blob/master/LICENSE) \n[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/custom-components/hacs)\n<a href=\"https://www.buymeacoffee.com/JonahKr\" target=\"_blank\"><img src=\"https://cdn.buymeacoffee.com/buttons/v2/default-blue.png\" alt=\"Buy Me A Coffee\" height=\"20px\" width=\"83px\" ></a>\n<br/>\n\n<div>\n<i>Inspired by</i>\n<img height=\"40px\" alt=\"e3dc-logo\" href=\"https://www.e3dc.com/en/#Intro\" src=\"https://user-images.githubusercontent.com/38377070/95522835-7de46d80-09cd-11eb-9aae-55657aa3caae.png\"/>\n</div>\n<br/>\n<h1 align=\"center\">The Lovelace Card for visualizing power distributions.</h1>\n<p align=\"center\">\n<img src=\"https://user-images.githubusercontent.com/38377070/103143008-389f2480-470e-11eb-945a-68115febef8a.gif\"/>\n</p>\n\n<br/>\n\n</div>\n\n<div id=\"toc\">\n  <h2> Table of Contents </h2>\n  <ul>\n    <li>\n      <h3><a href=\"#installation\">Installation</a></h3>\n    </li>\n    <li>\n      <h3><a href=\"#configuration\">Configuration</a></h3>\n      <h4><a href=\"#presets\">Presets</a></h4>\n      <h4><a href=\"#simple\">Simple Configuration</a></h4>\n      <h4><a href=\"#yaml\">YAML Configuration</a></h4>\n      <h4><a href=\"#animation\">Animation Options</a></h4>\n      <h4><a href=\"#center\">Center Panel</a></h4>\n      <h4><a href=\"#entity\">Advanced Configuration</a></h4>\n    </li>\n    <li>\n      <h3><a href=\"#faq\">FAQs</a></h2>\n    </li>\n  </ul>\n<br/>\n</div>\n\n<hr>\n\n<br/>\n\n\n<div id=\"installation\">\n<h1> Installation</h1>\n\n<h2> Installation via <a href=\"https://hacs.xyz/\">HACS</a> <img src=\"https://img.shields.io/badge/-Recommended-%2303a9f4\"/> </h2>\n\n1. Make sure the [HACS](https://github.com/custom-components/hacs) custom component is installed and working.\n2. Search for `power-distribution-card` and add it through HACS\n3. Refresh home-assistant.\n\n<h2> Manual installation</h2>\n\n1. Download the latest release of the [power-distribution-card](http://www.github.com/JonahKr/power-distribution-card/releases/latest/download/power-distribution-card.js)\n2. Place the file in your `config/www` folder\n3. Include the card code in your `ui-lovelace-card.yaml`\n  ```yaml\n  resources:\n    - url: /local/power-distribution-card.js\n      type: module\n  ```\n  Or alternatively set it up via the UI: \n  `Configuration -> Lovelace Dashboards -> Resources (TAB)`\n  For more guidance check out the [docs](https://developers.home-assistant.io/docs/frontend/custom-ui/registering-resources/).\n\n<br/>\n</div>\n\n***\n\n<br/>\n\n<div id=\"configuration\">\n<h1> Configuration</h1>\n\n<div id=\"presets\">\n<h3>Presets</h3>\n\nEvery Sensor you want to add has to use one of the Presets. You can add as many of these as you want.\n\n<table style=\"text-align: center;\">\n<tr>\n  <td>\n    <img height=\"60px\" alt=\"mdi-battery-outline\" src=\"https://user-images.githubusercontent.com/38377070/95509029-356c8600-09b4-11eb-834d-1c05cdb9758e.png\"/>\n  <td>\n    <img height=\"60px\" alt=\"mdi-electirc-car\" src=\"https://user-images.githubusercontent.com/38377070/95509040-369db300-09b4-11eb-8caa-046c5b2999d2.png\"/>\n  </td>\n  <td>\n    <img height=\"60px\" alt=\"mdi-lightbulb\" src=\"https://user-images.githubusercontent.com/38377070/95515835-87b2a480-09be-11eb-92af-45a97c895cda.png\"/>\n  </td>\n  <td>\n    <img height=\"60px\" alt=\"mdi-transmission-tower\" src=\"https://user-images.githubusercontent.com/38377070/95508865-ee7e9080-09b3-11eb-8981-eab0969cecac.png\"/>\n  </td>\n  <td>\n    <img height=\"60px\" alt=\"mdi-home-assistant\" src=\"https://user-images.githubusercontent.com/38377070/95509151-66e55180-09b4-11eb-9228-585dcde1d40e.png\"/>\n  </td>\n  <td>\n    <img height=\"60px\" alt=\"mdi-hydro-power\" src=\"https://user-images.githubusercontent.com/38377070/95515201-85037f80-09bd-11eb-8603-31eb4c70b83b.png\"/>\n  </td>\n  <td>\n    <img height=\"60px\" alt=\"mdi-pool\" src=\"https://user-images.githubusercontent.com/38377070/95515296-abc1b600-09bd-11eb-8b81-1e6fbbddb7c3.png\"/>\n  </td>\n  <td>\n    <img height=\"60px\" alt=\"mdi-lightning-bolt-outline\" src=\"https://user-images.githubusercontent.com/38377070/95509102-50d79100-09b4-11eb-96ea-8a544db60ccb.png\"/>\n  </td>\n  <td>\n    <img height=\"60px\" alt=\"mdi-solar-power\" src=\"https://user-images.githubusercontent.com/38377070/95516097-03145600-09bf-11eb-9027-37593379e1c2.png\"/>\n  </td>\n  <td>\n    <img height=\"60px\" alt=\"mdi-wind-turbine\" src=\"https://user-images.githubusercontent.com/38377070/95516203-2b9c5000-09bf-11eb-9fd0-a3a87447f30e.png\"/>\n  </td>\n  <td>\n    <img height=\"60px\" alt=\"mdi-radiator\" src=\"https://user-images.githubusercontent.com/38377070/151670027-67edf52c-1f7f-47a6-8f2a-1ee1a68b5906.png\"/>\n  </td>\n</tr>\n<tr>\n  <td>battery</td>\n  <td>car_charger</td>\n  <td>consumer</td>\n  <td>grid</td>\n  <td>home</td>\n  <td>hydro</td>\n  <td>pool</td>\n  <td>producer</td>\n  <td>solar</td>\n  <td>wind</td>\n  <td>heating</td>\n</tr>\n<tr>\n  <td>\n    Any Home Battery e.g. <a href=\"https://www.e3dc.com/en/#Intro\">E3dc</a>, <a href=\"https://www.tesla.com/en_eu/powerwall\">Powerwall</a>\n  </td>\n  <td>\n    Any Electric Car Charger\n  </td>\n  <td>\n    A custom home power consumer\n  </td>\n  <td>\n    The interface to the power grid\n  </td>\n  <td>\n    Your Home's power consumption\n  </td>\n  <td>\n    Hydropower setup like <a href=\"https://www.turbulent.be/\">Turbulent</a>\n  </td>\n  <td>\n    pool heater or pump\n  </td>\n  <td>\n    custom home power producer\n  </td>\n  <td>\n    Power coming from Solar\n  </td>\n  <td>\n    Power coming from Wind\n  </td>\n  <td>\n    Radiators\n  </td>\n</tr>\n</table>\n\nThe presets *consumer* and *producer* enable to add any custom device into your Card with just a bit of tweaking.\n</div>\n<br/>\n<div id=\"simple\">\n\n## Simple Configuration 🛠️ <img src=\"https://img.shields.io/badge/-Recommended-%2303a9f4\"/>\n\nWith Version 2.0 a Visual Editor got introduced.\nYou can find the Card in your Card Selector probably at the bottom.\nFrom there on you can configure your way to your custom Card.\nThe easiest way to get your Card up and running, is by defining the entities for the presets directly.\n<br/>\n\n<p align=\"center\">\n<img src=\"https://github.com/user-attachments/assets/d83e941c-0436-4efc-8060-ba3cbcd457d1\" />\n</p>\n<br/>\n\n```diff\n! Please Check for every Sensor: positive sensor values = production, negative values = consumption\n! If this is the other way around in your Case, check the `invert_value` setting (Advanced Configuration)!\n```\n\n<p align=\"center\">\n<img src=\"https://github.com/user-attachments/assets/e3b25a1f-3438-4b04-8174-9093473ec05b\"/>\n</p>\n\n\n### Placeholder\nBy submitting an empty entity_id and preset, you will generate a plain transparent placeholder item which can be used to further customize your layout.\nAlternatively you can use the provided `placeholder` preset.\n<p align=\"center\">\n<img src=\"https://user-images.githubusercontent.com/38377070/124113882-3676a380-da6c-11eb-8f3e-db00466fd601.png\"/>\n</p>\n</div>\n<br/><br/>\n\n\n<div id=\"yaml\">\n\n## YAML Only\n\nIf you are a real hardcore YAML connoisseur here is a basic example to get things started:\n```yaml\ntype: 'custom:power-distribution-card'\ntitle: Title\nanimation: flash\nentities:\n  - entity: sensor.e3dc_home\n    preset: home\n  - entity: sensor.e3dc_solar\n    preset: solar\n  - entity: sensor.e3dc_battery\n    preset: battery\ncenter:\n  type: bars\n  bars:\n    - preset: autarky\n      name: autarky\n    - preset: ratio\n      name: ratio\n```\nYou can find all options for every entity <a href=\"#entity\">here</a>.\nIf you want to further modify the center panel youz can find the documentation <a href=\"#center\">here</a>.\n</div>\n<br/><br/>\n\n\n<div id=\"animation\">\n\n## Animation\n\nFor the animation you have 3 options: `flash`, `slide`, `none`\n```yaml\ntype: 'custom:power-distribution-card'\nanimation: 'slide'\n```\n</div>\n<br/>\n\n<div id=\"center\">\n\n## Center Panel\n\nFor customizing the Center Panel you basically have 3 Options:\n\n### None 🕳️\n\nthe *void* \n\n<br/>\n\n### Bars 📊\n\nBars have the following Settings:\n| Setting               | type          | example           | description  |\n| --------------------- |:-------------:|:-----------------:| :------------|\n| `bar_color`           | string        | red, #C1C1C1      | You can pass any string that CSS will accept as a color. |\n| `bar_bg_color`        | string        | red, #C1C1C1      | The Background Color of the Bar. You can pass any string that CSS will accept as a color. |\n| `entity`              | string        | sensor.ln_autarky | You can specify the entity_id here as well. Required when `preset` is not `autarky` or `ratio`. |\n| `invert_value`        | bool          | false             | This will invert the value received from HASS. |\n| `lower_bound`         | number        | 0                 | Lower bound for bar fill scaling (default: 0). Values at or below this show an empty bar. |\n| `name`                | string        | Eigenstrom        | Feel free to change the displayed name of the element. |\n| `preset`              | `'autarky'` \\| `'ratio'` \\| `''` | `autarky` | `autarky`/`ratio` auto-calculate from entity totals. Use `''` (empty string) with an `entity` for a custom bar. |\n| `tap_action`          | Action Config | [Configuration](https://www.home-assistant.io/lovelace/actions/#configuration-variables) | Single tap action for item. |\n| `double_tap_action`   | Action Config | [Configuration](https://www.home-assistant.io/lovelace/actions/#configuration-variables) | Double tap action for item. |\n| `unit_of_measurement` | string        | *W* , *kW*        | Default: %; The Unit of the sensor value. **Should be detected automatically!** |\n| `upper_bound`         | number        | 100               | Upper bound for bar fill scaling (default: 100). Values at or above this show a full bar. |\n\n<br/>\n\n<p align=\"center\">\n<img src=\"https://github.com/user-attachments/assets/b56a9980-5248-4c7f-a029-161920bc68d2\"/>\n</p>\n\n<br/>\n\n### Cards 🃏\n\n\n<p align=\"center\">\n<img width=\"600px\" src=\"https://user-images.githubusercontent.com/38377070/97620471-e8fbef80-1a21-11eb-90d3-1bcbab57da2c.PNG\"/>\n</p>\n\nCards couldn't yet be included in the Visual editor in a nice way. I am working on it though. Feel free to open a Issue with suggestions.\nTo add a card you can simply replace the `center` part in the Code Editor. Be aware though: While you can switch between `none` and `card` without any issues, switching to Bars will override your settings.\n\nFor example you could insert a glance card:\n```yaml\ncenter:\n  type: card\n  card:\n    type: glance\n    entities:\n      - sensor.any_Sensor\n```\n</div>\n<br/><br/>\n\n<div id=\"entity\">\n\n## Entity Configuration ⚙️\n\nThere are alot of settings you can customize your sensors with:      \n\n| Setting                    | type          | example                      | description  |\n| -------------------------- |:-------------:|:----------------------------:| :------------|\n| `attribute`                | string        | deferredWatts                | A Sensor can have multiple attributes. If one of them is your desired value to display, add it here. |\n| `arrow_color`              | object        | {smaller:'red'}              | You can Change the Color of the arrow dependant on the value. (Bigger, Equal and Smaller) |\n| `calc_excluded`            | boolean       | true                         | If the Item should be excluded from ratio/autarky calculations. |\n| `color_threshold`          | number        | 0, -100, 420.69              | The value at which the coloring logic switches on. (default: 0) |\n| `consumer`                 | boolean       | true                         | Marks this entity as a power consumer. Negative values contribute to the consumption total used by autarky/ratio bars. |\n| `decimals`                 | number        | 0, 2                         | The Number of Decimals shown. (default: 2) |\n| `display_abs`              | boolean       | true                         | Display values as absolute (non-negative) numbers. Defaults to `true` when a preset is used. |\n| `double_tap_action`        | Action Config | [Configuration](https://www.home-assistant.io/lovelace/actions/#configuration-variables) | Double tap action for item. |\n| `entity`                   | string        | sensor.e3dc_grid             | You can specify the entity_id here as well. |\n| `hide_arrows`              | bool          | true                         | Toggles the visibility of the *arrows*. |\n| `icon`                     | string        | mdi:dishwasher               | Why not change the displayed Icon to any [MDI](https://pictogrammers.com/library/mdi/) one? |\n| `icon_color`               | object        | {smaller:'red'}              | You can Change the Color of the icon dependant on the value. (Bigger, Equal and Smaller) |\n| `invert_arrow`             | bool          | true                         | This will change the *arrows* direction to the opposite one. |\n| `invert_value`             | bool          | false                        | This will invert the value received from HASS. This affects calculations as well! |\n| `name`                     | string        | dishwasher                   | Feel free to change the displayed name of the element. |\n| `producer`                 | boolean       | true                         | Marks this entity as a power producer. Positive values contribute to the production total used by autarky/ratio bars. |\n| `secondary_info_attribute` | string        | min_temp                     | Requires `secondary_info_entity`. Displays the attribute value instead of the sensor state. |\n| `secondary_info_decimals`  | number        | 1                            | Number of decimals for the secondary info value. |\n| `secondary_info_entity`    | string        | sensor.e3dc_grid             | entity_id of the secondary info sensor. |\n| `secondary_info_replace_name` | bool       | true                         | This will replace the name of the item with the secondary info. |\n| `tap_action`               | Action Config | [Configuration](https://www.home-assistant.io/lovelace/actions/#configuration-variables) | Single tap action for item. |\n| `threshold`                | number        | 2                            | Ignoring all absolute values smaller than threshold. |\n| `unit_of_display`          | string        | *W* , *kW* , *adaptive*      | The Unit the value is displayed in (default: W). Adaptive will show kW for values >= 1kW. |\n| `unit_of_measurement`      | string        | *W* , *kW*                   | The Unit of the sensor value. **Should be detected automatically!** |\n<p> \n\nThis could look something like:\n\n```yaml\nentities:\n  - decimals: 2\n    display_abs: true\n    name: battery\n    unit_of_display: W\n    consumer: true\n    icon: 'mdi:battery-outline'\n    producer: true\n    entity: sensor.e3dc_battery\n    preset: battery\n    icon_color:\n      bigger: 'green'\n      equal: ''\n      smaller: 'red'\n```\n\n</div>\n<br/>\n<br/>\n\n<div>\n\n## Preset features\n\nThe Presets `battery` and `grid` have some additional features which allow some further customization.\nFor the Battery the icon can display the state of charge and the grid preset can have a small display with power sold and bought from the grid.\n\n<img width=\"600px\" src=\"https://user-images.githubusercontent.com/38377070/137152436-34753a15-86f9-44c4-ad47-87c35d94bd91.png\"/>\n\nIf one of those presets is selected there will be additional options in the visual editor.\nIf you prefer yaml, here are all extra options which can be set per item:\n\n| Setting                     | type          | example                      | description  |\n| --------------------------- |:-------------:|:----------------------------:| :------------|\n| `battery_percentage_entity` | string        | sensor.xyz                   | Sensor containing the battery charge percentage from 0 to 100 |\n| `grid_buy_entity`           | string        | sensor.xyz                   | Sensor containing the imported power from the grid |\n| `grid_sell_entity`          | string        | sensor.xyz                   | Sensor containing the sold power towards the grid |\n\n</div>\n</div> \n\n<hr>\n\n<div id=\"faq\">\n<h1> FAQs ❓</h1>\n\n### What the heck are these autarky and ratio calculating?\nSo basically these bar-graphs are nice indicators to show you:\n1. the autarky of your home (Home Production like Solar / Home Consumption) \n2. the ratio / share of produced electricity used by the home (The Germans call it `Eigenverbrauchsanteil` 😉)\n\n### kW and kWh is not the Same!\nI know... In this case usability is more important and the user has to decide if he is ok with that.\n\n<br/>\n</div>\n\n \n<hr>\n\n**If you find a Bug or have some suggestions, let me know <a href=\"https://github.com/JonahKr/power-distribution-card/issues\">here</a>!**\n\n**If you like the card, consider starring it.**\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import { defineConfig } from \"eslint/config\";\nimport tsParser from \"@typescript-eslint/parser\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport js from \"@eslint/js\";\nimport { FlatCompat } from \"@eslint/eslintrc\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst compat = new FlatCompat({\n    baseDirectory: __dirname,\n    recommendedConfig: js.configs.recommended,\n    allConfig: js.configs.all\n});\n\nexport default defineConfig([{\n    extends: compat.extends(\n        \"plugin:@typescript-eslint/recommended\",\n        \"prettier\",\n        \"plugin:prettier/recommended\",\n    ),\n\n    languageOptions: {\n        parser: tsParser,\n        ecmaVersion: 2018,\n        sourceType: \"module\",\n\n        parserOptions: {\n            experimentalDecorators: true,\n        },\n    },\n\n    rules: {\n        \"@typescript-eslint/camelcase\": 0,\n    },\n}]);\n"
  },
  {
    "path": "hacs.json",
    "content": "{\n  \"name\": \"Power Distribution Card\",\n  \"filename\": \"power-distribution-card.js\",\n  \"render_readme\": true,\n  \"homeassistant\": \"2026.3\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"power-distribution-card\",\n  \"version\": \"3.0.0\",\n  \"license\": \"MIT\",\n  \"author\": \"JonahKr\",\n  \"description\": \"A Lovelace Card for visualizing power distributions.\",\n  \"keywords\": [\n    \"power\",\n    \"distribution\",\n    \"lovelace\",\n    \"hacs\",\n    \"home assistant\",\n    \"e3dc\"\n  ],\n  \"module\": \"power-distribution-card.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/JonahKr/power-distribution-card.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/JonahKr/power-distribution-card/issues\"\n  },\n  \"homepage\": \"https://github.com/JonahKr/power-distribution-card#readme\",\n  \"scripts\": {\n    \"start\": \"BUILD_DEV=true rollup -c --watch\",\n    \"serve\": \"http-server dist/ --cors -p 5000\",\n    \"build\": \"pnpm run rollup\",\n    \"lint\": \"eslint src/*.ts\",\n    \"rollup\": \"rollup -c\"\n  },\n  \"dependencies\": {\n    \"@formatjs/intl-numberformat\": \"^9.3.1\",\n    \"@mdi/js\": \"^7.4.47\",\n    \"lit\": \"3.3.2\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"7.29.0\",\n    \"@babel/plugin-proposal-class-properties\": \"7.18.6\",\n    \"@babel/plugin-proposal-decorators\": \"7.29.0\",\n    \"@rollup/plugin-babel\": \"7.0.0\",\n    \"@rollup/plugin-commonjs\": \"29.0.2\",\n    \"@rollup/plugin-json\": \"6.1.0\",\n    \"@rollup/plugin-node-resolve\": \"16.0.3\",\n    \"@rollup/plugin-replace\": \"^6.0.3\",\n    \"@rollup/plugin-terser\": \"^1.0.0\",\n    \"@rollup/plugin-typescript\": \"^12.3.0\",\n    \"@typescript-eslint/eslint-plugin\": \"8.57.2\",\n    \"@typescript-eslint/parser\": \"8.57.2\",\n    \"eslint\": \"10.1.0\",\n    \"eslint-config-airbnb-base\": \"15.0.0\",\n    \"eslint-config-prettier\": \"10.1.8\",\n    \"eslint-plugin-import\": \"2.32.0\",\n    \"eslint-plugin-prettier\": \"5.5.5\",\n    \"http-server\": \"^14.1.1\",\n    \"prettier\": \"3.8.1\",\n    \"rollup\": \"4.60.0\",\n    \"typescript\": \"6.0.2\"\n  }\n}\n"
  },
  {
    "path": "rollup.config.mjs",
    "content": "import typescript from '@rollup/plugin-typescript';\nimport nodeResolve from '@rollup/plugin-node-resolve';\nimport babel from '@rollup/plugin-babel';\nimport terser from '@rollup/plugin-terser';\nimport json from '@rollup/plugin-json';\nimport replace from '@rollup/plugin-replace';\n\nconst dev = process.env.BUILD_DEV === 'true';\nconst devSuffix = dev ? '-dev' : '';\n\nconst plugins = [\n  replace({\n    __DEV_SUFFIX__: JSON.stringify(devSuffix),\n    preventAssignment: true,\n  }),\n  nodeResolve({}),\n  typescript(),\n  json(),\n  babel({\n    exclude: 'node_modules/**',\n    babelHelpers: 'bundled',\n  }),\n  terser(),\n];\n\nexport default [\n  {\n    input: 'src/power-distribution-card.ts',\n    output: {\n      file: `dist/power-distribution-card${devSuffix}.js`,\n      format: 'es',\n    },\n    plugins: [...plugins],\n  },\n];\n"
  },
  {
    "path": "src/action-handler.ts",
    "content": "import { noChange } from 'lit';\r\nimport { AttributePart, directive, Directive, DirectiveParameters } from 'lit/directive.js';\r\n\r\nimport { deepEqual } from './deep-equal';\r\nimport { ACTION_HANDLER_TAG } from './card-tags';\r\nimport { fireEvent } from './utils';\r\n\r\nexport const actions = ['more-info', 'toggle', 'navigate', 'url', 'call-service', 'none'] as const;\r\n\r\ninterface ActionHandlerMock extends HTMLElement {\r\n  holdTime: number;\r\n  bind(element: Element, options?: ActionHandlerOptions): void;\r\n}\r\ninterface ActionHandlerElement extends HTMLElement {\r\n  actionHandler?: {\r\n    options: ActionHandlerOptions;\r\n    start?: (ev: Event) => void;\r\n    end?: (ev: Event) => void;\r\n    handleEnter?: (ev: KeyboardEvent) => void;\r\n  };\r\n}\r\n\r\nexport interface ActionHandlerOptions {\r\n  hasHold?: boolean;\r\n  hasDoubleClick?: boolean;\r\n  disabled?: boolean;\r\n}\r\n\r\nclass ActionHandler extends HTMLElement implements ActionHandlerMock {\r\n  public holdTime = 500;\r\n  protected timer?: number;\r\n  private dblClickTimeout?: number;\r\n\r\n  public bind(element: ActionHandlerElement, options: ActionHandlerOptions = {}) {\r\n    if (element.actionHandler && deepEqual(options, element.actionHandler.options)) {\r\n      return;\r\n    }\r\n\r\n    if (element.actionHandler) {\r\n      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\r\n      element.removeEventListener('click', element.actionHandler.end!);\r\n    }\r\n    element.actionHandler = { options };\r\n\r\n    if (options.disabled) {\r\n      return;\r\n    }\r\n\r\n    element.actionHandler.end = (ev: Event): void => {\r\n      const target = element; //ev.target as HTMLElement;\r\n      // Prevent mouse event if touch event\r\n      if (ev.cancelable) {\r\n        ev.preventDefault();\r\n      }\r\n      clearTimeout(this.timer);\r\n      this.timer = undefined;\r\n      if (options.hasDoubleClick) {\r\n        if ((ev.type === 'click' && (ev as MouseEvent).detail < 2) || !this.dblClickTimeout) {\r\n          this.dblClickTimeout = window.setTimeout(() => {\r\n            this.dblClickTimeout = undefined;\r\n            fireEvent(target, 'action', { action: 'tap' });\r\n          }, 250);\r\n        } else {\r\n          clearTimeout(this.dblClickTimeout);\r\n          this.dblClickTimeout = undefined;\r\n          fireEvent(target, 'action', { action: 'double_tap' });\r\n        }\r\n      } else {\r\n        fireEvent(target, 'action', { action: 'tap' });\r\n      }\r\n    };\r\n    element.addEventListener('click', element.actionHandler.end);\r\n  }\r\n}\r\n\r\ncustomElements.define(ACTION_HANDLER_TAG, ActionHandler);\r\n\r\nconst getActionHandler = (): ActionHandler => {\r\n  const body = document.body;\r\n  if (body.querySelector(ACTION_HANDLER_TAG)) {\r\n    return body.querySelector(ACTION_HANDLER_TAG) as ActionHandler;\r\n  }\r\n\r\n  const actionhandler = document.createElement(ACTION_HANDLER_TAG);\r\n  body.appendChild(actionhandler);\r\n\r\n  return actionhandler as ActionHandler;\r\n};\r\n\r\nexport const actionHandlerBind = (element: ActionHandlerElement, options?: ActionHandlerOptions): void => {\r\n  const actionhandler: ActionHandler = getActionHandler();\r\n  if (!actionhandler) {\r\n    return;\r\n  }\r\n  actionhandler.bind(element, options);\r\n};\r\n\r\nexport const actionHandler = directive(\r\n  class extends Directive {\r\n    update(part: AttributePart, [options]: DirectiveParameters<this>) {\r\n      actionHandlerBind(part.element as ActionHandlerElement, options);\r\n      return noChange;\r\n    }\r\n    // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars\r\n    render(_options?: ActionHandlerOptions) {}\r\n  },\r\n);\r\n"
  },
  {
    "path": "src/card-tags.ts",
    "content": "// Build-time constant injected by rollup: '-dev' in watch mode, '' in production.\n// Never edit this value manually — change the rollup config or npm scripts instead.\ndeclare const __DEV_SUFFIX__: string;\n\nexport const CARD_TAG = `power-distribution-card${__DEV_SUFFIX__}`;\nexport const EDITOR_TAG = `${CARD_TAG}-editor`;\nexport const ITEM_EDITOR_TAG = `${CARD_TAG}-item-editor`;\nexport const BAR_EDITOR_TAG = `${CARD_TAG}-bar-editor`;\nexport const ITEMS_EDITOR_TAG = `${CARD_TAG}-items-editor`;\nexport const ACTION_HANDLER_TAG = `action-handler${__DEV_SUFFIX__}-power-distribution-card`;\n"
  },
  {
    "path": "src/deep-equal.ts",
    "content": "// From https://github.com/epoberezkin/fast-deep-equal\r\n// MIT License - Copyright (c) 2017 Evgeny Poberezkin\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nexport const deepEqual = (a: any, b: any): boolean => {\r\n  if (a === b) {\r\n    return true;\r\n  }\r\n\r\n  if (a && b && typeof a === 'object' && typeof b === 'object') {\r\n    if (a.constructor !== b.constructor) {\r\n      return false;\r\n    }\r\n\r\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n    let i: number | [any, any];\r\n    let length: number;\r\n    if (Array.isArray(a)) {\r\n      length = a.length;\r\n      if (length !== b.length) {\r\n        return false;\r\n      }\r\n      for (i = length; i-- !== 0; ) {\r\n        if (!deepEqual(a[i], b[i])) {\r\n          return false;\r\n        }\r\n      }\r\n      return true;\r\n    }\r\n\r\n    if (a instanceof Map && b instanceof Map) {\r\n      if (a.size !== b.size) {\r\n        return false;\r\n      }\r\n      for (i of a.entries()) {\r\n        if (!b.has(i[0])) {\r\n          return false;\r\n        }\r\n      }\r\n      for (i of a.entries()) {\r\n        if (!deepEqual(i[1], b.get(i[0]))) {\r\n          return false;\r\n        }\r\n      }\r\n      return true;\r\n    }\r\n\r\n    if (a instanceof Set && b instanceof Set) {\r\n      if (a.size !== b.size) {\r\n        return false;\r\n      }\r\n      for (i of a.entries()) {\r\n        if (!b.has(i[0])) {\r\n          return false;\r\n        }\r\n      }\r\n      return true;\r\n    }\r\n\r\n    if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {\r\n      // eslint-disable-next-line @typescript-eslint/ban-ts-comment\r\n      // @ts-ignore\r\n      length = a.length;\r\n      // eslint-disable-next-line @typescript-eslint/ban-ts-comment\r\n      // @ts-ignore\r\n      if (length !== b.length) {\r\n        return false;\r\n      }\r\n      for (i = length; i-- !== 0; ) {\r\n        if (a[i] !== b[i]) {\r\n          return false;\r\n        }\r\n      }\r\n      return true;\r\n    }\r\n\r\n    if (a.constructor === RegExp) {\r\n      return a.source === b.source && a.flags === b.flags;\r\n    }\r\n    if (a.valueOf !== Object.prototype.valueOf) {\r\n      return a.valueOf() === b.valueOf();\r\n    }\r\n    if (a.toString !== Object.prototype.toString) {\r\n      return a.toString() === b.toString();\r\n    }\r\n\r\n    const keys = Object.keys(a);\r\n    length = keys.length;\r\n    if (length !== Object.keys(b).length) {\r\n      return false;\r\n    }\r\n    for (i = length; i-- !== 0; ) {\r\n      if (!Object.prototype.hasOwnProperty.call(b, keys[i])) {\r\n        return false;\r\n      }\r\n    }\r\n\r\n    for (i = length; i-- !== 0; ) {\r\n      const key = keys[i];\r\n\r\n      if (!deepEqual(a[key], b[key])) {\r\n        return false;\r\n      }\r\n    }\r\n\r\n    return true;\r\n  }\r\n\r\n  // true if both NaN, false otherwise\r\n  // eslint-disable-next-line no-self-compare\r\n  return a !== a && b !== b;\r\n};\r\n"
  },
  {
    "path": "src/editor/bar-editor.ts",
    "content": "import { LitElement, html, css, nothing, CSSResultGroup } from 'lit';\nimport { property, state } from 'lit/decorators.js';\nimport { BAR_EDITOR_TAG } from '../card-tags';\n\n\n\nimport { BarSettings } from '../types';\nimport { localize } from '../localize/localize';\nimport { HaFormSchema } from './ha-form';\nimport { mdiDelete, mdiPlus } from '@mdi/js';\nimport { deepEqual } from '../deep-equal';\nimport { fireCustomEvent, HomeAssistant } from '../utils';\n\nconst BAR_PRESETS = ['autarky', 'ratio', ''];\n\nconst SCHEMA: HaFormSchema[] = [\n    { name: \"entity\", selector: { entity: {} } },\n    {\n        type: \"grid\",\n        name: \"\",\n        schema: [\n            { name: \"name\", selector: { text: {} } },\n            { name: \"preset\", selector: { select: { options: BAR_PRESETS, mode: 'dropdown' } } },\n        ]\n    },\n    {\n        type: \"grid\",\n        name: \"\",\n        schema: [\n            { name: \"lower_bound\", selector: { number: {} } },\n            { name: \"upper_bound\", selector: { number: {} } },\n        ]\n    },\n    {\n        type: \"grid\",\n        name: \"\",\n        schema: [\n            { name: \"bar_color\", selector: { ui_color: {} } },\n            { name: \"bar_bg_color\", selector: { ui_color: {} } },\n        ]\n    },\n    {\n        name: \"tap_action\",\n        selector: { ui_action: {} },\n    },\n    {\n        name: \"double_tap_action\",\n        selector: { ui_action: {} },\n    }\n];\n\n\n\nexport class ItemEditor extends LitElement {\n\n    @property({ attribute: false }) hass?: HomeAssistant;\n\n    @property({ attribute: false }) config?: BarSettings[];\n\n    @state() protected _selectedCard = 0;\n\n    private _computeLabel = (schema: HaFormSchema) => {\n        const nameMap: Record<string, string> = {\n            bar_color: 'color',\n            bar_bg_color: 'background_color',\n        };\n        const name = nameMap[schema.name] ?? schema.name;\n        return localize('editor.settings.' + name);\n    };\n\n    protected render() {\n\n        if (!this.hass) {\n            return nothing;\n        }\n\n        const config = this.config ?? [];\n        const selected = this._selectedCard;\n        const numBars = config.length;\n\n        return html`\n            <div class=\"card-config\">\n                <div class=\"toolbar\">\n                <ha-tab-group @wa-tab-show=${this._selectBar}>\n                    ${config.map(\n            (_card, i) => html`\n                        <ha-tab-group-tab\n                            slot=\"nav\"\n                            .panel=${i}\n                            .active=${i === selected}\n                        >${i + 1}</ha-tab-group-tab>`\n        )}\n                </ha-tab-group>\n                <ha-icon-button\n                    id=\"add-bar\"\n                    .path=${mdiPlus}\n                    @click=${this._addBar}\n                ></ha-icon-button>\n                </div>\n            </div>\n\n            ${numBars > 0 ? html`\n            <div id=\"editor\">\n                <div id=\"bar-options\">\n                    <ha-icon-button-arrow-prev\n                        .disabled=${selected === 0}\n                        .label=${this.hass.localize(\n            \"ui.panel.lovelace.editor.edit_card.move_before\"\n        )}\n                        @click=${this._moveLeft}\n                        .move=${-1}\n                    ></ha-icon-button-arrow-prev>\n\n                    <ha-icon-button-arrow-next\n                        .label=${this.hass.localize(\n            \"ui.panel.lovelace.editor.edit_card.move_after\"\n        )}\n                        .disabled=${selected === numBars - 1}\n                        @click=${this._moveRight}\n                        .move=${1}\n                    ></ha-icon-button-arrow-next>\n\n                    <ha-icon-button\n                        .label=${this.hass.localize(\n            \"ui.panel.lovelace.editor.edit_card.delete\"\n        )}\n                        .path=${mdiDelete}\n                        @click=${this._delete}\n                    ></ha-icon-button>\n                </div>\n\n                <ha-form\n                    .hass=${this.hass}\n                    .data=${config[selected]}\n                    .schema=${SCHEMA}\n                    .computeLabel=${this._computeLabel}\n                    @value-changed=${this.valueChanged}\n                ></ha-form>\n            </div>\n            ` : nothing}\n        `;\n    }\n\n    protected valueChanged(ev: CustomEvent<{ value: BarSettings }>) {\n\n        ev.stopPropagation();\n        if (!this.config || !this.hass) {\n            return;\n        }\n\n        // Check if value has changed\n        if (deepEqual(this.config[this._selectedCard], ev.detail.value)) return;\n\n        // Replace value for current index in readonly config\n        this.config = this.config!.map((item, index) =>\n            index === this._selectedCard ? ev.detail.value : item);\n\n        fireCustomEvent(this, \"config-changed\", this.config)\n    }\n\n    protected _addBar() {\n        if (!this.config) {\n            this.config = [{}];\n        } else {\n            this.config = [...this.config, {}];\n        }\n        this._selectedCard = this.config.length - 1;\n        fireCustomEvent(this, \"config-changed\", this.config);\n    }\n\n    protected _selectBar(ev: CustomEvent<{ name: string }>) {\n        this._selectedCard = parseInt(ev.detail.name, 10);\n    }\n\n    protected _moveRight() {\n        if (!this.config || this._selectedCard >= this.config.length - 1) return;\n\n        const newConfig = this.config.slice();\n        const movedElement = newConfig.splice(this._selectedCard, 1)[0];\n        newConfig.splice(this._selectedCard + 1, 0, movedElement);\n        this.config = newConfig;\n\n        this._selectedCard++;\n        fireCustomEvent(this, \"config-changed\", this.config);\n    }\n\n    protected _moveLeft() {\n        if (!this.config || this._selectedCard === 0) return;\n\n        const newConfig = this.config.slice();\n        const movedElement = newConfig.splice(this._selectedCard, 1)[0];\n        newConfig.splice(this._selectedCard - 1, 0, movedElement);\n        this.config = newConfig;\n\n        this._selectedCard--;\n        fireCustomEvent(this, \"config-changed\", this.config);\n    }\n\n    protected _delete() {\n        if (!this.config) return;\n\n        const newConfig = this.config.slice();\n        newConfig.splice(this._selectedCard, 1);\n        this.config = newConfig;\n\n        if (this._selectedCard >= newConfig.length && newConfig.length > 0) {\n            this._selectedCard = newConfig.length - 1;\n        }\n\n        fireCustomEvent(this, \"config-changed\", this.config);\n    }\n\n\n    static get styles(): CSSResultGroup {\n        return [\n            css`\n            .toolbar {\n              display: flex;\n              justify-content: space-between;\n              align-items: center;\n            }\n            ha-tab-group {\n              flex-grow: 1;\n              min-width: 0;\n              --ha-tab-track-color: var(--card-background-color);\n            }\n    \n            #bar-options {\n              display: flex;\n              justify-content: flex-end;\n              width: 100%;\n            }\n    \n            #editor {\n              border: 1px solid var(--divider-color);\n              padding: 12px;\n            }\n            @media (max-width: 450px) {\n              #editor {\n                margin: 0 -12px;\n              }\n            }\n          `,\n        ];\n    }\n\n}\n\ncustomElements.define(BAR_EDITOR_TAG, ItemEditor);"
  },
  {
    "path": "src/editor/editor.ts",
    "content": "import { LitElement, TemplateResult, html, css, CSSResultGroup, nothing } from 'lit';\nimport { html as staticHtml, unsafeStatic } from 'lit/static-html.js';\nimport { property, state } from 'lit/decorators.js';\nimport { EDITOR_TAG, ITEM_EDITOR_TAG, BAR_EDITOR_TAG, ITEMS_EDITOR_TAG } from '../card-tags';\n\n\nimport { mdiPencil } from '@mdi/js';\n\nimport { getLovelace } from '../utils';\nimport {\n  PDCConfig,\n  EntitySettings,\n  CustomValueEvent,\n} from '../types';\nimport { computeLabel, localize } from '../localize/localize';\n\nimport './item-editor';\nimport './items-editor';\nimport './bar-editor';\n\nimport { loadHaComponents } from '../utils/ha-component-loader';\nimport { HaFormSchema } from './ha-form';\nimport { fireEvent, HomeAssistant } from '../utils';\n\n/**\n * Editor Settings\n */\nconst animation = ['none', 'flash', 'slide'];\nconst center = ['none', 'card', 'bars'];\n\ntype EditorType = 'main' | 'item' | 'bars' | 'card';\n\ntype Editor = {\n  type: EditorType;\n  index?: number;\n}\n\nconst SCHEMA: HaFormSchema[] = [\n  { name: 'title', selector: { text: {} } },\n  { name: 'animation', selector: { select: { options: animation, mode: 'dropdown' } }, required: true },\n];\n\nexport class PowerDistributionCardEditor extends LitElement {\n  @property({ attribute: false }) public hass?: HomeAssistant;\n\n  @state() private _config!: PDCConfig;\n  @state() private _activeEditor: Editor = { type: 'main' };\n\n  public setConfig(config: PDCConfig) {\n    // Migrate old format: center.content -> center.bars or center.card\n    if (config.center && 'content' in config.center) {\n      const oldContent = (config.center as any).content;\n      const { content: _removed, ..._centerWithoutContent } = config.center as any;\n      let newCenter: PDCConfig['center'];\n      if (config.center.type === 'bars') {\n        newCenter = { ..._centerWithoutContent, bars: oldContent };\n      } else if (config.center.type === 'card') {\n        newCenter = { ..._centerWithoutContent, card: oldContent };\n      } else {\n        newCenter = _centerWithoutContent;\n      }\n      this._config = { ...config, center: newCenter };\n      fireEvent(this, 'config-changed', { config: this._config });\n    } else {\n      this._config = config;\n    }\n  }\n\n  protected firstUpdated() {\n    loadHaComponents();\n  }\n\n  protected render() {\n    if (!this.hass || !this._config) return nothing;\n\n    if (this._activeEditor.type === 'main') {\n      return this._renderMainEditor();\n    }\n\n    // All Subpages get an additional header\n    const content: (TemplateResult | typeof nothing)[] = [\n      html`\n        <div class=\"header\">\n          <div class=\"back-title\">\n            <ha-icon-button-arrow-prev @click=${this._goBack}>\n            </ha-icon-button-arrow-prev>\n          </div>\n        </div>`,\n    ];\n\n    switch (this._activeEditor.type) {\n      case 'item':\n        content.push(this._renderItemEditor());\n        break;\n      case 'bars':\n        content.push(this._renderBarEditor());\n        break;\n      case 'card':\n        content.push(this._renderCardEditor());\n        break;\n    }\n    return html`${content}`;\n  }\n\n  protected _enableCenterEditor(ev: any): void {\n    ev.stopPropagation();\n\n    this._activeEditor = { type: ev.currentTarget.value };\n  }\n\n  protected _enableItemEditor(ev: any): void {\n    ev.stopPropagation();\n\n    this._activeEditor = {\n      type: 'item',\n      index: ev.detail,\n    };\n  }\n\n  protected _goBack(): void {\n    this._activeEditor = { type: 'main' };\n  }\n\n  protected _valueChanged(ev: CustomValueEvent<unknown>) {\n    ev.stopPropagation();\n    if (!this._config || !this.hass) {\n      return;\n    }\n    const target = ev.target;\n    const detail = ev.detail;\n    if (target && detail) {\n      if (target.configValue) {\n        let value: any = detail;\n\n        // Specific Case\n        if (target.configValue == 'center.type') {\n          value = detail.value;\n        }\n\n        // We split the target configValue by '.' to allow for nested config values of depth 1\n        const configValues = target.configValue.split('.');\n\n        this._config = {\n          ...this._config,\n          [configValues[0]]: configValues.length > 1 ? {\n            ...this._config[configValues[0]],\n            [configValues[1]]: value,\n          } : value,\n        };\n\n      } else {\n        // Assuming a return from ha-form\n        this._config = detail.value as PDCConfig;\n      }\n\n      fireEvent(this, 'config-changed', { config: this._config });\n    }\n  }\n\n\n  protected _renderMainEditor(): TemplateResult {\n\n    return html`\n      <ha-form\n        .hass=${this.hass}\n        .data=${this._config}\n        .schema=${SCHEMA}\n        .computeLabel=${computeLabel}\n        @value-changed=${this._valueChanged}\n      ></ha-form>\n\n      <br />\n      <div class=\"entity row\">\n        <ha-select\n          style=\"flex-grow: 1\"\n          label=\"${localize('editor.settings.center')}\"\n          .configValue=${'center.type'}\n          @selected=${this._valueChanged}\n          .value=${this._config?.center?.type || 'none'}\n          .options=${center.map((val) => ({ value: val, label: val }))}\n        ></ha-select>\n        ${this._config?.center?.type != 'none'\n        ? html`<ha-icon-button\n              class=\"edit-icon\"\n              .value=${this._config?.center?.type}\n              .path=${mdiPencil}\n              @click=\"${this._enableCenterEditor}\"\n            ></ha-icon-button>`\n        : ''}\n        </div>\n        <br />\n        ${staticHtml`<${unsafeStatic(ITEMS_EDITOR_TAG)}\n          .hass=${this.hass}\n          .entities=${this._config.entities}\n          .configValue=${'entities'}\n          @edit-item=${this._enableItemEditor}\n          @config-changed=${this._valueChanged}\n        ></${unsafeStatic(ITEMS_EDITOR_TAG)}>`}\n      </div>\n    `;\n  }\n\n  protected _renderItemEditor() {\n    const index = this._activeEditor.index;\n    if (index == undefined) {\n      return nothing;\n    }\n\n    return staticHtml`<${unsafeStatic(ITEM_EDITOR_TAG)}\n      .hass=${this.hass}\n      .config=${this._config.entities[index]}\n      @config-changed=${this._itemChanged}\n    ></${unsafeStatic(ITEM_EDITOR_TAG)}>`;\n  }\n\n  /**\n   * Bar Editor\n   * -------------------\n   * This Bar Editor allows the user to easily add and remove new bars.\n   */\n  protected _renderBarEditor() {\n    return staticHtml`<${unsafeStatic(BAR_EDITOR_TAG)}\n      .hass=${this.hass}\n      .config=${this._config.center.bars}\n      .configValue=${'center.bars'}\n      @config-changed=${this._valueChanged}\n    ></${unsafeStatic(BAR_EDITOR_TAG)}>`;\n  }\n\n  private _itemChanged(ev: CustomEvent<EntitySettings>) {\n    ev.stopPropagation();\n    if (!this._config || !this.hass) {\n      return;\n    }\n    const index = this._activeEditor.index;\n    if (index != undefined) {\n      const entities = [...this._config.entities];\n      entities[index] = ev.detail;\n      fireEvent(this, 'config-changed', { config: { ...this._config, entities: entities } });\n    }\n  }\n\n  private _renderCardEditor(): TemplateResult {\n    return html`\n      <p />\n      Card configuration is only editable via yaml.\n      <p />\n      Check out the\n      <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/JonahKr/power-distribution-card#cards-\"\n        >Readme</a\n      >\n      to check out the latest and best way to add it.\n    `;\n  }\n\n  private _cardChanged(ev: CustomEvent): void {\n    ev.stopPropagation();\n    if (!this._config || !this.hass) return;\n    this._config = {\n      ...this._config,\n      center: { ...this._config.center, card: ev.detail.config },\n    };\n    fireEvent(this, 'config-changed', { config: this._config });\n  }\n\n  /**\n   * The Second Part comes from here: https://github.com/home-assistant/frontend/blob/dev/src/resources/ha-sortable-style.ts\n   * @returns Editor CSS\n   */\n  static get styles(): CSSResultGroup[] {\n    return [\n      css`\n        .checkbox {\n          display: flex;\n          align-items: center;\n          padding: 8px 0;\n        }\n        .checkbox input {\n          height: 20px;\n          width: 20px;\n          margin-left: 0;\n          margin-right: 8px;\n        }\n      `,\n      css`\n        h3 {\n          margin-bottom: 0.5em;\n        }\n        .row {\n          margin-bottom: 12px;\n          margin-top: 12px;\n          display: block;\n        }\n        .side-by-side {\n          display: flex;\n        }\n        .side-by-side > * {\n          flex: 1 1 0%;\n          padding-right: 4px;\n        }\n        .entity,\n        .add-item {\n          display: flex;\n          align-items: center;\n        }\n        .entity .handle {\n          padding-right: 8px;\n          cursor: move;\n        }\n        .entity ha-entity-picker,\n        .add-item ha-entity-picker {\n          flex-grow: 1;\n        }\n        .add-preset {\n          padding-right: 8px;\n          max-width: 130px;\n        }\n        .remove-icon,\n        .edit-icon,\n        .add-icon {\n          --mdc-icon-button-size: 36px;\n          color: var(--secondary-text-color);\n        }\n        .secondary {\n          font-size: 12px;\n          color: var(--secondary-text-color);\n        }`,\n    ];\n  }\n}\n\ncustomElements.define(EDITOR_TAG, PowerDistributionCardEditor);\n"
  },
  {
    "path": "src/editor/ha-form.ts",
    "content": "import type { LitElement } from \"lit\";\n\ninterface HaDurationData {\n  hours?: number;\n  minutes?: number;\n  seconds?: number;\n  milliseconds?: number;\n}\n\nexport type HaFormSchema =\n  | HaFormConstantSchema\n  | HaFormStringSchema\n  | HaFormIntegerSchema\n  | HaFormFloatSchema\n  | HaFormBooleanSchema\n  | HaFormSelectSchema\n  | HaFormMultiSelectSchema\n  | HaFormTimeSchema\n  | HaFormSelector\n  | HaFormGridSchema\n  | HaFormExpandableSchema\n  | HaFormOptionalActionsSchema;\n\nexport interface HaFormBaseSchema {\n  name: string;\n  // This value is applied if no data is submitted for this field\n  default?: HaFormData;\n  required?: boolean;\n  disabled?: boolean;\n  description?: {\n    suffix?: string;\n    // This value will be set initially when form is loaded\n    suggested_value?: HaFormData;\n  };\n  context?: Record<string, string>;\n}\n\nexport interface HaFormGridSchema extends HaFormBaseSchema {\n  type: \"grid\";\n  flatten?: boolean;\n  column_min_width?: string;\n  schema: readonly HaFormSchema[];\n}\n\nexport interface HaFormExpandableSchema extends HaFormBaseSchema {\n  type: \"expandable\";\n  flatten?: boolean;\n  title?: string;\n  icon?: string;\n  iconPath?: string;\n  expanded?: boolean;\n  headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;\n  schema: readonly HaFormSchema[];\n}\n\nexport interface HaFormOptionalActionsSchema extends HaFormBaseSchema {\n  type: \"optional_actions\";\n  flatten?: boolean;\n  schema: readonly HaFormSchema[];\n}\n\nexport interface HaFormSelector extends HaFormBaseSchema {\n  type?: never;\n  selector: Selector;\n}\n\nexport interface HaFormConstantSchema extends HaFormBaseSchema {\n  type: \"constant\";\n  value?: string;\n}\n\nexport interface HaFormIntegerSchema extends HaFormBaseSchema {\n  type: \"integer\";\n  default?: HaFormIntegerData;\n  valueMin?: number;\n  valueMax?: number;\n}\n\nexport interface HaFormSelectSchema extends HaFormBaseSchema {\n  type: \"select\";\n  options: readonly (readonly [string, string])[];\n}\n\nexport interface HaFormMultiSelectSchema extends HaFormBaseSchema {\n  type: \"multi_select\";\n  options:\n    | Record<string, string>\n    | readonly string[]\n    | readonly (readonly [string, string])[];\n}\n\nexport interface HaFormFloatSchema extends HaFormBaseSchema {\n  type: \"float\";\n}\n\nexport interface HaFormStringSchema extends HaFormBaseSchema {\n  type: \"string\";\n  format?: string;\n  autocomplete?: string;\n  autofocus?: boolean;\n}\n\nexport interface HaFormBooleanSchema extends HaFormBaseSchema {\n  type: \"boolean\";\n}\n\nexport interface HaFormTimeSchema extends HaFormBaseSchema {\n  type: \"positive_time_period_dict\";\n}\n\n// Type utility to unionize a schema array by flattening any grid schemas\nexport type SchemaUnion<\n  SchemaArray extends readonly HaFormSchema[],\n  Schema = SchemaArray[number],\n> = Schema extends HaFormGridSchema | HaFormExpandableSchema\n  ? SchemaUnion<Schema[\"schema\"]> | Schema\n  : Schema;\n\nexport type HaFormDataContainer = Record<string, HaFormData>;\n\nexport type HaFormData =\n  | HaFormStringData\n  | HaFormIntegerData\n  | HaFormFloatData\n  | HaFormBooleanData\n  | HaFormSelectData\n  | HaFormMultiSelectData\n  | HaFormTimeData;\n\nexport type HaFormStringData = string;\nexport type HaFormIntegerData = number;\nexport type HaFormFloatData = number;\nexport type HaFormBooleanData = boolean;\nexport type HaFormSelectData = string;\nexport type HaFormMultiSelectData = string[];\nexport type HaFormTimeData = HaDurationData;\n\nexport interface HaFormElement extends LitElement {\n  schema: HaFormSchema | readonly HaFormSchema[];\n  data?: HaFormDataContainer | HaFormData;\n  label?: string;\n}\n\n\nexport type Selector =\n  | ActionSelector\n  | AddonSelector\n  | AreaSelector\n  | AttributeSelector\n  | BooleanSelector\n  | ColorRGBSelector\n  | ColorTempSelector\n  | UiColorSelector\n  | DateSelector\n  | DateTimeSelector\n  | DeviceSelector\n  | DurationSelector\n  | EntitySelector\n  | IconSelector\n  | LocationSelector\n  | MediaSelector\n  | NumberSelector\n  | ObjectSelector\n  | SelectSelector\n  | StringSelector\n  | TargetSelector\n  | TemplateSelector\n  | ThemeSelector\n  | TimeSelector\n  | UiActionSelector;\n\nexport interface ActionSelector {\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  action: {};\n}\n\nexport interface AddonSelector {\n  addon: {\n    name?: string;\n    slug?: string;\n  };\n}\n\nexport interface AreaSelector {\n  area: {\n    entity?: {\n      integration?: EntitySelector[\"entity\"][\"integration\"];\n      domain?: EntitySelector[\"entity\"][\"domain\"];\n      device_class?: EntitySelector[\"entity\"][\"device_class\"];\n    };\n    device?: {\n      integration?: DeviceSelector[\"device\"][\"integration\"];\n      manufacturer?: DeviceSelector[\"device\"][\"manufacturer\"];\n      model?: DeviceSelector[\"device\"][\"model\"];\n    };\n    multiple?: boolean;\n  };\n}\n\nexport interface AttributeSelector {\n  attribute: {\n    entity_id?: string;\n  };\n}\n\nexport interface BooleanSelector {\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  boolean: {};\n}\n\nexport interface ColorRGBSelector {\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  color_rgb: {};\n}\n\nexport interface UiColorSelector {\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  ui_color: {};\n}\n\nexport interface ColorTempSelector {\n  color_temp: {\n    min_mireds?: number;\n    max_mireds?: number;\n  };\n}\n\nexport interface DateSelector {\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  date: {};\n}\n\nexport interface DateTimeSelector {\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  datetime: {};\n}\n\nexport interface DeviceSelector {\n  device: {\n    integration?: string;\n    manufacturer?: string;\n    model?: string;\n    entity?: {\n      domain?: EntitySelector[\"entity\"][\"domain\"];\n      device_class?: EntitySelector[\"entity\"][\"device_class\"];\n    };\n    multiple?: boolean;\n  };\n}\n\nexport interface DurationSelector {\n  duration: {\n    enable_day?: boolean;\n  };\n}\n\nexport interface EntitySelector {\n  entity: {\n    integration?: string;\n    domain?: string | string[];\n    device_class?: string;\n    multiple?: boolean;\n    include_entities?: string[];\n    exclude_entities?: string[];\n  };\n}\n\nexport interface IconSelector {\n  icon: {\n    placeholder?: string;\n    fallbackPath?: string;\n  };\n}\n\nexport interface LocationSelector {\n  location: { radius?: boolean; icon?: string };\n}\n\nexport interface LocationSelectorValue {\n  latitude: number;\n  longitude: number;\n  radius?: number;\n}\n\nexport interface MediaSelector {\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  media: {};\n}\n\nexport interface MediaSelectorValue {\n  entity_id?: string;\n  media_content_id?: string;\n  media_content_type?: string;\n  metadata?: {\n    title?: string;\n    thumbnail?: string | null;\n    media_class?: string;\n    children_media_class?: string | null;\n    navigateIds?: { media_content_type: string; media_content_id: string }[];\n  };\n}\n\nexport interface NumberSelector {\n  number: {\n    min?: number;\n    max?: number;\n    step?: number;\n    mode?: \"box\" | \"slider\";\n    unit_of_measurement?: string;\n  };\n}\n\nexport interface ObjectSelector {\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  object: {};\n}\n\nexport interface SelectOption {\n  value: string;\n  label: string;\n}\n\nexport interface SelectSelector {\n  select: {\n    multiple?: boolean;\n    custom_value?: boolean;\n    mode?: \"list\" | \"dropdown\";\n    options: string[] | SelectOption[];\n  };\n}\n\nexport interface StringSelector {\n  text: {\n    multiline?: boolean;\n    type?:\n      | \"number\"\n      | \"text\"\n      | \"search\"\n      | \"tel\"\n      | \"url\"\n      | \"email\"\n      | \"password\"\n      | \"date\"\n      | \"month\"\n      | \"week\"\n      | \"time\"\n      | \"datetime-local\"\n      | \"color\";\n    suffix?: string;\n  };\n}\n\nexport interface TargetSelector {\n  target: {\n    entity?: {\n      integration?: EntitySelector[\"entity\"][\"integration\"];\n      domain?: EntitySelector[\"entity\"][\"domain\"];\n      device_class?: EntitySelector[\"entity\"][\"device_class\"];\n    };\n    device?: {\n      integration?: DeviceSelector[\"device\"][\"integration\"];\n      manufacturer?: DeviceSelector[\"device\"][\"manufacturer\"];\n      model?: DeviceSelector[\"device\"][\"model\"];\n    };\n  };\n}\n\nexport interface TemplateSelector {\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  template: {};\n}\n\nexport interface ThemeSelector {\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  theme: {};\n}\nexport interface TimeSelector {\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  time: {};\n}\n\nexport type UiAction = Exclude<ActionConfig[\"action\"], \"fire-dom-event\">;\n\nexport interface UiActionSelector {\n  ui_action: {\n    actions?: UiAction[];\n  } | null;\n}\nexport interface ToggleActionConfig extends BaseActionConfig {\n    action: \"toggle\";\n  }\n  \n  export interface CallServiceActionConfig extends BaseActionConfig {\n    action: \"call-service\" | \"perform-action\";\n    /** @deprecated \"service\" is kept for backwards compatibility. Replaced by \"perform_action\". */\n    service?: string;\n    perform_action: string;\n    target?: any;\n    /** @deprecated \"service_data\" is kept for backwards compatibility. Replaced by \"data\". */\n    service_data?: Record<string, unknown>;\n    data?: Record<string, unknown>;\n  }\n  \n  export interface NavigateActionConfig extends BaseActionConfig {\n    action: \"navigate\";\n    navigation_path: string;\n  }\n  \n  export interface UrlActionConfig extends BaseActionConfig {\n    action: \"url\";\n    url_path: string;\n  }\n  \n  export interface MoreInfoActionConfig extends BaseActionConfig {\n    action: \"more-info\";\n  }\n  \n  export interface NoActionConfig extends BaseActionConfig {\n    action: \"none\";\n  }\n  \n  export interface CustomActionConfig extends BaseActionConfig {\n    action: \"fire-dom-event\";\n  }\n  \n  export interface AssistActionConfig extends BaseActionConfig {\n    action: \"assist\";\n    pipeline_id?: string;\n    start_listening?: boolean;\n  }\n  \n  export interface BaseActionConfig {\n    action: string;\n    confirmation?: ConfirmationRestrictionConfig;\n  }\n  \n  export interface ConfirmationRestrictionConfig {\n    text?: string;\n    exemptions?: RestrictionConfig[];\n  }\n  \n  export interface RestrictionConfig {\n    user: string;\n  }\n  \n  export type ActionConfig =\n    | ToggleActionConfig\n    | CallServiceActionConfig\n    | NavigateActionConfig\n    | UrlActionConfig\n    | MoreInfoActionConfig\n    | AssistActionConfig\n    | NoActionConfig\n    | CustomActionConfig;"
  },
  {
    "path": "src/editor/item-editor.ts",
    "content": "import { LitElement, html, css, CSSResult, nothing } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { ITEM_EDITOR_TAG } from '../card-tags';\n\nimport { EntitySettings } from '../types';\nimport { localize } from '../localize/localize';\nimport { PresetList } from '../presets';\nimport { HaFormSchema } from './ha-form';\nimport { fireEvent, HomeAssistant } from '../utils';\n\nconst BASE_SCHEMA: HaFormSchema[] = [\n  {\n    name: \"general\",\n    type: \"expandable\",\n    flatten: true,\n    expanded: true,\n    title: localize('editor.settings.general_settings', true),\n    icon: \"mdi:text\",\n    schema: [\n      {\n        name: \"entity\",\n        selector: { entity: { domain: \"sensor\"} }\n      },\n      {\n        type: \"grid\",\n        name: \"\",\n        schema: [\n            { name: \"name\", selector: { text: {} } },\n            { name: \"icon\", selector: { icon: {} } },\n            { name: \"attribute\", selector: { attribute: {}}, context: { filter_entity: \"entity\" } },\n            { name: \"preset\", selector: { select: { options: PresetList as any as string[], mode: 'dropdown' } } },\n        ]\n      },\n    ]\n  },\n  {\n    name: \"Value Settings\",\n    type: \"expandable\",\n    flatten: true,\n    title: localize('editor.settings.value', true) + \" \" +localize('editor.settings.settings', true),\n    icon: \"mdi:numeric\",\n    schema: [\n      {\n        type: \"grid\",\n        name: \"\",\n        schema: [\n            { name: \"unit_of_display\", selector: { text: {} } },\n            { name: \"decimals\", selector: { number: { step: 1} } },\n            { name: \"invert_value\", type: \"boolean\"},\n            { name: \"display_abs\", type: \"boolean\"},\n            { name: \"hide_arrows\", type: \"boolean\"},\n            { name: \"calc_excluded\", type: \"boolean\"},\n            { name: \"threshold\", selector: { number: { } } },\n        ]\n      }\n    ]\n  },\n  {\n    name: \"Secondary Info\",\n    type: \"expandable\",\n    flatten: true,\n    title: localize('editor.settings.secondary_info', true),\n    icon: \"mdi:attachment-plus\",\n    schema: [\n      { name: \"secondary_info_entity\",\n        selector: { entity: { domain: \"sensor\"} } },\n      { name: \"secondary_info_attribute\", selector: { attribute: {}}, context: { filter_entity: \"secondary_info_entity\" }},\n      { name: \"secondary_info_decimals\", selector: { number: { step: 1 } } },\n      { name: \"secondary_info_replace_name\", type: \"boolean\"},\n    ]\n  },\n  {\n    name: \"Action Settings\",\n    type: \"expandable\",\n    flatten: true,\n    title: localize('editor.settings.action_settings', true),\n    icon: \"mdi:gesture-tap\",\n    schema: [\n      {\n        type: \"grid\",\n        name: \"\",\n        schema: [\n          {\n            name: \"tap_action\",\n            selector: { ui_action: {} },\n        },\n        {\n            name: \"double_tap_action\",\n            selector: { ui_action: {} },\n        }\n        ]\n      }\n    ]\n  },\n  {\n    name: \"Color Settings\",\n    type: \"expandable\",\n    flatten: true,\n    title: localize('editor.settings.color_settings', true),\n    icon: \"mdi:palette\",\n    schema: [\n      { name: \"color_threshold\", selector: { number: { } } },\n      {\n        type: \"grid\",\n        name: \"\",\n        schema: [\n          { name: \"icon_color_bigger\", selector: { ui_color: {} } },\n          { name: \"arrow_color_bigger\", selector: { ui_color: {} } },\n          { name: \"icon_color_equal\", selector: { ui_color: {} } },\n          { name: \"arrow_color_equal\", selector: { ui_color: {} } },\n          { name: \"icon_color_smaller\", selector: { ui_color: {} } },\n          { name: \"arrow_color_smaller\", selector: { ui_color: {} } },\n        ]\n      }\n    ]\n  }\n];\n\nconst PRESET_LABEL_MAP: Record<string, string> = {\n  battery_percentage_entity: 'battery_percentage',\n  grid_buy_entity: 'grid_buy',\n  grid_sell_entity: 'grid_sell',\n  secondary_info_decimals: 'decimals',\n};\n\n\nexport class ItemEditor extends LitElement {\n  @property({ attribute: false }) config?: EntitySettings;\n\n  @property({ attribute: false }) hass?: HomeAssistant;\n\n  private get _flatConfig() {\n    const c = this.config!;\n    return {\n      ...c,\n      icon_color_bigger: c.icon_color?.bigger,\n      icon_color_equal: c.icon_color?.equal,\n      icon_color_smaller: c.icon_color?.smaller,\n      arrow_color_bigger: c.arrow_color?.bigger,\n      arrow_color_equal: c.arrow_color?.equal,\n      arrow_color_smaller: c.arrow_color?.smaller,\n    };\n  }\n\n  private get _schema(): HaFormSchema[] {\n    const preset = this.config?.preset;\n    const presetFields: HaFormSchema[] =\n      preset === 'battery'\n        ? [{ name: 'battery_percentage_entity', selector: { entity: {} } }]\n        : preset === 'grid'\n        ? [\n            { name: 'grid_buy_entity', selector: { entity: {} } },\n            { name: 'grid_sell_entity', selector: { entity: {} } },\n          ]\n        : [];\n\n    if (presetFields.length === 0) return BASE_SCHEMA;\n\n    const presetSection: HaFormSchema = {\n      name: 'preset_section',\n      type: 'expandable',\n      flatten: true,\n      title: localize('editor.settings.preset_settings', true),\n      icon: \"mdi:shape\",\n      schema: presetFields,\n    };\n    return [BASE_SCHEMA[0], presetSection, ...BASE_SCHEMA.slice(1)];\n  }\n\n  private _computeLabel = (schema: HaFormSchema) => {\n    const key = PRESET_LABEL_MAP[schema.name] ?? schema.name;\n    return `${localize('editor.settings.' + key)} ${!schema.required ? `(${localize('editor.optional')})` : ''}`;\n  };\n\n  protected render() {\n    // If its a placeholder, don't render anything\n    if (!this.hass || !this.config || this.config.preset == 'placeholder') {\n      return nothing;\n    }\n\n    return html`\n      <ha-form\n        .hass=${this.hass}\n        .data=${this._flatConfig}\n        .schema=${this._schema}\n        .computeLabel=${this._computeLabel}\n        @value-changed=${this._formValueChanged}\n      ></ha-form>\n    `;\n  }\n\n  private _formValueChanged(ev: CustomEvent): void {\n    ev.stopPropagation();\n    if (!this.config || !this.hass) return;\n\n    const {\n      icon_color_bigger, icon_color_equal, icon_color_smaller,\n      arrow_color_bigger, arrow_color_equal, arrow_color_smaller,\n      ...rest\n    } = ev.detail.value;\n\n    const icon_color = (icon_color_bigger || icon_color_equal || icon_color_smaller)\n      ? { bigger: icon_color_bigger || undefined, equal: icon_color_equal || undefined, smaller: icon_color_smaller || undefined }\n      : undefined;\n    const arrow_color = (arrow_color_bigger || arrow_color_equal || arrow_color_smaller)\n      ? { bigger: arrow_color_bigger || undefined, equal: arrow_color_equal || undefined, smaller: arrow_color_smaller || undefined }\n      : undefined;\n\n    fireEvent<any>(this, 'config-changed', { ...rest, icon_color, arrow_color });\n  }\n\n  static get styles(): CSSResult {\n    return css`\n      .checkbox {\n        display: flex;\n        align-items: center;\n        padding: 8px 0;\n      }\n      .checkbox input {\n        height: 20px;\n        width: 20px;\n        margin-left: 0;\n        margin-right: 8px;\n      }\n      h3 {\n        margin-bottom: 0.5em;\n      }\n      .row {\n        margin-bottom: 12px;\n        margin-top: 12px;\n        display: block;\n      }\n      .side-by-side {\n        display: flex;\n      }\n      .side-by-side > * {\n        flex: 1 1 0%;\n        padding-right: 4px;\n      }\n    `;\n  }\n}\n\ncustomElements.define(ITEM_EDITOR_TAG, ItemEditor);\n"
  },
  {
    "path": "src/editor/items-editor.ts",
    "content": "import { LitElement, html } from 'lit';\n\nimport { EditorTarget, EntitySettings, HTMLElementValue } from '../types';\nimport { localize } from '../localize/localize';\nimport { property, state } from 'lit/decorators.js';\nimport { ITEMS_EDITOR_TAG } from '../card-tags';\nimport { repeat } from 'lit/directives/repeat.js';\nimport { css, CSSResult, nothing } from 'lit';\nimport { mdiClose, mdiPencil, mdiPlusCircleOutline } from '@mdi/js';\nimport { DefaultItem, PresetList, PresetObject } from '../presets';\n\nimport { fireCustomEvent, HomeAssistant } from '../utils';\n\nexport class ItemsEditor extends LitElement {\n  @property({ attribute: false }) entities?: EntitySettings[];\n\n  @property({ attribute: false }) hass?: HomeAssistant;\n\n  @state() private _selectedPreset: string = PresetList[0];\n\n  private _entityKeys = new WeakMap<EntitySettings, string>();\n\n  private _getKey(action: EntitySettings) {\n    if (!this._entityKeys.has(action)) {\n      this._entityKeys.set(action, Math.random().toString());\n    }\n\n    return this._entityKeys.get(action)!;\n  }\n\n  public disconnectedCallback() {\n    super.disconnectedCallback();\n  }\n\n  protected render() {\n    if (!this.entities || !this.hass) {\n      return nothing;\n    }\n\n    return html`\n      <h3>${localize('editor.settings.entities')}</h3>\n      <ha-sortable handle-selector=\".handle\" @item-moved=${this._rowMoved}>\n        <div class=\"entities\">\n          ${repeat(\n            this.entities,\n            (entityConf) => this._getKey(entityConf),\n            (entityConf, index) => html`\n              <div class=\"entity\">\n                <div class=\"handle\">\n                  <ha-icon icon=\"mdi:drag\"></ha-icon>\n                </div>\n                <ha-entity-picker\n                  allow-custom-entity\n                  hideClearIcon\n                  .hass=${this.hass}\n                  .configValue=${'entity'}\n                  .value=${entityConf.entity}\n                  .index=${index}\n                  @value-changed=${this._valueChanged}\n                ></ha-entity-picker>\n\n                <ha-icon-button\n                  .label=${localize('editor.actions.remove')}\n                  .path=${mdiClose}\n                  class=\"remove-icon\"\n                  .index=${index}\n                  @click=${this._removeRow}\n                ></ha-icon-button>\n\n                <ha-icon-button\n                  .label=${localize('editor.actions.edit')}\n                  .path=${mdiPencil}\n                  class=\"edit-icon\"\n                  .index=${index}\n                  @click=\"${this._editRow}\"\n                ></ha-icon-button>\n              </div>\n            `,\n          )}\n        </div>\n      </ha-sortable>\n\n      \n      <div class=\"add-item row\">\n        <ha-select\n          label=\"${localize('editor.settings.preset')}\"\n          class=\"add-preset\"\n          .value=${this._selectedPreset}\n          .options=${PresetList.map((val) => ({ value: val, label: val }))}\n          @selected=${(ev: CustomEvent<{ value: string }>) => { this._selectedPreset = ev.detail.value; }}\n        ></ha-select>\n\n        <ha-entity-picker .hass=${this.hass} name=\"entity\" class=\"add-entity\"></ha-entity-picker>\n\n        <ha-icon-button\n          .label=${localize('editor.actions.add')}\n          .path=${mdiPlusCircleOutline}\n          class=\"add-icon\"\n          @click=\"${this._addRow}\"\n        ></ha-icon-button>\n      </div>\n    `;\n  }\n\n  private _valueChanged(ev: CustomEvent): void {\n    if (!this.entities || !this.hass) {\n      return;\n    }\n    const value = ev.detail.value;\n    const index = (ev.target as any).index;\n    const newConfigEntities = this.entities!.concat();\n\n    newConfigEntities[index] = {\n      ...newConfigEntities[index],\n      entity: value || '',\n    };\n\n    fireCustomEvent<EntitySettings[]>(this, 'config-changed', newConfigEntities);\n  }\n\n  private _removeRow(ev: Event): void {\n    ev.stopPropagation();\n    const index = (ev.currentTarget as EditorTarget).index;\n    if (index != undefined) {\n      const entities = this.entities!.concat();\n      entities.splice(index, 1);\n      fireCustomEvent<EntitySettings[]>(this, 'config-changed', entities);\n    }\n  }\n\n  private _editRow(ev: Event): void {\n    ev.stopPropagation();\n\n    const index = (ev.target as EditorTarget).index;\n    if (index != undefined) {\n      fireCustomEvent<number>(this, 'edit-item', index);\n    }\n  }\n\n  private _addRow(ev: Event): void {\n    ev.stopPropagation();\n    if (!this.entities || !this.hass) {\n      return;\n    }\n\n    const preset = this._selectedPreset || 'placeholder';\n    const entity_id = (this.shadowRoot!.querySelector('.add-entity') as HTMLElementValue).value;\n\n    const item = Object.assign({}, DefaultItem, PresetObject[preset], {\n      entity: entity_id,\n      preset: entity_id == '' ? 'placeholder' : preset,\n    });\n\n    fireCustomEvent<EntitySettings[]>(this, 'config-changed', [...this.entities, item]);\n  }\n\n  private _rowMoved(ev: CustomEvent<{ oldIndex: number; newIndex: number }>): void {\n    ev.stopPropagation();\n    const { oldIndex, newIndex } = ev.detail;\n    if (oldIndex === newIndex || !this.entities) return;\n\n    const newEntities = this.entities.concat();\n    newEntities.splice(newIndex, 0, newEntities.splice(oldIndex, 1)[0]);\n\n    fireCustomEvent<EntitySettings[]>(this, 'config-changed', newEntities);\n  }\n\n  static get styles(): CSSResult {\n    return css`\n      .entity,\n      .add-item {\n        display: flex;\n        align-items: center;\n      }\n      .entity {\n        display: flex;\n        align-items: center;\n      }\n      .entity .handle {\n        padding-right: 8px;\n        cursor: move;\n        padding-inline-end: 8px;\n        padding-inline-start: initial;\n        direction: var(--direction);\n      }\n      .entity .handle > * {\n        pointer-events: none;\n      }\n      .entity ha-entity-picker,\n      .add-item ha-entity-picker {\n        flex-grow: 1;\n      }\n      .entities {\n        margin-bottom: 8px;\n      }\n      .add-preset {\n        padding-right: 8px;\n        max-width: 130px;\n      }\n      .remove-icon,\n      .edit-icon,\n      .add-icon {\n        --mdc-icon-button-size: 36px;\n        color: var(--secondary-text-color);\n      }\n    `;\n  }\n}\n\ncustomElements.define(ITEMS_EDITOR_TAG, ItemsEditor);"
  },
  {
    "path": "src/localize/languages/de.json",
    "content": "{\r\n  \"common\": {\r\n    \"description\": \"Eine Karte zur Visualizierung von Stromverteilungen\"\r\n  },\r\n  \"editor\": {\r\n    \"actions\": {\r\n      \"add\": \"Hinzufügen\",\r\n      \"edit\": \"Bearbeiten\",\r\n      \"remove\": \"Entfernen\"\r\n    },\r\n    \"optional\": \"Optional\",\r\n    \"settings\": {\r\n      \"action_settings\": \"Interaktions Einstellungen\",\r\n      \"animation\": \"Animation\",\r\n      \"autarky\": \"Autarkie\",\r\n      \"attribute\": \"Attribut\",\r\n      \"background_color\": \"Hintergrundfarbe\",\r\n      \"battery_percentage\": \"Batterie Ladung %\",\r\n      \"arrow_color_bigger\": \"Pfeil - Größer\",\r\n      \"arrow_color_equal\": \"Pfeil - Gleich\",\r\n      \"arrow_color_smaller\": \"Pfeil - Kleiner\",\r\n      \"icon_color_bigger\": \"Symbol - Größer\",\r\n      \"icon_color_equal\": \"Symbol - Gleich\",\r\n      \"icon_color_smaller\": \"Symbol - Kleiner\",\r\n      \"calc_excluded\": \"Von Rechnungen ausschließen\",\r\n      \"center\": \"Mittelbereich\",\r\n      \"color\": \"Farbe\",\r\n      \"color_settings\": \"Farb Einstellungen\",\r\n      \"color_threshold\": \"Farb-Schwellenwert\",\r\n      \"decimals\": \"Dezimalstellen\",\r\n      \"display_abs\": \"Absolute Wertanzeige\",\r\n      \"double_tap_action\": \"Doppel Tipp Aktion\",\r\n      \"entities\": \"Entities\",\r\n      \"entity\": \"Element\",\r\n      \"general_settings\": \"Allgemeine Einstellungen\",\r\n      \"grid_buy\": \"Netz Ankauf\",\r\n      \"grid_sell\": \"Netz Verkauf\",\r\n      \"hide_arrows\": \"Pfeile Verstecken\",\r\n      \"lower_bound\": \"Untere Grenze\",\r\n      \"upper_bound\": \"Obere Grenze\",\r\n      \"preset_settings\": \"Vorlagen Einstellungen\",\r\n      \"icon\": \"Symbol\",\r\n      \"invert_value\": \"Wert Invertieren\",\r\n      \"name\": \"Name\",\r\n      \"preset\": \"Vorlagen\",\r\n      \"ratio\": \"Anteil\",\r\n      \"secondary_info\": \"Zusatzinformationen\",\r\n      \"secondary_info_entity\": \"Element\",\r\n      \"secondary_info_attribute\": \"Attribut\",\r\n      \"secondary_info_replace_name\": \"Namen Ersetzen\",\r\n      \"settings\": \"Einstellungen\",\r\n      \"tap_action\": \"Tipp Aktion\",\r\n      \"threshold\": \"Schwellenwert\",\r\n      \"title\": \"Titel\",\r\n      \"unit_of_display\": \"Angezeigte Einheit\",\r\n      \"value\": \"Wert\"\r\n    }\r\n  }\r\n}"
  },
  {
    "path": "src/localize/languages/en.json",
    "content": "{\r\n  \"common\": {\r\n    \"description\": \"A Lovelace Card for visualizing power distributions.\"\r\n  },\r\n  \"editor\": {\r\n    \"actions\": {\r\n      \"add\": \"Add\",\r\n      \"edit\": \"Edit\",\r\n      \"remove\": \"Remove\"\r\n    },\r\n    \"optional\": \"Optional\",\r\n    \"settings\": {\r\n      \"action_settings\": \"Action Settings\",\r\n      \"animation\": \"Animation\",\r\n      \"autarky\": \"autarky\",\r\n      \"attribute\": \"Attribute\",\r\n      \"background_color\": \"Background Color\",\r\n      \"battery_percentage\": \"Battery Charge %\",\r\n      \"arrow_color_bigger\": \"Arrow - Bigger\",\r\n      \"arrow_color_equal\": \"Arrow - Equal\",\r\n      \"arrow_color_smaller\": \"Arrow - Smaller\",\r\n      \"icon_color_bigger\": \"Icon - Bigger\",\r\n      \"icon_color_equal\": \"Icon - Equal\",\r\n      \"icon_color_smaller\": \"Icon - Smaller\",\r\n      \"calc_excluded\": \"Excluded from Calculations\",\r\n      \"center\": \"Center\",\r\n      \"color\": \"Color\",\r\n      \"color_settings\": \"Color Settings\",\r\n      \"color_threshold\": \"Color Threshold\",\r\n      \"decimals\": \"Decimals\",\r\n      \"display_abs\": \"Display Absolute Value\",\r\n      \"double_tap_action\": \"Double Tap Action\",\r\n      \"entities\": \"Entities\",\r\n      \"entity\": \"Entity\",\r\n      \"general_settings\": \"General Settings\",\r\n      \"grid_buy\": \"Grid Buy\",\r\n      \"grid_sell\": \"Grid Sell\",\r\n      \"hide_arrows\": \"Hide Arrows\",\r\n      \"lower_bound\": \"Lower Bound\",\r\n      \"upper_bound\": \"Upper Bound\",\r\n      \"preset_settings\": \"Preset Settings\",\r\n      \"icon\": \"Icon\",\r\n      \"invert_value\": \"Invert Value\",\r\n      \"name\": \"Name\",\r\n      \"preset\": \"Preset\",\r\n      \"ratio\": \"ratio\",\r\n      \"secondary_info\": \"Secondary Info\",\r\n      \"secondary_info_entity\": \"Entity\",\r\n      \"secondary_info_attribute\": \"Attribute\",\r\n      \"secondary_info_replace_name\": \"Replace Name\",\r\n      \"settings\": \"Settings\",\r\n      \"tap_action\": \"Tap Action\",\r\n      \"threshold\": \"Threshold\",\r\n      \"title\": \"Title\",\r\n      \"unit_of_display\": \"Unit of Display\",\r\n      \"value\": \"value\"\r\n    }\r\n  }\r\n}"
  },
  {
    "path": "src/localize/languages/sk.json",
    "content": "{\n  \"common\": {\n    \"description\": \"A Lovelace Card for visualizing power distributions.\"\n  },\n  \"editor\": {\n    \"actions\": {\n      \"add\": \"Pridať\",\n      \"edit\": \"Editovať\",\n      \"remove\": \"Odobrať\"\n    },\n    \"optional\": \"Voliteľné\",\n    \"settings\": {\n      \"action_settings\": \"Nastavenia akcie\",\n      \"animation\": \"Animácia\",\n      \"autarky\": \"sebestačnosť\",\n      \"attribute\": \"Atribút\",\n      \"background_color\": \"Farba pozadia\",\n      \"battery_percentage\": \"Nabitie batérie %\",\n      \"arrow_color_bigger\": \"Šípka - Väčšie\",\n      \"arrow_color_equal\": \"Šípka - Rovné\",\n      \"arrow_color_smaller\": \"Šípka - Menšie\",\n      \"icon_color_bigger\": \"Ikona - Väčšie\",\n      \"icon_color_equal\": \"Ikona - Rovné\",\n      \"icon_color_smaller\": \"Ikona - Menšie\",\n      \"calc_excluded\": \"Vylúčené z výpočtov\",\n      \"center\": \"Centrum\",\n      \"color\": \"Farba\",\n      \"color_settings\": \"Nastavenia farby\",\n      \"color_threshold\": \"Prah farby\",\n      \"decimals\": \"Desatinné čísla\",\n      \"display_abs\": \"Zobraziť absolútnu hodnotu\",\n      \"double_tap_action\": \"Akcia dvojitého klepnutia\",\n      \"entities\": \"Entity\",\n      \"entity\": \"Entita\",\n      \"general_settings\": \"Všeobecné nastavenia\",\n      \"grid_buy\": \"Sieť nákup\",\n      \"grid_sell\": \"Sieť predaj\",\n      \"hide_arrows\": \"Skryť šípky\",\n      \"lower_bound\": \"Dolná hranica\",\n      \"upper_bound\": \"Horná hranica\",\n      \"preset_settings\": \"Nastavenia predvoľby\",\n      \"icon\": \"Ikona\",\n      \"invert_value\": \"Invertovať hodnotu\",\n      \"name\": \"Názov\",\n      \"preset\": \"Predvoľba\",\n      \"ratio\": \"pomer\",\n      \"secondary_info\": \"Sekundárne informácie\",\n      \"secondary_info_entity\": \"Entita\",\n      \"secondary_info_attribute\": \"Atribút\",\n      \"secondary_info_replace_name\": \"Nahradiť názov\",\n      \"settings\": \"nastavenia\",\n      \"tap_action\": \"Akcia klepnutia\",\n      \"threshold\": \"Prah\",\n      \"title\": \"Titul\",\n      \"unit_of_display\": \"Jednotka zobrazenia\",\n      \"value\": \"hodnota\"\n    }\n  }\n}\n"
  },
  {
    "path": "src/localize/localize.ts",
    "content": "import * as en from './languages/en.json';\r\nimport * as de from './languages/de.json';\r\nimport { HaFormSchema } from '../editor/ha-form';\r\nimport * as sk from './languages/sk.json';\r\n\r\nconst languages = {\r\n  en: en,\r\n  de: de,\r\n  sk: sk,\r\n};\r\n\r\n/**\r\n * Translating Strings to different languages.\r\n * Thanks to custom-cards/spotify-card\r\n * @param string The Section-Key Pair\r\n * @param search String which should be replaced\r\n * @param replace String to replace with\r\n */\r\nexport function localize(string: string, capitalized = false, search = '', replace = ''): string {\r\n  const lang = (localStorage.getItem('selectedLanguage') || navigator.language.split('-')[0] || 'en')\r\n    .replace(/['\"]+/g, '')\r\n    .replace('-', '_');\r\n\r\n  let translated: string;\r\n  try {\r\n    translated = string.split('.').reduce((o, i) => o[i], languages[lang]);\r\n  } catch (e) {\r\n    translated = string.split('.').reduce((o, i) => o[i], languages['en']) as unknown as string;\r\n  }\r\n\r\n  if (translated === undefined)\r\n    translated = string.split('.').reduce((o, i) => o[i], languages['en']) as unknown as string;\r\n\r\n  if (search !== '' && replace !== '') {\r\n    translated = translated.replace(search, replace);\r\n  }\r\n  return capitalized ? capitalizeFirstLetter(translated) : translated;\r\n}\r\n\r\nfunction capitalizeFirstLetter(string: string) {\r\n  if (!string) return \"\";\r\n  return string.charAt(0).toUpperCase() + string.slice(1);\r\n}\r\n\r\nexport function computeLabel(schema: HaFormSchema) {\r\n  return `${localize('editor.settings.' + schema.name)} ${!schema.required ? `(${localize('editor.optional')})` : ''}`;\r\n}"
  },
  {
    "path": "src/power-distribution-card.ts",
    "content": "import { LitElement, html, TemplateResult, PropertyValues, CSSResultGroup } from 'lit';\n\nimport { customElement, property, state } from 'lit/decorators.js';\n\nimport { createThing } from './utils';\n\nimport { version } from '../package.json';\n\nimport { PDCConfig, EntitySettings, ArrowStates, BarSettings } from './types';\nimport { DefaultItem, DefaultConfig, PresetList, PresetObject } from './presets';\nimport { styles, narrow_styles } from './styles';\nimport { localize } from './localize/localize';\nimport { actionHandler } from './action-handler';\n\nimport './editor/editor';\n\nimport { ActionHandlerEvent, handleAction, hasAction, HomeAssistant, LovelaceCard, LovelaceCardConfig, registerCustomCard, formatNumber } from './utils';\nimport { CARD_TAG, EDITOR_TAG } from './card-tags';\nimport { computeCssColor } from './utils/compute-color';\nimport { debounce } from './utils/debounce';\n\nconsole.info(\n  `%c POWER-DISTRIBUTION-CARD %c ${version} `,\n  `font-weight: 500; color: black; background:#f6aa1c;`,\n  `font-weight: 500; color: #f6aa1c; background: #220901;`,\n);\n\nregisterCustomCard(CARD_TAG, 'Power Distribution Card', localize('common.description'));\n\n@customElement(CARD_TAG)\nexport class PowerDistributionCard extends LitElement {\n  /**\n   * Function for creating the editor for the power-distribution-card\n   */\n  public static async getConfigElement(): Promise<LitElement> {\n    await import('./editor/editor');\n    return document.createElement(EDITOR_TAG) as LitElement;\n  }\n\n  /**\n   * Returns a mock config for preview in the card picker\n   */\n  public static getStubConfig(): Record<string, unknown> {\n    return {\n      title: 'Title',\n      entities: [],\n      center: {\n        type: 'bars',\n        bars: [\n          { preset: 'autarky', name: localize('editor.settings.autarky') },\n          { preset: 'ratio', name: localize('editor.settings.ratio') },\n        ],\n      },\n    };\n  }\n\n  @property({ attribute: false }) public hass!: HomeAssistant;\n\n  @state() private _config!: PDCConfig;\n\n  @property() private _card!: LovelaceCard;\n\n  private _resizeObserver?: ResizeObserver;\n  @state() private _narrow = false;\n\n  /**\n   * Configuring all the passed Settings and Changing it to a more usefull Internal one.\n   * @param config The Config Object configured via YAML\n   */\n  public async setConfig(config: PDCConfig): Promise<void> {\n    //The Addition of the last object is needed to override the entities array for the preset settings\n    const _config = Object.assign({}, DefaultConfig, config);\n\n    // Migrate old format: center.content -> center.bars or center.card\n    if (_config.center && 'content' in _config.center) {\n      const oldContent = (_config.center as any).content;\n      const { content: _removed, ..._centerWithoutContent } = _config.center as any;\n      if (_config.center.type === 'bars') {\n        _config.center = { ..._centerWithoutContent, bars: oldContent as BarSettings[] };\n      } else if (_config.center.type === 'card') {\n        _config.center = { ..._centerWithoutContent, card: oldContent as import('./utils').LovelaceCardConfig };\n      } else {\n        _config.center = _centerWithoutContent;\n      }\n    }\n\n    // Applying Defaults depending on preset\n    _config.entities = config.entities.map((item) => {\n      if (item.preset && PresetList.includes(item.preset)) {\n        return Object.assign({}, DefaultItem, PresetObject[item.preset], item);\n      } else {\n        return item;\n      }\n    });\n\n    this._config = _config;\n  }\n\n  public firstUpdated(): void {\n    const _config = this._config;\n\n    //unit-of-measurement Auto Configuration from hass element\n    _config.entities.forEach((item, index) => {\n      if (item.entity && !item.unit_of_measurement) {\n        const hass_uom = this._state({ entity: item.entity, attribute: 'unit_of_measurement' }) as string;\n        this._config.entities[index].unit_of_measurement = hass_uom || 'W';\n      }\n    });\n\n    // Applying the same to bars\n    if (_config.center.type == 'bars' && _config.center.bars) {\n      const bars = _config.center.bars.map((item) => {\n        if (item.unit_of_measurement) return item;\n\n        let hass_uom = '%';\n        if (item.entity) {\n          hass_uom = this._state({ entity: item.entity, attribute: 'unit_of_measurement' }) as string;\n        }\n        return Object.assign({}, item, { unit_of_measurement: item.unit_of_measurement || hass_uom });\n      });\n\n      this._config.center = {\n          ...this._config.center,\n          bars: bars,\n      };\n    } else if (this._config.center.type == 'card' && this._config.center.card) {\n      this._card = this._createCardElement(this._config.center.card);\n    }\n\n    //Resize Observer\n    this._adjustWidth();\n    this._attachObserver();\n  }\n\n  protected updated(changedProps: PropertyValues): void {\n    super.updated(changedProps);\n    if (!this._card || (!changedProps.has('hass') && !changedProps.has('editMode'))) {\n      return;\n    }\n    if (this.hass) {\n      this._card.hass = this.hass;\n    }\n  }\n\n  public static get styles(): CSSResultGroup {\n    return styles;\n  }\n\n  public connectedCallback(): void {\n    super.connectedCallback();\n    this.updateComplete.then(() => this._attachObserver());\n  }\n\n  public disconnectedCallback(): void {\n    if (this._resizeObserver) {\n      this._resizeObserver.disconnect();\n    }\n  }\n\n  private async _attachObserver(): Promise<void> {\n    if (!this._resizeObserver) {\n      this._resizeObserver = new ResizeObserver(debounce(() => this._adjustWidth(), 250, false));\n    }\n    const card = this.shadowRoot?.querySelector('ha-card');\n    // If we show an error or warning there is no ha-card\n    if (!card) return;\n    this._resizeObserver.observe(card);\n  }\n\n  private _adjustWidth(): void {\n    const card = this.shadowRoot?.querySelector('ha-card');\n    if (!card) return;\n    this._narrow = card.offsetWidth < 400;\n  }\n\n\n  private _formatValue(rawValue: number, entity?: string, decimals?: number):  [string, number] {\n    const precision = decimals != null\n      ? decimals\n      : (entity ? (this.hass.entities[entity]?.display_precision ?? 2) : 2);\n    const factor = 10 ** precision;\n    const rounded = Math.round(rawValue * factor) / factor;\n    return [formatNumber(rounded, this.hass.locale), rounded];\n  }\n\n  /**\n   * Retrieving the sensor value of hass for a Item as a number\n   * @param item a Settings object\n   * @returns The current value from Homeassistant in Watts\n   */\n  private _val(item: EntitySettings | BarSettings): number {\n    let modifier = item.invert_value ? -1 : 1;\n    //Proper K Scaling e.g. 1kW = 1000W\n    if (item.unit_of_measurement?.charAt(0) == 'k') modifier *= 1000;\n    // If an entity exists, check if the attribute setting is entered -> value from attribute else value from entity\n    let num = this._state(item as EntitySettings) as number;\n    //Applying Threshold\n    const threshold = (item as EntitySettings).threshold || null;\n    num = threshold ? (Math.abs(num) < threshold ? 0 : num) : num;\n    return num * modifier;\n  }\n\n  /**\n   * Retrieving the raw state of an sensor/attribute\n   * @param item A Settings object\n   * @returns entitys/attributes state\n   */\n  private _state(item: EntitySettings): unknown {\n    return item.entity && this.hass.states[item.entity]\n      ? item.attribute\n        ? this.hass.states[item.entity].attributes[item.attribute]\n        : this.hass.states[item.entity].state\n      : null;\n  }\n\n  /**\n   * This is the main rendering function for this card\n   * @returns html for the power-distribution-card\n   */\n  protected render(): TemplateResult {\n    const left_panel: TemplateResult[] = [];\n    const center_panel: (TemplateResult | LovelaceCard)[] = [];\n    const right_panel: TemplateResult[] = [];\n\n    let consumption = 0;\n    let production = 0;\n\n    this._config.entities.forEach((item, index) => {\n      const value = this._val(item);\n\n      if (!item.calc_excluded) {\n        if (item.producer && value > 0) {\n          production += value;\n        }\n        if (item.consumer && value < 0) {\n          consumption -= value;\n        }\n      }\n\n      const _item = this._render_item(value, item, index);\n      //Sorting the Items to either side\n      if (index % 2 == 0) left_panel.push(_item);\n      else right_panel.push(_item);\n    });\n\n    //Populating the Center Panel\n    const center = this._config.center;\n    switch (center.type) {\n      case 'none':\n        break;\n      case 'card':\n        if (this._card) {\n          center_panel.push(this._card);\n        } else {\n          console.warn('NO CARD');\n        }\n        break;\n      case 'bars':\n        center_panel.push(this._render_bars(consumption, production));\n        break;\n    }\n\n    return html` ${this._narrow ? narrow_styles : undefined}\n      <ha-card .header=${this._config.title}>\n        <div class=\"card-content\">\n          <div id=\"left-panel\">${left_panel}</div>\n          <div id=\"center-panel\">${center_panel}</div>\n          <div id=\"right-panel\">${right_panel}</div>\n        </div>\n      </ha-card>`;\n  }\n\n  private _handleAction(ev: ActionHandlerEvent): void {\n    if (this.hass && this._config && ev.detail.action) {\n      handleAction(\n        this,\n        this.hass,\n        {\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          entity: (ev.currentTarget as any).entity,\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          tap_action: (ev.currentTarget as any).tap_action,\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          double_tap_action: (ev.currentTarget as any).double_tap_action,\n        },\n        ev.detail.action,\n      );\n    }\n  }\n\n  /**\n   * Creating a Item Element\n   * @param value The Value of the Sensor\n   * @param item The EntitySettings Object of the Item\n   * @param index The index of the Item. This is needed for the Arrow Directions.\n   * @returns Html for a single Item\n   */\n  private _render_item(value: number, item: EntitySettings, index: number): TemplateResult {\n    //Placeholder item\n    if (!item.entity) {\n      return html`<item class=\"placeholder\"></item>`;\n    }\n    let math_value = value;\n    //Unit-Of-Display and Unit_of_measurement\n    let unit_of_display = item.unit_of_display || 'W';\n    const uod_split = unit_of_display.charAt(0);\n    if (uod_split[0] == 'k') {\n      math_value /= 1000;\n    } else if (item.unit_of_display == 'adaptive') {\n      //Using the uom suffix enables to adapt the initial unit to the automatic scaling naming\n      let uom_suffix = 'W';\n      if (item.unit_of_measurement) {\n        uom_suffix =\n          item.unit_of_measurement[0] == 'k' ? item.unit_of_measurement.substring(1) : item.unit_of_measurement;\n      }\n      if (Math.abs(math_value) > 999) {\n        math_value /= 1000;\n        unit_of_display = 'k' + uom_suffix;\n      } else {\n        unit_of_display = uom_suffix;\n      }\n    }\n\n    // Arrow directions\n    const state = item.invert_arrow ? math_value * -1 : math_value;\n\n    // Toggle Absolute Values\n    math_value = item.display_abs ? Math.abs(math_value) : math_value;\n\n    // Decimal Precision\n    let [formatValue, formatted_math_value] = this._formatValue(math_value, item.entity, item.decimals);\n\n    //NaNFlag for Offline Sensors for example\n    const NanFlag = isNaN(formatted_math_value);\n\n    // Secondary info\n    let secondary_info: string | undefined;\n    if (item.secondary_info_entity) {\n      if (item.secondary_info_attribute) {\n        secondary_info =\n          this._state({ entity: item.secondary_info_entity, attribute: item.secondary_info_attribute }) + '';\n      } else {\n        const siRaw = this._state({ entity: item.secondary_info_entity }) as string;\n\n        if (isNaN(parseFloat(siRaw))) {\n          secondary_info = String(siRaw);\n        } else {\n          secondary_info = `${this._formatValue(parseFloat(siRaw), item.secondary_info_entity, item.secondary_info_decimals)[0]}${this._state({ entity: item.secondary_info_entity, attribute: 'unit_of_measurement' }) || ''}`;\n        }\n      }\n    }\n    // Secondary info replace name\n    let displayName = item.name;\n    if (item.secondary_info_replace_name) {\n      displayName = secondary_info;\n      secondary_info = undefined;\n    }\n\n    //Preset Features\n    // 1. Battery Icon\n    let icon = item.icon;\n    if (item.preset === 'battery' && item.battery_percentage_entity) {\n      const bat_val = this._val({ entity: item.battery_percentage_entity });\n      if (!isNaN(bat_val)) {\n        icon = 'mdi:battery';\n        // mdi:battery-100 and -0 don't exist thats why we have to handle it seperately\n        if (bat_val < 5) {\n          icon = 'mdi:battery-outline';\n        } else if (bat_val < 95) {\n          icon = 'mdi:battery-' + (bat_val / 10).toFixed(0) + '0';\n        }\n      }\n    }\n    // 2. Grid Buy-Sell\n    let nameReplaceFlag = false;\n    let grid_buy_sell = html``;\n    if (item.preset === 'grid' && (item.grid_buy_entity || item.grid_sell_entity)) {\n      nameReplaceFlag = true;\n\n      const gridBuyValue = item.grid_buy_entity\n        ? this._formatValue(this._val({ entity: item.grid_buy_entity }), item.grid_buy_entity, item.decimals)[0]\n        : undefined;\n      const gridSellValue = item.grid_sell_entity\n        ? this._formatValue(this._val({ entity: item.grid_sell_entity }), item.grid_sell_entity, item.decimals)[0]\n        : undefined;\n\n      grid_buy_sell = html`\n        <div class=\"buy-sell\">\n          ${item.grid_buy_entity\n          ? html`<div class=\"grid-buy\">\n                B:\n                ${gridBuyValue}${this._state({\n            entity: item.grid_buy_entity,\n            attribute: 'unit_of_measurement',\n          }) || undefined}\n              </div>`\n          : undefined}\n          ${item.grid_sell_entity\n          ? html`<div class=\"grid-sell\">\n                S:\n                ${gridSellValue}${this._state({\n            entity: item.grid_sell_entity,\n            attribute: 'unit_of_measurement',\n          }) || undefined}\n              </div>`\n          : undefined}\n        </div>\n      `;\n    }\n\n    // COLOR CHANGE\n    const ct = item.color_threshold || 0;\n    // Icon color dependant on state\n    let icon_color: string | undefined;\n    if (item.icon_color) {\n      if (state > ct) icon_color = item.icon_color.bigger;\n      if (state < ct) icon_color = item.icon_color.smaller;\n      if (state == ct) icon_color = item.icon_color.equal;\n      if (icon_color) icon_color = computeCssColor(icon_color);\n    }\n    // Arrow color\n    let arrow_color: string | undefined;\n    if (item.arrow_color) {\n      if (state > ct) arrow_color = item.arrow_color.bigger;\n      if (state < ct) arrow_color = item.arrow_color.smaller;\n      if (state == ct) arrow_color = item.arrow_color.equal;\n      if (arrow_color) arrow_color = computeCssColor(arrow_color);\n    }\n\n    return html`\n      <item\n        .entity=${item.entity}\n        .tap_action=${item.tap_action}\n        .double_tap_action=${item.double_tap_action}\n        @action=${this._handleAction}\n        .actionHandler=${actionHandler({\n      hasDoubleClick: hasAction(item.double_tap_action),\n    })}\n      >\n        <badge>\n          <icon>\n            <ha-icon icon=\"${icon}\" style=\"${icon_color ? `color:${icon_color};` : ''}\"></ha-icon>\n            ${secondary_info ? html`<p class=\"secondary\">${secondary_info}</p>` : null}\n          </icon>\n          ${nameReplaceFlag ? grid_buy_sell : html`<p class=\"subtitle\">${displayName}</p>`}\n        </badge>\n        <value>\n          <p>${NanFlag ? `` : formatValue} ${NanFlag ? `` : unit_of_display}</p>\n          ${!item.hide_arrows\n        ? this._render_arrow(\n          //This takes the side the item is on (index even = left) into account for the arrows\n          value == 0 || NanFlag\n            ? 'none'\n            : index % 2 == 0\n              ? state > 0\n                ? 'right'\n                : 'left'\n              : state > 0\n                ? 'left'\n                : 'right',\n          arrow_color,\n        )\n        : html``\n      }\n        </value>\n      </item>\n    `;\n  }\n\n  /**\n   * Render function for Generating Arrows (CSS Only)\n   * @param direction One of three Options: none, right, left\n   * @param index To detect which side the item is on and adapt the direction accordingly\n   */\n  private _render_arrow(direction: ArrowStates, color?: string): TemplateResult {\n    const a = this._config.animation;\n    if (direction == 'none') {\n      return html` <div class=\"blank\" style=\"${color ? `background-color:${color};` : ''}\"></div> `;\n    } else {\n      return html`\n        <div class=\"arrow-container ${direction}\">\n          <div class=\"arrow ${a} \" style=\"border-left-color: ${color};\"></div>\n          <div class=\"arrow ${a} ${a == 'flash' ? 'delay-1' : ''}\" style=\"border-left-color: ${color};\"></div>\n          <div class=\"arrow ${a} ${a == 'flash' ? 'delay-2' : ''}\" style=\"border-left-color: ${color};\"></div>\n          <div class=\"arrow ${a}\" style=\"border-left-color: ${color};\"></div>\n        </div>\n      `;\n    }\n  }\n\n  /**\n   * Render Support Function Calculating and Generating the Autarky and Ratio Bars\n   * @param consumption the total home consumption\n   * @param production the total home production\n   * @returns html containing the bars as Template Results\n   */\n  private _render_bars(consumption: number, production: number): TemplateResult {\n    const bars: TemplateResult[] = [];\n    if (!this._config.center.bars || this._config.center.bars.length == 0) return html``;\n    this._config.center.bars.forEach((element) => {\n      let value = -1;\n\n      switch (element.preset) {\n        case 'autarky': //Autarky in Percent = Home Production(Solar, Battery)*100 / Home Consumption\n          if (!element.entity)\n            value = consumption != 0 ? Math.min(Math.round((production * 100) / Math.abs(consumption)), 100) : 0;\n          break;\n        case 'ratio': //Ratio in Percent = Home Consumption / Home Production(Solar, Battery)*100\n          if (!element.entity)\n            value = production != 0 ? Math.min(Math.round((Math.abs(consumption) * 100) / production), 100) : 0;\n          break;\n      }\n      const rawValue = value < 0 ? parseInt(this._val(element).toFixed(0), 10) : value;\n      const lb = element.lower_bound ?? 0;\n      const ub = element.upper_bound ?? 100;\n      const barHeight = Math.min(Math.max(((rawValue - lb) / (ub - lb)) * 100, 0), 100);\n      bars.push(html`\n        <div\n          class=\"bar-element\"\n          .entity=${element.entity}\n          .tap_action=${element.tap_action}\n          .double_tap_action=${element.double_tap_action}\n          @action=${this._handleAction}\n          .actionHandler=${actionHandler({\n        hasDoubleClick: hasAction(element.double_tap_action),\n      })}\n          style=\"${element.tap_action || element.double_tap_action ? 'cursor: pointer;' : ''}\"\n        >\n          <p class=\"bar-percentage\">${Math.round(barHeight)}${element.unit_of_measurement || '%'}</p>\n          <div class=\"bar-wrapper\" style=\"${element.bar_bg_color ? `background-color:${computeCssColor(element.bar_bg_color)};` : ''}\">\n            <bar style=\"height:${barHeight}%; background-color:${element.bar_color ? computeCssColor(element.bar_color) : ''};\" />\n          </div>\n          <p>${element.name || ''}</p>\n        </div>\n      `);\n    });\n    return html`${bars}`;\n  }\n\n  private _createCardElement(cardConfig: LovelaceCardConfig) {\n    const element = createThing(cardConfig) as LovelaceCard;\n    if (this.hass) {\n      element.hass = this.hass;\n    }\n    element.addEventListener(\n      'll-rebuild',\n      (ev) => {\n        ev.stopPropagation();\n        this._rebuildCard(element, cardConfig);\n      },\n      { once: true },\n    );\n    return element;\n  }\n\n  private _rebuildCard(cardElToReplace: LovelaceCard, config: LovelaceCardConfig): void {\n    const newCardEl = this._createCardElement(config);\n    if (cardElToReplace.parentElement) {\n      cardElToReplace.parentElement.replaceChild(newCardEl, cardElToReplace);\n    }\n    if (this._card === cardElToReplace) {\n      this._card = newCardEl;\n    }\n  }\n}\n"
  },
  {
    "path": "src/presets.ts",
    "content": "import { EntitySettings, PDCConfig } from './types';\r\n\r\nexport type PresetType = (typeof PresetList)[number];\r\n\r\nexport const PresetList = [\r\n  'battery',\r\n  'car_charger',\r\n  'consumer',\r\n  'grid',\r\n  'home',\r\n  'hydro',\r\n  'pool',\r\n  'producer',\r\n  'solar',\r\n  'wind',\r\n  'heating',\r\n  'placeholder',\r\n] as const;\r\n\r\nexport const PresetObject: { [key: string]: EntitySettings } = {\r\n  battery: {\r\n    consumer: true,\r\n    icon: 'mdi:battery-outline',\r\n    name: 'battery',\r\n    producer: true,\r\n  },\r\n  car_charger: {\r\n    consumer: true,\r\n    icon: 'mdi:car-electric',\r\n    name: 'car',\r\n  },\r\n  consumer: {\r\n    consumer: true,\r\n    icon: 'mdi:lightbulb',\r\n    name: 'consumer',\r\n  },\r\n  grid: {\r\n    icon: 'mdi:transmission-tower',\r\n    name: 'grid',\r\n  },\r\n  home: {\r\n    consumer: true,\r\n    icon: 'mdi:home-assistant',\r\n    name: 'home',\r\n  },\r\n  hydro: {\r\n    icon: 'mdi:hydro-power',\r\n    name: 'hydro',\r\n    producer: true,\r\n  },\r\n  pool: {\r\n    consumer: true,\r\n    icon: 'mdi:pool',\r\n    name: 'pool',\r\n  },\r\n  producer: {\r\n    icon: 'mdi:lightning-bolt-outline',\r\n    name: 'producer',\r\n    producer: true,\r\n  },\r\n  solar: {\r\n    icon: 'mdi:solar-power',\r\n    name: 'solar',\r\n    producer: true,\r\n  },\r\n  wind: {\r\n    icon: 'mdi:wind-turbine',\r\n    name: 'wind',\r\n    producer: true,\r\n  },\r\n  heating: {\r\n    icon: 'mdi:radiator',\r\n    name: 'heating',\r\n    consumer: true,\r\n  },\r\n  placeholder: {\r\n    name: 'placeholder',\r\n  },\r\n};\r\n\r\nexport const DefaultItem: EntitySettings = {\r\n  decimals: 2,\r\n  display_abs: true,\r\n  name: '',\r\n  unit_of_display: 'W',\r\n};\r\n\r\nexport const DefaultConfig: PDCConfig = {\r\n  type: '',\r\n  title: undefined,\r\n  animation: 'flash',\r\n  entities: [],\r\n  center: {\r\n    type: 'none',\r\n  },\r\n};\r\n"
  },
  {
    "path": "src/styles.ts",
    "content": "import { css, html } from 'lit';\r\n\r\nexport const styles = css`\r\n  * {\r\n    box-sizing: border-box;\r\n  }\r\n\r\n  p {\r\n    margin: 4px 0 4px 0;\r\n    text-align: center;\r\n  }\r\n\r\n  .card-content {\r\n    display: grid;\r\n    grid-template-columns: 1.5fr 1fr 1.5fr;\r\n    column-gap: 10px;\r\n  }\r\n\r\n  #center-panel {\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n    grid-column: 2;\r\n    flex-wrap: wrap;\r\n    min-width: 100px;\r\n  }\r\n\r\n  #center-panel > div {\r\n    display: flex;\r\n    width: 100%;\r\n    min-height: 150px;\r\n    max-height: 200px;\r\n    flex-basis: 50%;\r\n    flex-flow: column;\r\n  }\r\n\r\n  #center-panel > div > p {\r\n    flex: 0 1 auto;\r\n  }\r\n\r\n  .bar-wrapper {\r\n    position: relative;\r\n\r\n    width: 50%;\r\n    height: 80%;\r\n    margin: auto;\r\n\r\n    flex: 1 1 auto;\r\n\r\n    background-color: rgba(114, 114, 114, 0.2);\r\n  }\r\n\r\n  bar {\r\n    position: absolute;\r\n    right: 0;\r\n    bottom: 0;\r\n    left: 0;\r\n    background-color: var(--secondary-text-color);\r\n  }\r\n\r\n  item {\r\n    display: block;\r\n    overflow: hidden;\r\n    margin-bottom: 10px;\r\n    cursor: pointer;\r\n  }\r\n\r\n  .buy-sell {\r\n    height: 28px;\r\n    display: flex;\r\n    flex-direction: column;\r\n    font-size: 11px;\r\n    line-height: 14px;\r\n    text-align: center;\r\n  }\r\n\r\n  .grid-buy {\r\n    color: red;\r\n  }\r\n\r\n  .grid-sell {\r\n    color: green;\r\n  }\r\n\r\n  .placeholder {\r\n    height: 62px;\r\n  }\r\n\r\n  #right-panel > item > value {\r\n    float: left;\r\n  }\r\n\r\n  #right-panel > item > badge {\r\n    float: right;\r\n  }\r\n\r\n  badge {\r\n    float: left;\r\n\r\n    width: 50%;\r\n    padding: 4px;\r\n\r\n    border: 1px solid;\r\n    border-color: var(--disabled-text-color);\r\n    border-radius: 1em;\r\n\r\n    position: relative;\r\n  }\r\n\r\n  icon > ha-icon {\r\n    display: block;\r\n\r\n    width: 24px;\r\n    margin: 0 auto;\r\n\r\n    color: var(--state-icon-color);\r\n  }\r\n\r\n  .secondary {\r\n    position: absolute;\r\n    top: 4px;\r\n    right: 8%;\r\n    font-size: 80%;\r\n  }\r\n\r\n  value {\r\n    float: right;\r\n    width: 50%;\r\n    min-width: 54px;\r\n  }\r\n\r\n  value > p {\r\n    height: 1em;\r\n  }\r\n\r\n  /**************\r\n  ARROW ANIMATION\r\n  **************/\r\n\r\n  .blank {\r\n    width: 55px;\r\n    height: 4px;\r\n    margin: 8px auto 8px auto;\r\n    opacity: 0.2;\r\n    background-color: var(--secondary-text-color);\r\n  }\r\n\r\n  .arrow-container {\r\n    display: flex;\r\n    width: 55px;\r\n    height: 16px;\r\n    overflow: hidden;\r\n    margin: auto;\r\n  }\r\n\r\n  .left {\r\n    transform: rotate(180deg);\r\n  }\r\n\r\n  .arrow {\r\n    width: 0;\r\n    border-top: 8px solid transparent;\r\n    border-bottom: 8px solid transparent;\r\n    border-left: 16px solid var(--secondary-text-color);\r\n    margin: 0 1.5px;\r\n  }\r\n\r\n  .flash {\r\n    animation: flash 3s infinite steps(1);\r\n    opacity: 0.2;\r\n  }\r\n\r\n  @keyframes flash {\r\n    0%,\r\n    66% {\r\n      opacity: 0.2;\r\n    }\r\n    33% {\r\n      opacity: 0.8;\r\n    }\r\n  }\r\n\r\n  .delay-1 {\r\n    animation-delay: 1s;\r\n  }\r\n  .delay-2 {\r\n    animation-delay: 2s;\r\n  }\r\n\r\n  .slide {\r\n    animation: slide 1.5s linear infinite both;\r\n    position: relative;\r\n    left: -19px;\r\n  }\r\n\r\n  @keyframes slide {\r\n    0% {\r\n      -webkit-transform: translateX(0);\r\n      transform: translateX(0);\r\n    }\r\n    100% {\r\n      -webkit-transform: translateX(19px);\r\n      transform: translateX(19px);\r\n    }\r\n  }\r\n`;\r\n\r\nexport const narrow_styles = html`\r\n  <style>\r\n    /**********\r\n    Mobile View\r\n    **********/\r\n    .card-content {\r\n      grid-template-columns: 1fr 1fr 1fr;\r\n    }\r\n    .placeholder {\r\n      height: 114px !important;\r\n    }\r\n    item > badge,\r\n    item > value {\r\n      display: block;\r\n      float: none !important;\r\n\r\n      width: 72px;\r\n      margin: 0 auto;\r\n    }\r\n\r\n    .arrow {\r\n      margin: 0px 8px;\r\n    }\r\n  </style>\r\n`;\r\n"
  },
  {
    "path": "src/types.ts",
    "content": "import { PresetType } from './presets';\r\nimport { ActionConfig, LovelaceCardConfig } from './utils';\r\nimport { NavigateOptions } from './utils/hass-types/navigate';\r\n\r\ndeclare global {\r\n  interface HASSDomEvents {\r\n    \"action\": { action: string };\r\n    \"config-changed\": { config: any };\r\n    \"hass-more-info\": { entityId: string };\r\n    \"ll-custom\": ActionConfig;\r\n    \"ll-rebuild\": Record<string, unknown>;\r\n    \"ll-upgrade\": Record<string, unknown>;\r\n    \"show-dialog\": { dialogTag: string; dialogParams: unknown; dialogImport?: () => Promise<void>; addHistory?: boolean };\r\n    \"location-changed\": NavigateOptions;\r\n  }\r\n}\r\n\r\nexport interface PDCConfig extends LovelaceCardConfig {\r\n  title?: string;\r\n  animation?: 'none' | 'flash' | 'slide';\r\n  entities: EntitySettings[];\r\n  center: center;\r\n}\r\n\r\nexport interface EntitySettings extends presetFeatures {\r\n  attribute?: string;\r\n  arrow_color?: { bigger?: string; equal?: string; smaller?: string };\r\n  calc_excluded?: boolean;\r\n  consumer?: boolean;\r\n  color_threshold?: number;\r\n  decimals?: number;\r\n  display_abs?: boolean;\r\n  double_tap_action?: ActionConfig;\r\n  entity?: string;\r\n  hide_arrows?: boolean;\r\n  icon?: string;\r\n  icon_color?: { bigger?: string; equal?: string; smaller?: string };\r\n  invert_value?: boolean;\r\n  invert_arrow?: boolean;\r\n  name?: string | undefined;\r\n  preset?: PresetType;\r\n  producer?: boolean;\r\n  secondary_info_attribute?: string;\r\n  secondary_info_decimals?: number;\r\n  secondary_info_entity?: string;\r\n  secondary_info_replace_name?: boolean;\r\n  tap_action?: ActionConfig;\r\n  threshold?: number;\r\n  unit_of_display?: string;\r\n  unit_of_measurement?: string;\r\n}\r\n\r\nexport interface center {\r\n  type: 'none' | 'card' | 'bars';\r\n  bars?: BarSettings[];\r\n  card?: LovelaceCardConfig;\r\n}\r\n\r\nexport interface presetFeatures {\r\n  battery_percentage_entity?: string;\r\n  grid_sell_entity?: string;\r\n  grid_buy_entity?: string;\r\n}\r\nexport interface BarSettings {\r\n  bar_color?: string;\r\n  bar_bg_color?: string;\r\n  entity?: string;\r\n  invert_value?: boolean;\r\n  lower_bound?: number;\r\n  name?: string | undefined;\r\n  preset?: 'autarky' | 'ratio' | '';\r\n  tap_action?: ActionConfig;\r\n  unit_of_measurement?: string;\r\n  upper_bound?: number;\r\n  double_tap_action?: ActionConfig;\r\n}\r\n\r\nexport type ArrowStates = 'right' | 'left' | 'none';\r\n\r\nexport interface Target extends EventTarget {\r\n  checked?: boolean;\r\n  configValue?: string;\r\n  i?: number;\r\n  value?: string | EntitySettings[] | BarSettings[] | { bigger: string; equal: string; smaller: string };\r\n}\r\n\r\nexport interface CustomValueEvent<T> extends Event {\r\n  target: Target;\r\n  // currentTarget?: {\r\n  //   i?: number;\r\n  //   value?: string;\r\n  // };\r\n  detail?: {\r\n    value?: T;\r\n  };\r\n}\r\n\r\nexport interface EditorTarget extends EventTarget {\r\n  value?: string;\r\n  index?: number;\r\n  checked?: boolean;\r\n  configValue?: string;\r\n  type?: HTMLInputElement['type'];\r\n  config?: ActionConfig;\r\n}\r\n\r\nexport interface HTMLElementValue extends HTMLElement {\r\n  value: string;\r\n}\r\ndeclare global {\r\n  interface Window {\r\n    loadCardHelpers: () => Promise<void>;\r\n    customCards: { type?: string; name?: string; description?: string; preview?: boolean }[];\r\n    ResizeObserver: { new (callback: ResizeObserverCallback): ResizeObserver; prototype: ResizeObserver };\r\n  }\r\n\r\n  interface Element {\r\n    offsetWidth: number;\r\n  }\r\n}\r\n\r\n"
  },
  {
    "path": "src/utils/compute-color.ts",
    "content": "export const THEME_COLORS = new Set([\n  \"primary\",\n  \"accent\",\n  \"red\",\n  \"pink\",\n  \"purple\",\n  \"deep-purple\",\n  \"indigo\",\n  \"blue\",\n  \"light-blue\",\n  \"cyan\",\n  \"teal\",\n  \"green\",\n  \"light-green\",\n  \"lime\",\n  \"yellow\",\n  \"amber\",\n  \"orange\",\n  \"deep-orange\",\n  \"brown\",\n  \"light-grey\",\n  \"grey\",\n  \"dark-grey\",\n  \"blue-grey\",\n  \"black\",\n  \"white\",\n]);\n\nconst YAML_ONLY_THEMES_COLORS = new Set([\n  \"primary-text\",\n  \"secondary-text\",\n  \"disabled\",\n]);\n\nexport function computeCssVariableName(color: string): string {\n  if (THEME_COLORS.has(color) || YAML_ONLY_THEMES_COLORS.has(color)) {\n    return `--${color}-color`;\n  }\n  return color;\n}\n\nexport function computeCssColor(color: string): string {\n  const cssVarName = computeCssVariableName(color);\n  if (cssVarName !== color) {\n    return `var(${cssVarName})`;\n  }\n  return color;\n}\n\n/**\n * Validates if a string is a valid color.\n * Accepts: hex colors (#xxx, #xxxxxx), theme colors, and valid CSS color names.\n */\nexport function isValidColorString(color: string | undefined): boolean {\n  if (!color || typeof color !== \"string\") {\n    return false;\n  }\n\n  // Check if it's a theme color\n  if (THEME_COLORS.has(color)) {\n    return true;\n  }\n\n  // Check if it's a hex color\n  if (/^#([0-9A-Fa-f]{3}){1,2}$/.test(color)) {\n    return true;\n  }\n\n  // Check if it's a valid CSS color name by trying to parse it\n  // Use CSS.supports() for a more efficient test without DOM manipulation\n  // This checks if the browser recognizes the color value\n  try {\n    const style = new Option().style;\n    style.color = color;\n    return style.color !== \"\";\n  } catch {\n    return false;\n  }\n}"
  },
  {
    "path": "src/utils/create-thing.ts",
    "content": "import { fireEvent } from \"./hass-types/fire_event\";\nimport type { LovelaceCardConfig } from \"./hass-types/lovelace\";\n\nconst TIMEOUT = 2000;\n\nconst _createErrorCardElement = (error: string, config: LovelaceCardConfig) => {\n  const el = document.createElement(\"hui-error-card\") as any;\n  try {\n    el.setConfig({ type: \"error\", error, config });\n  } catch (_err) {\n    // ignore\n  }\n  return el;\n};\n\nconst _createElement = (tag: string, config: LovelaceCardConfig) => {\n  const element = document.createElement(tag) as any;\n  try {\n    element.setConfig(config);\n  } catch (err) {\n    console.error(tag, err);\n    return _createErrorCardElement((err as Error).message, config);\n  }\n  return element;\n};\n\nexport const createThing = (cardConfig: LovelaceCardConfig) => {\n  if (!cardConfig || typeof cardConfig !== \"object\" || !cardConfig.type) {\n    return _createErrorCardElement(\"No type defined\", cardConfig);\n  }\n\n  const { type } = cardConfig;\n\n  if (type.startsWith(\"custom:\")) {\n    const tag = type.slice(\"custom:\".length);\n    if (customElements.get(tag)) {\n      return _createElement(tag, cardConfig);\n    }\n\n    const element = _createErrorCardElement(\n      `Custom element doesn't exist: ${tag}.`,\n      cardConfig\n    );\n    element.style.display = \"None\";\n    const timer = window.setTimeout(() => {\n      element.style.display = \"\";\n    }, TIMEOUT);\n\n    customElements.whenDefined(tag).then(() => {\n      clearTimeout(timer);\n      fireEvent(element, \"ll-rebuild\");\n    });\n\n    return element;\n  }\n\n  const tag = `hui-${type}-card`;\n  if (customElements.get(tag)) {\n    return _createElement(tag, cardConfig);\n  }\n\n  const element = _createErrorCardElement(\n    `Unknown card type: ${type}.`,\n    cardConfig\n  );\n  element.style.display = \"None\";\n  const timer = window.setTimeout(() => {\n    element.style.display = \"\";\n  }, TIMEOUT);\n\n  customElements.whenDefined(tag).then(() => {\n    clearTimeout(timer);\n    fireEvent(element, \"ll-rebuild\");\n  });\n\n  return element;\n};\n"
  },
  {
    "path": "src/utils/custom-cards.ts",
    "content": "import { repository } from \"../../package.json\";\n\nexport function registerCustomCard(type: string, name: string, description: string): void {\n    const windowWithCards = window as unknown as Window & {\n        customCards: unknown[];\n    };\n\n    windowWithCards.customCards = windowWithCards.customCards || [];\n\n    windowWithCards.customCards.push({\n        type,\n        name,\n        description,\n        preview: true,\n        documentationURL: `${repository.url}/readme.md`,\n    });\n}\n"
  },
  {
    "path": "src/utils/debounce.ts",
    "content": "// From: src/common/util/debounce.ts https://raw.githubusercontent.com/home-assistant/frontend/446661915bbfd74b119176076d6d5f6ae7e392fa/src/common/util/debounce.ts\n\n// Returns a function, that, as long as it continues to be invoked, will not\n// be triggered. The function will be called after it stops being called for\n// N milliseconds. If `immediate` is passed, trigger the function on the\n// leading edge. The trailing edge only fires if there were additional calls\n// during the wait period.\n\nexport const debounce = <T extends any[]>(\n  func: (...args: T) => void,\n  wait: number,\n  immediate = false\n) => {\n  let timeout: number | undefined;\n  let trailingArgs: T | undefined;\n\n  const debouncedFunc = (...args: T): void => {\n    const isLeading = immediate && !timeout;\n\n    if (timeout) {\n      trailingArgs = args;\n    }\n    clearTimeout(timeout);\n\n    timeout = window.setTimeout(() => {\n      timeout = undefined;\n      if (trailingArgs) {\n        func(...trailingArgs);\n        trailingArgs = undefined;\n      } else if (!immediate) {\n        func(...args);\n      }\n    }, wait);\n\n    if (isLeading) {\n      func(...args);\n    }\n  };\n\n  debouncedFunc.cancel = () => {\n    clearTimeout(timeout);\n    trailingArgs = undefined;\n  };\n\n  return debouncedFunc;\n};"
  },
  {
    "path": "src/utils/get-lovelace.ts",
    "content": "export const getLovelace = () => {\n  const root = document\n    .querySelector(\"home-assistant\")\n    ?.shadowRoot?.querySelector(\"home-assistant-main\")?.shadowRoot;\n\n  const resolver =\n    root?.querySelector(\"ha-drawer partial-panel-resolver\") ||\n    root?.querySelector(\"app-drawer-layout partial-panel-resolver\");\n\n  const huiRoot = (((resolver as any)?.shadowRoot || resolver) as any)\n    ?.querySelector(\"ha-panel-lovelace\")\n    ?.shadowRoot?.querySelector(\"hui-root\");\n\n  if (huiRoot) {\n    const ll = huiRoot.lovelace;\n    ll.current_view = huiRoot.___curView;\n    return ll;\n  }\n\n  return null;\n};\n"
  },
  {
    "path": "src/utils/ha-component-loader.ts",
    "content": "\nexport const loadHaComponents = () => {\n    if (!customElements.get(\"ha-entity-picker\")) {\n      loadCustomElement(\"hui-entities-card\").then((el: any) => el?.getConfigElement());\n    }\n};\n\nexport const loadCustomElement = async <T = any>(name: string) => {\n  let Component = customElements.get(name) as T;\n  if (Component) {\n    return Component;\n  }\n  await customElements.whenDefined(name);\n  return customElements.get(name) as T;\n};"
  },
  {
    "path": "src/utils/hass-types/action.ts",
    "content": "\n/**\n * Types are from the  original Homeassistant Repository:\n * https://github.com/home-assistant/frontend/blob/dev/src/data/lovelace/config/action.ts\n */\n\nimport type { HassServiceTarget } from \"home-assistant-js-websocket\";\n\nexport interface ToggleActionConfig extends BaseActionConfig {\n  action: \"toggle\";\n}\n\nexport interface CallServiceActionConfig extends BaseActionConfig {\n  action: \"call-service\" | \"perform-action\";\n  /** @deprecated \"service\" is kept for backwards compatibility. Replaced by \"perform_action\". */\n  service?: string;\n  perform_action: string;\n  target?: HassServiceTarget;\n  /** @deprecated \"service_data\" is kept for backwards compatibility. Replaced by \"data\". */\n  service_data?: Record<string, unknown>;\n  data?: Record<string, unknown>;\n}\n\nexport interface NavigateActionConfig extends BaseActionConfig {\n  action: \"navigate\";\n  navigation_path: string;\n  navigation_replace?: boolean;\n}\n\nexport interface UrlActionConfig extends BaseActionConfig {\n  action: \"url\";\n  url_path: string;\n}\n\nexport interface MoreInfoActionConfig extends BaseActionConfig {\n  action: \"more-info\";\n  entity?: string;\n}\n\nexport interface AssistActionConfig extends BaseActionConfig {\n  action: \"assist\";\n  pipeline_id?: string;\n  start_listening?: boolean;\n}\n\nexport interface NoActionConfig extends BaseActionConfig {\n  action: \"none\";\n}\n\nexport interface CustomActionConfig extends BaseActionConfig {\n  action: \"fire-dom-event\";\n}\n\nexport interface BaseActionConfig {\n  action: string;\n  confirmation?: ConfirmationRestrictionConfig;\n}\n\nexport interface ConfirmationRestrictionConfig {\n  text?: string;\n  exemptions?: RestrictionConfig[];\n}\n\nexport interface RestrictionConfig {\n  user: string;\n}\n\nexport type ActionConfig =\n  | ToggleActionConfig\n  | CallServiceActionConfig\n  | NavigateActionConfig\n  | UrlActionConfig\n  | MoreInfoActionConfig\n  | AssistActionConfig\n  | NoActionConfig\n  | CustomActionConfig;\n\n\nexport interface ActionConfigParams {\n  entity?: string;\n  camera_image?: string;\n  image_entity?: string;\n  hold_action?: ActionConfig;\n  tap_action?: ActionConfig;\n  double_tap_action?: ActionConfig;\n}\n\n\nexport type IntegrationType =\n  | \"device\"\n  | \"helper\"\n  | \"hub\"\n  | \"service\"\n  | \"hardware\"\n  | \"entity\"\n  | \"system\";"
  },
  {
    "path": "src/utils/hass-types/action_handler.ts",
    "content": "/**\n * Types are from the  original Homeassistant Repository:\n * https://github.com/home-assistant/frontend/blob/dev/src/data/lovelace/action_handler.ts\n */\n\nimport { HASSDomEvent } from \"./event\";\n\nexport interface ActionHandlerOptions {\n  hasTap?: boolean;\n  hasHold?: boolean;\n  hasDoubleClick?: boolean;\n  disabled?: boolean;\n}\n\nexport interface ActionHandlerDetail {\n  action: \"hold\" | \"tap\" | \"double_tap\";\n}\n\nexport type ActionHandlerEvent = HASSDomEvent<ActionHandlerDetail>;"
  },
  {
    "path": "src/utils/hass-types/event.ts",
    "content": "/**\n * Types are from the  original Homeassistant Repository:\n * https://github.com/home-assistant/frontend/blob/dev/src/common/dom/fire_event.ts\n */\n\ndeclare global {\n  interface HASSDomEvents {}\n}\n\nexport type ValidHassDomEvent = keyof HASSDomEvents;\n\nexport interface HASSDomEvent<T> extends Event {\n  detail: T;\n}"
  },
  {
    "path": "src/utils/hass-types/fire_event.ts",
    "content": "// Polymer legacy event helpers used courtesy of the Polymer project.\n//\n// Copyright (c) 2017 The Polymer Authors. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//    * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//    * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//    * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\ndeclare global {\n  interface HASSDomEvents {}\n}\n\nexport type ValidHassDomEvent = keyof HASSDomEvents;\n\nexport interface HASSDomEvent<T> extends Event {\n  detail: T;\n}\n\n/**\n * Dispatches a custom event with an optional detail value.\n *\n * @param {string} type Name of event type.\n * @param {*=} detail Detail value containing event-specific\n *   payload.\n * @param {{ bubbles: (boolean|undefined),\n *           cancelable: (boolean|undefined),\n *           composed: (boolean|undefined) }=}\n *  options Object specifying options.  These may include:\n *  `bubbles` (boolean, defaults to `true`),\n *  `cancelable` (boolean, defaults to false), and\n *  `node` on which to fire the event (HTMLElement, defaults to `this`).\n * @return {Event} The new event that was fired.\n */\nexport const fireEvent = <HassEvent extends ValidHassDomEvent>(\n  node: HTMLElement | Window,\n  type: HassEvent,\n  detail?: HASSDomEvents[HassEvent],\n  options?: {\n    bubbles?: boolean;\n    cancelable?: boolean;\n    composed?: boolean;\n  }\n) => {\n  options = options || {};\n  // @ts-ignore\n  detail = detail === null || detail === undefined ? {} : detail;\n  const event = new Event(type, {\n    bubbles: options.bubbles === undefined ? true : options.bubbles,\n    cancelable: Boolean(options.cancelable),\n    composed: options.composed === undefined ? true : options.composed,\n  });\n  (event as any).detail = detail;\n  node.dispatchEvent(event);\n  return event;\n};"
  },
  {
    "path": "src/utils/hass-types/format-number.ts",
    "content": "import { shouldPolyfill } from \"@formatjs/intl-numberformat/should-polyfill.js\";\nimport { FrontendLocaleData, NumberFormat } from \"./homeassistant\";\n\nexport async function applyPolyfills(): Promise<void> {\n  if (shouldPolyfill()) {\n    await import(\"@formatjs/intl-numberformat/polyfill-force.js\");\n  }\n}\n\n\nexport const numberFormatToLocale = (\n  localeOptions: FrontendLocaleData\n): string | string[] | undefined => {\n  switch (localeOptions.number_format) {\n    case NumberFormat.comma_decimal:\n      return [\"en-US\", \"en\"]; // Use United States with fallback to English formatting 1,234,567.89\n    case NumberFormat.decimal_comma:\n      return [\"de\", \"es\", \"it\"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89\n    case NumberFormat.space_comma:\n      return [\"fr\", \"sv\", \"cs\"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89\n    case NumberFormat.quote_decimal:\n      return [\"de-CH\"]; // Use German (Switzerland) formatting 1'234'567.89\n    case NumberFormat.system:\n      return undefined;\n    default:\n      return localeOptions.language;\n  }\n};\n\n/**\n * Formats a number based on the user's preference with thousands separator(s) and decimal character for better legibility.\n *\n * @param num The number to format\n * @param localeOptions The user-selected language and formatting, from `hass.locale`\n * @param options Intl.NumberFormatOptions to use\n */\nexport const formatNumber = (\n  num: string | number,\n  localeOptions?: FrontendLocaleData,\n  options?: Intl.NumberFormatOptions\n): string =>\n  formatNumberToParts(num, localeOptions, options)\n    .map((part) => part.value)\n    .join(\"\");\n\n/**\n * Returns an array of objects containing the formatted number in parts\n * Similar to Intl.NumberFormat.prototype.formatToParts()\n *\n * Input params - same as for formatNumber()\n */\nexport const formatNumberToParts = (\n  num: string | number,\n  localeOptions?: FrontendLocaleData,\n  options?: Intl.NumberFormatOptions\n): any[] => {\n  const locale = localeOptions\n    ? numberFormatToLocale(localeOptions)\n    : undefined;\n\n  if (\n    localeOptions?.number_format !== NumberFormat.none &&\n    !Number.isNaN(Number(num))\n  ) {\n    return new Intl.NumberFormat(\n      locale,\n      getDefaultFormatOptions(num, options)\n    ).formatToParts(Number(num));\n  }\n\n  if (\n    !Number.isNaN(Number(num)) &&\n    num !== \"\" &&\n    localeOptions?.number_format === NumberFormat.none\n  ) {\n    // If NumberFormat is none, use en-US format without grouping.\n    return new Intl.NumberFormat(\n      \"en-US\",\n      getDefaultFormatOptions(num, {\n        ...options,\n        useGrouping: false,\n      })\n    ).formatToParts(Number(num));\n  }\n\n  return [{ type: \"literal\", value: num }];\n};\n\n/**\n * Generates default options for Intl.NumberFormat\n * @param num The number to be formatted\n * @param options The Intl.NumberFormatOptions that should be included in the returned options\n */\nexport const getDefaultFormatOptions = (\n  num: string | number,\n  options?: Intl.NumberFormatOptions\n): Intl.NumberFormatOptions => {\n  const defaultOptions: Intl.NumberFormatOptions = {\n    maximumFractionDigits: 2,\n    ...options,\n  };\n\n  if (typeof num !== \"string\") {\n    return defaultOptions;\n  }\n\n  // Keep decimal trailing zeros if they are present in a string numeric value\n  if (\n    !options ||\n    (options.minimumFractionDigits === undefined &&\n      options.maximumFractionDigits === undefined)\n  ) {\n    const digits = num.indexOf(\".\") > -1 ? num.split(\".\")[1].length : 0;\n    defaultOptions.minimumFractionDigits = digits;\n    defaultOptions.maximumFractionDigits = digits;\n  }\n\n  return defaultOptions;\n};"
  },
  {
    "path": "src/utils/hass-types/get_main_window.ts",
    "content": "// From: https://github.com/home-assistant/frontend/blob/dev/src/data/main_window.ts\nexport const MAIN_WINDOW_NAME = \"ha-main-window\";\n\n// From: https://github.com/home-assistant/frontend/blob/dev/src/common/dom/get_main_window.ts\nexport const mainWindow = (() => {\n  try {\n    return window.name === MAIN_WINDOW_NAME\n      ? window\n      : parent.name === MAIN_WINDOW_NAME\n        ? parent\n        : top!;\n  } catch {\n    return window;\n  }\n})();"
  },
  {
    "path": "src/utils/hass-types/handle-action.ts",
    "content": "import { ActionConfig } from \"./action\";\nimport { fireEvent } from \"./fire_event\";\nimport { forwardHaptic } from \"./haptics\";\nimport { HomeAssistant } from \"./homeassistant\";\nimport { domainToName } from \"./integration\";\nimport { navigate } from \"./navigate\";\nimport { showConfirmationDialog } from \"./show-dialog-box\";\nimport { showVoiceCommandDialog } from \"./show-ha-voice-command-dialog\";\nimport { showToast } from \"./toast\";\nimport { toggleEntity } from \"./toggle-entity\";\n\nexport interface ActionConfigParams {\n  entity?: string;\n  camera_image?: string;\n  image_entity?: string;\n  hold_action?: ActionConfig;\n  tap_action?: ActionConfig;\n  double_tap_action?: ActionConfig;\n}\n\nexport const handleAction = async (\n  node: HTMLElement,\n  hass: HomeAssistant,\n  config: ActionConfigParams,\n  action: string\n): Promise<void> => {\n  let actionConfig: ActionConfig | undefined;\n\n  if (action === \"double_tap\" && config.double_tap_action) {\n    actionConfig = config.double_tap_action;\n  } else if (action === \"hold\" && config.hold_action) {\n    actionConfig = config.hold_action;\n  } else if (action === \"tap\" && config.tap_action) {\n    actionConfig = config.tap_action;\n  }\n\n  if (!actionConfig) {\n    actionConfig = {\n      action: \"more-info\",\n    };\n  }\n\n  if (\n    actionConfig.confirmation &&\n    (!actionConfig.confirmation.exemptions ||\n      !actionConfig.confirmation.exemptions.some(\n        (e) => e.user === hass!.user?.id\n      ))\n  ) {\n    forwardHaptic(node, \"warning\");\n\n    let serviceName;\n    if (\n      actionConfig.action === \"call-service\" ||\n      actionConfig.action === \"perform-action\"\n    ) {\n      const [domain, service] = (actionConfig.perform_action ||\n        actionConfig.service)!.split(\".\", 2);\n      const serviceDomains = hass.services;\n      if (domain in serviceDomains && service in serviceDomains[domain]) {\n        await hass.loadBackendTranslation(\"title\");\n        const localize = await hass.loadBackendTranslation(\"services\");\n        serviceName = `${domainToName(localize, domain)}: ${\n          localize(`component.${domain}.services.${serviceName}.name`) ||\n          serviceDomains[domain][service].name ||\n          service\n        }`;\n      }\n    }\n\n    if (\n      !(await showConfirmationDialog(node, {\n        text:\n          actionConfig.confirmation.text ||\n          hass.localize(\"ui.panel.lovelace.cards.actions.action_confirmation\", {\n            action:\n              serviceName ||\n              hass.localize(\n                `ui.panel.lovelace.editor.action-editor.actions.${actionConfig.action}`\n              ) ||\n              actionConfig.action,\n          }),\n      }))\n    ) {\n      return;\n    }\n  }\n\n  switch (actionConfig.action) {\n    case \"more-info\": {\n      const entityId =\n        actionConfig.entity ||\n        config.entity ||\n        config.camera_image ||\n        config.image_entity;\n      if (entityId) {\n        fireEvent(node, \"hass-more-info\", { entityId });\n      } else {\n        showToast(node, {\n          message: hass.localize(\n            \"ui.panel.lovelace.cards.actions.no_entity_more_info\"\n          ),\n        });\n        forwardHaptic(node, \"failure\");\n      }\n      break;\n    }\n    case \"navigate\":\n      if (actionConfig.navigation_path) {\n        navigate(actionConfig.navigation_path, {\n          replace: actionConfig.navigation_replace,\n        });\n      } else {\n        showToast(node, {\n          message: hass.localize(\n            \"ui.panel.lovelace.cards.actions.no_navigation_path\"\n          ),\n        });\n        forwardHaptic(node, \"failure\");\n      }\n      break;\n    case \"url\": {\n      if (actionConfig.url_path) {\n        window.open(actionConfig.url_path);\n      } else {\n        showToast(node, {\n          message: hass.localize(\"ui.panel.lovelace.cards.actions.no_url\"),\n        });\n        forwardHaptic(node, \"failure\");\n      }\n      break;\n    }\n    case \"toggle\": {\n      if (config.entity) {\n        toggleEntity(hass, config.entity!);\n        forwardHaptic(node, \"light\");\n      } else {\n        showToast(node, {\n          message: hass.localize(\n            \"ui.panel.lovelace.cards.actions.no_entity_toggle\"\n          ),\n        });\n        forwardHaptic(node, \"failure\");\n      }\n      break;\n    }\n    case \"perform-action\":\n    case \"call-service\": {\n      if (!actionConfig.perform_action && !actionConfig.service) {\n        showToast(node, {\n          message: hass.localize(\"ui.panel.lovelace.cards.actions.no_action\"),\n        });\n        forwardHaptic(node, \"failure\");\n        return;\n      }\n      const [domain, service] = (actionConfig.perform_action ||\n        actionConfig.service)!.split(\".\", 2);\n      hass.callService(\n        domain,\n        service,\n        actionConfig.data ?? actionConfig.service_data,\n        actionConfig.target\n      );\n      forwardHaptic(node, \"light\");\n      break;\n    }\n    case \"assist\": {\n      showVoiceCommandDialog(node, hass, {\n        start_listening: actionConfig.start_listening ?? false,\n        pipeline_id: actionConfig.pipeline_id ?? \"last_used\",\n      });\n      break;\n    }\n    case \"fire-dom-event\": {\n      fireEvent(node, \"ll-custom\", actionConfig);\n    }\n  }};"
  },
  {
    "path": "src/utils/hass-types/haptics.ts",
    "content": "/**\n * Broadcast haptic feedback requests\n */\n\nimport { HASSDomEvent } from \"./event\";\nimport { fireEvent } from \"./fire_event\";\n\n// Allowed types are from iOS HIG.\n// https://developer.apple.com/design/human-interface-guidelines/ios/user-interaction/feedback/#haptics\n// Implementors on platforms other than iOS should attempt to match the patterns (shown in HIG) as closely as possible.\nexport type HapticType =\n  | \"success\"\n  | \"warning\"\n  | \"failure\"\n  | \"light\"\n  | \"medium\"\n  | \"heavy\"\n  | \"selection\";\n\ndeclare global {\n  // for fire event\n  interface HASSDomEvents {\n    haptic: HapticType;\n  }\n\n  interface GlobalEventHandlersEventMap {\n    haptic: HASSDomEvent<HapticType>;\n  }\n}\n\nexport const forwardHaptic = (node: HTMLElement, hapticType: HapticType) => {\n  fireEvent(node, \"haptic\", hapticType);\n};"
  },
  {
    "path": "src/utils/hass-types/has-action.ts",
    "content": "// From: https://github.com/home-assistant/frontend/blob/dev/src/panels/lovelace/common/has-action.ts\nimport { ActionConfig } from \"./action\";\n\nexport function hasAction(config?: ActionConfig): boolean {\n  return config !== undefined && config.action !== \"none\";\n}"
  },
  {
    "path": "src/utils/hass-types/homeassistant.ts",
    "content": "/**\n * This contains the typings for the hass Homeassistant object from various sources.\n * The main file is:\n * https://github.com/home-assistant/frontend/blob/dev/src/types.ts\n */\n\n\nimport type {\n  Auth,\n  Connection,\n  HassConfig,\n  HassEntities,\n  HassEntity,\n  HassServices,\n  HassServiceTarget,\n  MessageBase,\n} from \"home-assistant-js-websocket\";\nimport { HTMLTemplateResult } from \"lit\";\n\n\ntype EntityCategory = \"config\" | \"diagnostic\";\n\nexport interface EntityRegistryDisplayEntry {\n  entity_id: string;\n  name?: string;\n  icon?: string;\n  device_id?: string;\n  area_id?: string;\n  labels: string[];\n  hidden?: boolean;\n  entity_category?: EntityCategory;\n  translation_key?: string;\n  platform?: string;\n  display_precision?: number;\n  has_entity_name?: boolean;\n}\n\nexport interface RegistryEntry {\n  created_at: number;\n  modified_at: number;\n}\n\nexport interface DeviceRegistryEntry extends RegistryEntry {\n  id: string;\n  config_entries: string[];\n  config_entries_subentries: Record<string, (string | null)[]>;\n  connections: [string, string][];\n  identifiers: [string, string][];\n  manufacturer: string | null;\n  model: string | null;\n  model_id: string | null;\n  name: string | null;\n  labels: string[];\n  sw_version: string | null;\n  hw_version: string | null;\n  serial_number: string | null;\n  via_device_id: string | null;\n  area_id: string | null;\n  name_by_user: string | null;\n  entry_type: \"service\" | null;\n  disabled_by: \"user\" | \"integration\" | \"config_entry\" | null;\n  configuration_url: string | null;\n  primary_config_entry: string | null;\n}\n\nexport interface AreaRegistryEntry extends RegistryEntry {\n  aliases: string[];\n  area_id: string;\n  floor_id: string | null;\n  humidity_entity_id: string | null;\n  icon: string | null;\n  labels: string[];\n  name: string;\n  picture: string | null;\n  temperature_entity_id: string | null;\n}\n\nexport interface FloorRegistryEntry extends RegistryEntry {\n  floor_id: string;\n  name: string;\n  level: number | null;\n  icon: string | null;\n  aliases: string[];\n}\n\n\nexport interface ThemeVars {\n  // Incomplete\n  \"primary-color\": string;\n  \"text-primary-color\": string;\n  \"accent-color\": string;\n  [key: string]: string;\n}\n\nexport type Theme = ThemeVars & {\n  modes?: {\n    light?: ThemeVars;\n    dark?: ThemeVars;\n  };\n};\n\nexport interface Themes {\n  default_theme: string;\n  default_dark_theme: string | null;\n  themes: Record<string, Theme>;\n  // Currently effective dark mode. Will never be undefined. If user selected \"auto\"\n  // in theme picker, this property will still contain either true or false based on\n  // what has been determined via system preferences and support from the selected theme.\n  darkMode: boolean;\n  // Currently globally active theme name\n  theme: string;\n}\n\nexport enum NumberFormat {\n  language = \"language\",\n  system = \"system\",\n  comma_decimal = \"comma_decimal\",\n  decimal_comma = \"decimal_comma\",\n  quote_decimal = \"quote_decimal\",\n  space_comma = \"space_comma\",\n  none = \"none\",\n}\n\nexport enum TimeFormat {\n  language = \"language\",\n  system = \"system\",\n  am_pm = \"12\",\n  twenty_four = \"24\",\n}\n\nexport enum TimeZone {\n  local = \"local\",\n  server = \"server\",\n}\n\nexport enum DateFormat {\n  language = \"language\",\n  system = \"system\",\n  DMY = \"DMY\",\n  MDY = \"MDY\",\n  YMD = \"YMD\",\n}\n\nexport enum FirstWeekday {\n  language = \"language\",\n  monday = \"monday\",\n  tuesday = \"tuesday\",\n  wednesday = \"wednesday\",\n  thursday = \"thursday\",\n  friday = \"friday\",\n  saturday = \"saturday\",\n  sunday = \"sunday\",\n}\n\nexport interface FrontendLocaleData {\n  language: string;\n  number_format: NumberFormat;\n  time_format: TimeFormat;\n  date_format: DateFormat;\n  first_weekday: FirstWeekday;\n  time_zone: TimeZone;\n}\n\nexport type LocalizeFunc = (\n  key: string,\n  values?: Record<\n    string,\n    string | number | HTMLTemplateResult | null | undefined\n  >\n) => string;\n\n\nexport interface ValueChangedEvent<T> extends CustomEvent {\n  detail: {\n    value: T;\n  };\n}\n\nexport type Constructor<T = any> = new (...args: any[]) => T;\n\nexport interface ClassElement {\n  kind: \"field\" | \"method\";\n  key: PropertyKey;\n  placement: \"static\" | \"prototype\" | \"own\";\n  initializer?: (...args) => unknown;\n  extras?: ClassElement[];\n  finisher?: <T>(cls: Constructor<T>) => undefined | Constructor<T>;\n  descriptor?: PropertyDescriptor;\n}\n\nexport interface Credential {\n  auth_provider_type: string;\n  auth_provider_id: string;\n}\n\nexport interface MFAModule {\n  id: string;\n  name: string;\n  enabled: boolean;\n}\n\nexport interface CurrentUser {\n  id: string;\n  is_owner: boolean;\n  is_admin: boolean;\n  name: string;\n  credentials: Credential[];\n  mfa_modules: MFAModule[];\n}\n\n// Currently selected theme and its settings. These are the values stored in local storage.\n// Note: These values are not meant to be used at runtime to check whether dark mode is active\n// or which theme name to use, as this interface represents the config data for the theme picker.\n// The actually active dark mode and theme name can be read from hass.themes.\nexport interface ThemeSettings {\n  theme: string;\n  // Radio box selection for theme picker. Do not use in Lovelace rendering as\n  // it can be undefined == auto.\n  // Property hass.themes.darkMode carries effective current mode.\n  dark?: boolean;\n  primaryColor?: string;\n  accentColor?: string;\n}\n\nexport interface PanelInfo<T = Record<string, any> | null> {\n  component_name: string;\n  config: T;\n  icon: string | null;\n  title: string | null;\n  url_path: string;\n  config_panel_domain?: string;\n}\n\nexport type Panels = Record<string, PanelInfo>;\n\nexport interface CalendarViewChanged {\n  end: Date;\n  start: Date;\n  view: string;\n}\n\nexport type FullCalendarView =\n  | \"dayGridMonth\"\n  | \"dayGridWeek\"\n  | \"dayGridDay\"\n  | \"listWeek\";\n\nexport type ThemeMode = \"auto\" | \"light\" | \"dark\";\n\nexport interface ToggleButton {\n  label: string;\n  iconPath?: string;\n  value: string;\n}\n\nexport interface Translation {\n  nativeName: string;\n  isRTL: boolean;\n  hash: string;\n}\n\nexport interface TranslationMetadata {\n  fragments: string[];\n  translations: Record<string, Translation>;\n}\n\nexport interface IconMetaFile {\n  version: string;\n  parts: IconMeta[];\n}\n\nexport interface IconMeta {\n  start: string;\n  file: string;\n}\n\nexport interface Notification {\n  notification_id: string;\n  message: string;\n  title: string;\n  status: \"read\" | \"unread\";\n  created_at: string;\n}\n\nexport type Resources = Record<string, Record<string, string>>;\n\nexport interface Context {\n  id: string;\n  parent_id?: string;\n  user_id?: string | null;\n}\n\nexport interface ServiceCallResponse<T = any> {\n  context: Context;\n  response?: T;\n}\n\nexport interface ServiceCallRequest {\n  domain: string;\n  service: string;\n  serviceData?: Record<string, any>;\n  target?: HassServiceTarget;\n}\n\n\nexport interface CoreFrontendUserData {\n  showAdvanced?: boolean;\n  showEntityIdPicker?: boolean;\n}\n\nexport type TranslationCategory =\n  | \"title\"\n  | \"state\"\n  | \"entity\"\n  | \"entity_component\"\n  | \"exceptions\"\n  | \"config\"\n  | \"config_subentries\"\n  | \"config_panel\"\n  | \"options\"\n  | \"device_automation\"\n  | \"mfa_setup\"\n  | \"system_health\"\n  | \"application_credentials\"\n  | \"issues\"\n  | \"selector\"\n  | \"services\";\n\nexport const getHassTranslations = async (\n  hass: HomeAssistant,\n  language: string,\n  category: TranslationCategory,\n  integration?: string | string[],\n  config_flow?: boolean\n): Promise<Record<string, unknown>> => {\n  const result = await hass.callWS<{ resources: Record<string, unknown> }>({\n    type: \"frontend/get_translations\",\n    language,\n    category,\n    integration,\n    config_flow,\n  });\n  return result.resources;\n};\n\n\nexport interface HomeAssistant {\n  auth: Auth & { external?: any };\n  connection: Connection;\n  connected: boolean;\n  states: HassEntities;\n  entities: Record<string, EntityRegistryDisplayEntry>;\n  devices: Record<string, DeviceRegistryEntry>;\n  areas: Record<string, AreaRegistryEntry>;\n  floors: Record<string, FloorRegistryEntry>;\n  services: HassServices;\n  config: HassConfig;\n  themes: Themes;\n  selectedTheme: ThemeSettings | null;\n  panels: Panels;\n  panelUrl: string;\n  // i18n\n  // current effective language in that order:\n  //   - backend saved user selected language\n  //   - language in local app storage\n  //   - browser language\n  //   - english (en)\n  language: string;\n  // local stored language, keep that name for backward compatibility\n  selectedLanguage: string | null;\n  locale: FrontendLocaleData;\n  resources: Resources;\n  localize: LocalizeFunc;\n  translationMetadata: TranslationMetadata;\n  suspendWhenHidden: boolean;\n  enableShortcuts: boolean;\n  vibrate: boolean;\n  debugConnection: boolean;\n  dockedSidebar: \"docked\" | \"always_hidden\" | \"auto\";\n  defaultPanel: string;\n  moreInfoEntityId: string | null;\n  user?: CurrentUser;\n  userData?: CoreFrontendUserData | null;\n  hassUrl(path?): string;\n  callService<T = any>(\n    domain: ServiceCallRequest[\"domain\"],\n    service: ServiceCallRequest[\"service\"],\n    serviceData?: ServiceCallRequest[\"serviceData\"],\n    target?: ServiceCallRequest[\"target\"],\n    notifyOnError?: boolean,\n    returnResponse?: boolean\n  ): Promise<ServiceCallResponse<T>>;\n  callApi<T>(\n    method: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\",\n    path: string,\n    parameters?: Record<string, any>,\n    headers?: Record<string, string>\n  ): Promise<T>;\n  callApiRaw( // introduced in 2024.11\n    method: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\",\n    path: string,\n    parameters?: Record<string, any>,\n    headers?: Record<string, string>,\n    signal?: AbortSignal\n  ): Promise<Response>;\n  fetchWithAuth(path: string, init?: Record<string, any>): Promise<Response>;\n  sendWS(msg: MessageBase): void;\n  callWS<T>(msg: MessageBase): Promise<T>;\n  loadBackendTranslation(\n    category: Parameters<typeof getHassTranslations>[2],\n    integrations?: Parameters<typeof getHassTranslations>[3],\n    configFlow?: Parameters<typeof getHassTranslations>[4]\n  ): Promise<LocalizeFunc>;\n  loadFragmentTranslation(fragment: string): Promise<LocalizeFunc | undefined>;\n  formatEntityState(stateObj: HassEntity, state?: string): string;\n  formatEntityAttributeValue(\n    stateObj: HassEntity,\n    attribute: string,\n    value?: any\n  ): string;\n  formatEntityAttributeName(stateObj: HassEntity, attribute: string): string;\n}"
  },
  {
    "path": "src/utils/hass-types/integration.ts",
    "content": "// From: https://github.com/home-assistant/frontend/blob/dev/src/data/integration.ts\nimport { LocalizeFunc } from \"./homeassistant\";\n\nexport type IntegrationType =\n  | \"device\"\n  | \"helper\"\n  | \"hub\"\n  | \"service\"\n  | \"hardware\"\n  | \"entity\"\n  | \"system\";\n\nexport interface IntegrationManifest {\n  is_built_in: boolean;\n  overwrites_built_in?: boolean;\n  domain: string;\n  name: string;\n  config_flow: boolean;\n  documentation: string;\n  issue_tracker?: string;\n  dependencies?: string[];\n  after_dependencies?: string[];\n  codeowners?: string[];\n  requirements?: string[];\n  ssdp?: { manufacturer?: string; modelName?: string; st?: string }[];\n  zeroconf?: string[];\n  homekit?: { models: string[] };\n  integration_type?: IntegrationType;\n  loggers?: string[];\n  quality_scale?:\n    | \"bronze\"\n    | \"silver\"\n    | \"gold\"\n    | \"platinum\"\n    | \"no_score\"\n    | \"internal\"\n    | \"legacy\"\n    | \"custom\";\n  iot_class:\n    | \"assumed_state\"\n    | \"cloud_polling\"\n    | \"cloud_push\"\n    | \"local_polling\"\n    | \"local_push\";\n  single_config_entry?: boolean;\n  version?: string;\n}\n\n\nexport const domainToName = (\n  localize: LocalizeFunc,\n  domain: string,\n  manifest?: IntegrationManifest\n) => localize(`component.${domain}.title`) || manifest?.name || domain;"
  },
  {
    "path": "src/utils/hass-types/localize.ts",
    "content": "export type LocalizeKeys =\n  | FlattenObjectKeys<Omit<TranslationDict, \"supervisor\">>\n  | `panel.${string}`\n  | `ui.card.alarm_control_panel.${string}`\n  | `ui.card.weather.attributes.${string}`\n  | `ui.card.weather.cardinal_direction.${string}`\n  | `ui.card.lawn_mower.actions.${string}`\n  | `ui.common.${string}`\n  | `ui.components.calendar.event.rrule.${string}`\n  | `ui.components.selectors.file.${string}`\n  | `ui.components.logbook.messages.detected_device_classes.${string}`\n  | `ui.components.logbook.messages.cleared_device_classes.${string}`\n  | `ui.dialogs.entity_registry.editor.${string}`\n  | `ui.dialogs.more_info_control.lawn_mower.${string}`\n  | `ui.dialogs.more_info_control.vacuum.${string}`\n  | `ui.dialogs.quick-bar.commands.${string}`\n  | `ui.dialogs.unhealthy.reasons.${string}`\n  | `ui.dialogs.unsupported.reasons.${string}`\n  | `ui.panel.config.${string}.${\"caption\" | \"description\"}`\n  | `ui.panel.config.dashboard.${string}`\n  | `ui.panel.config.storage.segments.${string}`\n  | `ui.panel.config.zha.${string}`\n  | `ui.panel.config.zwave_js.${string}`\n  | `ui.panel.lovelace.card.${string}`\n  | `ui.panel.lovelace.editor.${string}`\n  | `ui.panel.page-authorize.form.${string}`\n  | `component.${string}`;\n\n// Tweaked from https://www.raygesualdo.com/posts/flattening-object-keys-with-typescript-types\nexport type FlattenObjectKeys<\n  T extends Record<string, any>,\n  Key extends keyof T = keyof T,\n> = Key extends string\n  ? T[Key] extends Record<string, unknown>\n    ? `${Key}.${FlattenObjectKeys<T[Key]>}`\n    : `${Key}`\n  : never;\n\nexport type TranslationDict = typeof import('../../localize/languages/en.json');"
  },
  {
    "path": "src/utils/hass-types/lovelace.ts",
    "content": "import { HomeAssistant } from \"./homeassistant\";\n\nexport type Condition =\n  | LocationCondition\n  | NumericStateCondition\n  | StateCondition\n  | ScreenCondition\n  | UserCondition\n  | OrCondition\n  | AndCondition\n  | NotCondition;\n\n// Legacy conditional card condition\nexport interface LegacyCondition {\n  entity?: string;\n  state?: string | string[];\n  state_not?: string | string[];\n}\n\ninterface BaseCondition {\n  condition: string;\n}\n\nexport interface LocationCondition extends BaseCondition {\n  condition: \"location\";\n  locations?: string[];\n}\n\nexport interface NumericStateCondition extends BaseCondition {\n  condition: \"numeric_state\";\n  entity?: string;\n  below?: string | number;\n  above?: string | number;\n}\n\nexport interface StateCondition extends BaseCondition {\n  condition: \"state\";\n  entity?: string;\n  state?: string | string[];\n  state_not?: string | string[];\n}\n\nexport interface ScreenCondition extends BaseCondition {\n  condition: \"screen\";\n  media_query?: string;\n}\n\nexport interface UserCondition extends BaseCondition {\n  condition: \"user\";\n  users?: string[];\n}\n\nexport interface OrCondition extends BaseCondition {\n  condition: \"or\";\n  conditions?: Condition[];\n}\n\nexport interface AndCondition extends BaseCondition {\n  condition: \"and\";\n  conditions?: Condition[];\n}\n\nexport interface NotCondition extends BaseCondition {\n  condition: \"not\";\n  conditions?: Condition[];\n}\n\n\nexport interface LovelaceCardConfig {\n  index?: number;\n  view_index?: number;\n  view_layout?: any;\n  /** @deprecated Use `grid_options` instead */\n  layout_options?: LovelaceLayoutOptions;\n  grid_options?: LovelaceGridOptions;\n  type: string;\n  [key: string]: any;\n  visibility?: Condition[];\n}\n\nexport interface LovelaceLayoutOptions {\n  grid_columns?: number | \"full\";\n  grid_rows?: number | \"auto\";\n  grid_max_columns?: number;\n  grid_min_columns?: number;\n  grid_min_rows?: number;\n  grid_max_rows?: number;\n}\n\nexport interface LovelaceGridOptions {\n  columns?: number | \"full\";\n  rows?: number | \"auto\";\n  max_columns?: number;\n  min_columns?: number;\n  min_rows?: number;\n  max_rows?: number;\n  fixed_rows?: boolean;\n  fixed_columns?: boolean;\n}\n\nexport interface LovelaceCard extends HTMLElement {\n  hass?: HomeAssistant;\n  preview?: boolean;\n  layout?: string;\n  connectedWhileHidden?: boolean;\n  getCardSize(): number | Promise<number>;\n  /** @deprecated Use `getGridOptions` instead */\n  getLayoutOptions?(): LovelaceLayoutOptions;\n  getGridOptions?(): LovelaceGridOptions;\n  setConfig(config: LovelaceCardConfig): void;\n}\n"
  },
  {
    "path": "src/utils/hass-types/navigate.ts",
    "content": "// Partially from https://github.com/home-assistant/frontend/blob/dev/src/common/navigate.ts\n\nimport { fireEvent } from \"./fire_event\";\nimport { mainWindow } from \"./get_main_window\";\n\n\nexport interface NavigateOptions {\n  replace?: boolean;\n  data?: any;\n}\n\n\nexport const navigate = (\n  path: string,\n  options?: NavigateOptions,\n) => {\n    const replace = options?.replace || false;\n\n    if (replace) {\n        history.replaceState(\n        history.state?.root ? { root: true } : (options?.data ?? null),\n        \"\",\n        `${mainWindow.location.pathname}#${path}`\n      );\n    } else {\n        history.pushState(null, \"\", path);\n    }\n    fireEvent(window, \"location-changed\", {\n        replace\n    });\n};"
  },
  {
    "path": "src/utils/hass-types/show-dialog-box.ts",
    "content": "// Derived From: https://github.com/home-assistant/frontend/blob/dev/src/dialogs/generic/show-dialog-box.ts\nimport { TemplateResult } from \"lit\";\nimport { fireEvent } from \"./fire_event\";\n\n\ninterface BaseDialogBoxParams {\n  confirmText?: string;\n  text?: string | TemplateResult;\n  title?: string;\n  warning?: boolean;\n}\n\nexport interface AlertDialogParams extends BaseDialogBoxParams {\n  confirm?: () => void;\n}\n\nexport interface ConfirmationDialogParams extends BaseDialogBoxParams {\n  dismissText?: string;\n  confirm?: () => void;\n  cancel?: () => void;\n  destructive?: boolean;\n}\n\nexport interface PromptDialogParams extends BaseDialogBoxParams {\n  inputLabel?: string;\n  dismissText?: string;\n  inputType?: string;\n  defaultValue?: string;\n  placeholder?: string;\n  confirm?: (out?: string) => void;\n  cancel?: () => void;\n  inputMin?: number | string;\n  inputMax?: number | string;\n}\n\nexport interface DialogBoxParams\n  extends ConfirmationDialogParams,\n    PromptDialogParams {\n  confirm?: (out?: string) => void;\n  confirmation?: boolean;\n  prompt?: boolean;\n}\n\nconst showDialogHelper = (\n  element: HTMLElement,\n  dialogParams: DialogBoxParams,\n  extra?: {\n    confirmation?: DialogBoxParams[\"confirmation\"];\n    prompt?: DialogBoxParams[\"prompt\"];\n  }\n) =>\n  new Promise((resolve) => {\n    const origCancel = dialogParams.cancel;\n    const origConfirm = dialogParams.confirm;\n\n    fireEvent(element, \"show-dialog\", {\n      dialogTag: \"dialog-box\",\n      dialogParams: {\n        ...dialogParams,\n        ...extra,\n        cancel: () => {\n          resolve(extra?.prompt ? null : false);\n          if (origCancel) {\n            origCancel();\n          }\n        },\n        confirm: (out) => {\n          resolve(extra?.prompt ? out : true);\n          if (origConfirm) {\n            origConfirm(out);\n          }\n        },\n      },\n    });\n  });\n\nexport const showConfirmationDialog = (\n  element: HTMLElement,\n  dialogParams: ConfirmationDialogParams\n) =>\n  showDialogHelper(element, dialogParams, {\n    confirmation: true,\n  }) as Promise<boolean>;"
  },
  {
    "path": "src/utils/hass-types/show-ha-voice-command-dialog.ts",
    "content": "import { fireEvent } from \"./fire_event\";\nimport type { HomeAssistant } from \"./homeassistant\";\n\n\n\nexport interface VoiceCommandDialogParams {\n  pipeline_id: \"last_used\" | \"preferred\" | string;\n  start_listening?: boolean;\n}\n\nexport const showVoiceCommandDialog = (\n  element: HTMLElement,\n  hass: HomeAssistant,\n  dialogParams: VoiceCommandDialogParams\n): void => {\n  if (hass.auth.external?.config.hasAssist) {\n    hass.auth.external!.fireMessage({\n      type: \"assist/show\",\n      payload: {\n        pipeline_id: dialogParams.pipeline_id,\n        // Start listening by default for app\n        start_listening: dialogParams.start_listening ?? true,\n      },\n    });\n    return;\n  }\n  fireEvent(element, \"show-dialog\", {\n    dialogTag: \"ha-voice-command-dialog\",\n    dialogParams: {\n      pipeline_id: dialogParams.pipeline_id,\n      // Don't start listening by default for web\n      start_listening: dialogParams.start_listening ?? false,\n    },\n  });\n};"
  },
  {
    "path": "src/utils/hass-types/toast.ts",
    "content": "import { fireEvent } from \"./fire_event\";\n\ndeclare global {\n  // for fire event\n  interface HASSDomEvents {\n    \"hass-notification\": ShowToastParams;\n  }\n}\n\nexport interface ShowToastParams {\n  // Unique ID for the toast. If a new toast is shown with the same ID as the previous toast, it will be replaced to avoid flickering.\n  id?: string;\n  message:\n    | string\n    | { translationKey: string; args?: Record<string, string> };\n  action?: ToastActionParams;\n  duration?: number;\n  dismissable?: boolean;\n}\n\nexport interface ToastActionParams {\n  action: () => void;\n  text:\n    | string\n    | { translationKey: string; args?: Record<string, string> };\n}\n\nexport const showToast = (el: HTMLElement, params: ShowToastParams) =>\n  fireEvent(el, \"hass-notification\", params);"
  },
  {
    "path": "src/utils/hass-types/toggle-entity.ts",
    "content": "import { HomeAssistant, ServiceCallResponse } from \"./homeassistant\";\n\n// From: https://github.com/home-assistant/frontend/blob/dev/src/panels/lovelace/common/entity/toggle-entity.ts\n/** States that we consider \"off\". */\nexport const STATES_OFF = [\"closed\", \"locked\", \"off\"];\n\nexport const toggleEntity = (\n  hass: HomeAssistant,\n  entityId: string\n): Promise<ServiceCallResponse> => {\n  const turnOn = STATES_OFF.includes(hass.states[entityId].state);\n  return turnOnOffEntity(hass, entityId, turnOn);\n};\n\n// From: https://github.com/home-assistant/frontend/blob/dev/src/panels/lovelace/common/entity/turn-on-off-entity.ts\nexport const turnOnOffEntity = (\n  hass: HomeAssistant,\n  entityId: string,\n  turnOn = true\n): Promise<ServiceCallResponse> => {\n  const stateDomain = computeDomain(entityId);\n  const serviceDomain = stateDomain === \"group\" ? \"homeassistant\" : stateDomain;\n\n  let service;\n  switch (stateDomain) {\n    case \"lock\":\n      service = turnOn ? \"unlock\" : \"lock\";\n      break;\n    case \"cover\":\n      service = turnOn ? \"open_cover\" : \"close_cover\";\n      break;\n    case \"button\":\n    case \"input_button\":\n      service = \"press\";\n      break;\n    case \"scene\":\n      service = \"turn_on\";\n      break;\n    case \"valve\":\n      service = turnOn ? \"open_valve\" : \"close_valve\";\n      break;\n    default:\n      service = turnOn ? \"turn_on\" : \"turn_off\";\n  }\n\n  return hass.callService(serviceDomain, service, { entity_id: entityId });\n};\n\n// From: https://github.com/home-assistant/frontend/blob/dev/src/common/entity/compute_domain.ts\nexport const computeDomain = (entityId: string): string =>\n  entityId.substring(0, entityId.indexOf(\".\"));"
  },
  {
    "path": "src/utils/index.ts",
    "content": "export * from './custom-cards';\nexport { createThing } from './create-thing';\nexport { getLovelace } from './get-lovelace';\n\nexport * from './hass-types/handle-action';\nexport * from './hass-types/has-action';\nexport * from './hass-types/action_handler';\nexport { ActionConfig } from './hass-types/action';\nexport * from './hass-types/event';\nexport * from './hass-types/homeassistant';\nexport * from './hass-types/lovelace';\nexport { fireEvent } from './hass-types/fire_event';\nexport { formatNumber, applyPolyfills } from './hass-types/format-number';\n\nexport function fireCustomEvent<T>(node: HTMLElement | Window, type: string, detail: T): void {\n  const event = new CustomEvent(type, { bubbles: false, composed: false, detail: detail });\n  node.dispatchEvent(event);\n}"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2017\",\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"Bundler\",\n    \"lib\": [\"ES2021\", \"dom\", \"dom.iterable\"],\n    \"noEmit\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"strict\": true,\n    \"noImplicitAny\": false,\n    \"skipLibCheck\": true,\n    \"resolveJsonModule\": true,\n    \"esModuleInterop\": true,\n    \"experimentalDecorators\": true\n  }\n}\n"
  }
]