[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\ntrim_trailing_whitespace = true\ninsert_final_newline = true\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".git-blame-ignore-revs",
    "content": "# git-blame supports ignoring specific commits, this allows us to hide formatting\n# commits from git-blame.\n#\n# You can use the ignore list file by running:\n#\n# $ git config blame.ignoreRevsFile .git-blame-ignore-revs\n\n# refactor: reformat and sort imports\n8b31e632dbf8a17dd4f52c05f6fc620bfc6ddbf1\n\n# refactor: use default prettier config\n1b5fa9a994cba8295141a56b2973397291d55255\n\n# refactor: replace eslint with biome\nc119669d580673b29d3aed0c8f19a0491f4680c0\n\n# refactor: migrate to biome v2\n1f72b1f60595db38bfe10b76fc8cbf668725dc2b\n"
  },
  {
    "path": ".gitattributes",
    "content": "# normalise line endings to lf on all platforms\n* text=auto eol=lf\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "ko_fi: jcwillox\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Report an issue\ndescription: Report an issue with the Paper Buttons Row plugin.\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        This issue form is for reporting bugs, you should search through\n        the existing issues to see if others have had the same problem.\n\n        Try fill as many fields as you can, to make it easier to address the issue.\n  - type: textarea\n    attributes:\n      label: The problem\n      description: >-\n        Describe the issue you are experiencing here, to communicate to the\n        maintainers. Tell us what you were trying to do and what happened.\n\n        Provide a clear and concise description of what the problem is.\n  - type: markdown\n    attributes:\n      value: |\n        ## Environment\n  - type: input\n    id: version\n    attributes:\n      label: What version of Paper Buttons Row has the issue?\n      description: >\n        Can be found in: [HACS -> Frontend -> Paper Buttons Row](https://my.home-assistant.io/redirect/hacs_repository/?owner=jcwillox&repository=lovelace-paper-buttons-row&category=plugin). The version will be displayed in first chip at the top.\n\n        [![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=jcwillox&repository=lovelace-paper-buttons-row&category=plugin)\n  - type: input\n    id: ha_version\n    attributes:\n      label: What version of Home Assistant are you running?\n      placeholder: Home Assistant YYYY.MM.XX\n      description: >\n        Can be found in: [Settings -> About](https://my.home-assistant.io/redirect/info/).\n\n        [![Open your Home Assistant instance and show your Home Assistant version information.](https://my.home-assistant.io/badges/info.svg)](https://my.home-assistant.io/redirect/info/)\n  - type: input\n    id: frontend_version\n    attributes:\n      label: What version of the Frontend are you running?\n      placeholder: Frontend YYYYMMDD.X\n      description: >\n        Can be found in: [Settings -> About](https://my.home-assistant.io/redirect/info/).\n\n        [![Open your Home Assistant instance and show your Home Assistant version information.](https://my.home-assistant.io/badges/info.svg)](https://my.home-assistant.io/redirect/info/)\n  - type: markdown\n    attributes:\n      value: |\n        # Details\n  - type: textarea\n    attributes:\n      label: Example YAML snippet\n      description: |\n        If applicable, please provide an example piece of YAML that can help reproduce this problem.\n        This can be from an automation, script, service or configuration.\n      render: yml\n  - type: textarea\n    attributes:\n      label: Anything in the logs that might be useful for us?\n      description: |\n        For example, error message, or stack traces.\n\n        To view the browsers logs, press F12 and go to the \"Console\" tab, or use the \"Inspect\" option in the browsers right-click menu.\n      render: python3\n  - type: textarea\n    attributes:\n      label: Additional information\n      description: >\n        If you have any additional information for us, use the field below.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: Feature request\ndescription: Suggest an idea for this project.\ntitle: \"[FR]: \"\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        This form is for submitting feature requests.\n\n        You may want to open a [discussion](https://github.com/jcwillox/lovelace-paper-buttons-row/discussions) instead for less concrete ideas that need to be discussed further.\n\n        Please fill out all the fields that are relevant to your feature request.\n  - type: textarea\n    attributes:\n      label: Is your feature request related to a problem? Please describe.\n      description: >-\n        A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n  - type: textarea\n    attributes:\n      label: Describe the solution you'd like\n      description: >-\n        A clear and concise description of what you want to happen.\n  - type: textarea\n    attributes:\n      label: Describe alternatives you've considered\n      description: >-\n        A clear and concise description of any alternative solutions or features you've considered.\n  - type: textarea\n    attributes:\n      label: Additional context\n      description: >-\n        Add any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\"github>jcwillox/renovate-config\", \":automergeMinor\"],\n  \"packageRules\": [\n    {\n      \"matchPackageNames\": [\"lit\"],\n      \"allowedVersions\": \"<3.0.0\"\n    }\n  ]\n}\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "content": "name: \"CI\"\n\non:\n  push:\n    branches:\n      - \"main\"\n      - \"feat**\"\n      - \"fix**\"\n    tags-ignore:\n      - \"**\"\n  pull_request:\n\njobs:\n  lint:\n    name: \"Lint\"\n    runs-on: ubuntu-latest\n    steps:\n      - name: \"Checkout the repository\"\n        uses: actions/checkout@v4.2.2\n\n      - name: \"Setup Biome\"\n        uses: biomejs/setup-biome@v2.6.0\n\n      - name: \"Run Biome\"\n        run: biome ci .\n\n  build:\n    name: \"Build & Test\"\n    runs-on: ubuntu-latest\n    steps:\n      - name: \"Checkout the repository\"\n        uses: actions/checkout@v4.2.2\n\n      - name: \"Setup pnpm\"\n        uses: pnpm/action-setup@v4.1.0\n\n      - name: \"Setup node\"\n        uses: actions/setup-node@v4.4.0\n        with:\n          node-version-file: package.json\n          cache: \"pnpm\"\n\n      - name: \"Install dependencies\"\n        run: pnpm install\n\n      - name: \"Typecheck\"\n        run: pnpm run typecheck\n\n      - name: \"Run Build\"\n        run: pnpm run build\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: \"Release\"\n\non:\n  push:\n    branches:\n      - \"beta\"\n      - \"alpha\"\n  workflow_dispatch:\n    inputs:\n      draft:\n        type: boolean\n        description: \"Draft release\"\n        default: false\n      release_type:\n        type: choice\n        description: \"Release type\"\n        default: \"auto\"\n        options:\n          - \"auto\"\n          - \"patch\"\n          - \"minor\"\n          - \"major\"\n\njobs:\n  publish:\n    name: \"Publish\"\n    runs-on: ubuntu-latest\n    steps:\n      - name: \"Checkout the repository\"\n        uses: actions/checkout@v4.2.2\n\n      - name: \"Setup pnpm\"\n        uses: pnpm/action-setup@v4.1.0\n\n      - name: \"Setup node\"\n        uses: actions/setup-node@v4.4.0\n        with:\n          node-version-file: package.json\n          cache: \"pnpm\"\n\n      - name: \"Install dependencies\"\n        run: pnpm install\n\n      - name: \"Release Package 📦\"\n        run: pnpm dlx @jcwillox/semantic-release-config\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          SEMANTIC_RELEASE_GITHUB_DRAFT: ${{ inputs.draft }}\n          SEMANTIC_RELEASE_FORCE_RELEASE: ${{ inputs.release_type }}\n          SEMANTIC_RELEASE_GITHUB_ASSETS: \"dist/*\"\n          SEMANTIC_RELEASE_CMD_PREPARE: \"VERSION=${nextRelease.version} pnpm build\"\n"
  },
  {
    "path": ".github/workflows/validate.yaml",
    "content": "name: \"Validate\"\n\non:\n  push:\n    branches:\n      - \"main\"\n      - \"feat**\"\n      - \"fix**\"\n    tags-ignore:\n      - \"**\"\n  pull_request:\n  schedule:\n    - cron: \"0 0 * * *\"\n\njobs:\n  validate-hacs:\n    runs-on: ubuntu-latest\n    name: \"HACS\"\n    steps:\n      - name: \"Checkout the repository\"\n        uses: actions/checkout@v4.2.2\n\n      - name: \"Validate HACS\"\n        uses: hacs/action@main\n        with:\n          category: plugin\n"
  },
  {
    "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.*\n\n# IDE files\n.idea/\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "lint-staged\n"
  },
  {
    "path": ".npmrc",
    "content": "save-exact=true\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"formulahendry.auto-rename-tag\",\n    \"editorconfig.editorconfig\",\n    \"dbaeumer.vscode-eslint\",\n    \"christian-kohler.path-intellisense\",\n    \"esbenp.prettier-vscode\",\n    \"redhat.vscode-yaml\"\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"editor.formatOnSave\": true,\n  \"editor.linkedEditing\": true,\n  \"editor.renderWhitespace\": \"trailing\",\n  \"html.autoClosingTags\": true,\n  \"editor.quickSuggestions\": {\n    \"strings\": true\n  },\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll\": \"explicit\"\n  },\n  \"[javascript][javascriptreact][typescript][typescriptreact][json][jsonc][yaml][html][markdown][postcss][css]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"vite.autoStart\": false\n}\n"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\n  \"version\": \"2.0.0\",\n  \"tasks\": [\n    {\n      \"type\": \"npm\",\n      \"script\": \"install\",\n      \"label\": \"npm: install\"\n    },\n    {\n      \"type\": \"npm\",\n      \"script\": \"lint\",\n      \"problemMatcher\": [\"$eslint-stylish\"],\n      \"label\": \"npm: lint\"\n    },\n    {\n      \"type\": \"npm\",\n      \"script\": \"build\",\n      \"group\": \"build\",\n      \"label\": \"npm: build\"\n    }\n  ]\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 Joshua Cowie-Willox\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": "# Paper Buttons Row\n\n[![HACS Badge](https://img.shields.io/badge/HACS-Default-41BDF5.svg?style=for-the-badge)](https://github.com/hacs/integration)\n[![License](https://img.shields.io/github/license/jcwillox/lovelace-paper-buttons-row?style=for-the-badge)](https://github.com/jcwillox/lovelace-paper-buttons-row/blob/main/LICENSE)\n[![Latest Release](https://img.shields.io/github/v/release/jcwillox/lovelace-paper-buttons-row?style=for-the-badge)](https://github.com/jcwillox/lovelace-paper-buttons-row/releases)\n[![GZIP Size](https://img.badgesize.io/https:/github.com/jcwillox/lovelace-paper-buttons-row/releases/latest/download/paper-buttons-row.js?style=for-the-badge&compression=gzip)](https://github.com/jcwillox/lovelace-paper-buttons-row/releases)\n\nThis is a complete rewrite of the original [`button-entity-row`](https://github.com/custom-cards/button-entity-row) plugin, that is more consistent with Home Assistant's [button card](https://www.home-assistant.io/lovelace/button/), it uses **actions** including `tap_action`, `double_tap_action` and `hold_action` allowing for greater customisation of the buttons behaviour. It also retains the ability to style the button based on state, but adds the ability to style the icon, text, and ripple effect separately. There is a new option for **icon alignment** and the buttons have haptic feedback.\n\n![example-main](examples/example-5.gif)\n\n## Options\n\n| Name         | Type                                                  | Requirement  | Description                                                                                                                                           |\n| ------------ | ----------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |\n| type         | `string`                                              | **Required** | `custom:paper-buttons-row`                                                                                                                            |\n| preset       | `string`                                              | **Optional** | The preset configuration to use e.g. `mushroom`. [See presets](#presets)                                                                              |\n| buttons      | List [`string` or [`button object`](#button-options)] | **Required** | List of buttons to display. [See button options](#button-options)                                                                                     |\n| base_config  | [`button object`](#button-options)                    | **Optional** | Specify a base config that will be deep-merged with each buttons config. Buttons can override the base config                                         |\n| styles       | `object`                                              | **Optional** | CSS styles to apply to the entire button group. e.g. to change the flex-box alignment.                                                                |\n| extra_styles | `string`                                              | **Optional** | Inject CSS directly into the paper-buttons-row container, useful for animations. [See extra styles](#extra-styles)                                    |\n|              |                                                       |              |                                                                                                                                                       |\n| position     | `\"center\"` \\| `\"right\"`                               | **Optional** | Position embedded buttons in the middle or end of the entity-row (default: `center`). [See embedding in entity rows](#embedding-in-other-entity-rows) |\n| hide_badge   | `boolean`                                             | **Optional** | Hide state badge when embedding in an entity-row                                                                                                      |\n| hide_state   | `boolean`                                             | **Optional** | Hide state text or toggle when embedding in an entity-row                                                                                             |\n\n### Button Options\n\nWhen only an `entity` is provided the button will attempt to toggle it by default.\n\n| Name              | Type                                           | Requirement  | Description                                                                                                                                      |\n| ----------------- | ---------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |\n| entity            | `string`                                       | **Optional** | The entity_id of the entity you want to show.                                                                                                    |\n| name              | `string` \\| [`template`](#templating)          | **Optional** | Name to use for entity. Use `false` to hide name.                                                                                                |\n| state             | `string` \\| [`template`](#templating)          | **Optional** | State to display for entity. Use `true` to show the entity state.                                                                                |\n| icon              | `string` \\| [`template`](#templating)          | **Optional** | The icon to display. Use `false` to hide icon.                                                                                                   |\n| image             | `string` \\| [`template`](#templating)          | **Optional** | Display an image instead of an icon. e.g. `/local/custom.png`.                                                                                   |\n| preset            | `string`                                       | **Optional** | The preset configuration to use e.g. `mushroom`. [See presets](#presets)                                                                         |\n| active            | `string` \\| `list[string]`                     | **Optional** | Configure the states which the button considers itself to be active, defaults to [`on`, `open`, `unlocked`]. [See CSS variables](#css-variables) |\n| ripple            | `\"fill\"` \\| `\"none\"` \\| `\"circle\"`             | **Optional** | Override the default shape of the ripple.                                                                                                        |\n| layout            | `string` \\| `object`                           | **Optional** | Change the layout of the icon, name and state fields. [See layout options](#layout)                                                              |\n| tooltip           | `string`                                       | **Optional** | Override the default tooltip. Use `false` to hide tooltip.                                                                                       |\n|                   |                                                |              |                                                                                                                                                  |\n| tap_action        | `map`                                          | **Optional** | Tap action map [See action options](#action-options)                                                                                             |\n| hold_action       | `map`                                          | **Optional** | Hold action map [See action options](#action-options)                                                                                            |\n| double_tap_action | `map`                                          | **Optional** | Double Tap action map [See action options](#action-options)                                                                                      |\n|                   |                                                |              |                                                                                                                                                  |\n| styles            | [`style object`](#style-options) (templatable) | **Optional** | Map of CSS styles to apply to the button, icon, text or ripple. [See style options](#style-options)                                              |\n| state_styles      | `map[state: style object]`                     | **Optional** | Map of states to a [`style object`](#style-options), [See example](#using-style-and-state_styles).                                               |\n| state_icons       | `map[state: icon]`                             | **Optional** | Material icon for each state of the entity. Map state to icon, [See example](#using-state-icons-state-text-and-actions).                         |\n| state_text        | `map[state: text]`                             | **Optional** | Button text for each state of the entity, Map state to text, [See example](#using-state-icons-state-text-and-actions).                           |\n\n### Action Options\n\nEach button supports the same actions as seen in Home Assistant's [button card](https://www.home-assistant.io/lovelace/button).\n\n| Name              | Type           | Default  | Supported options                                                                                   | Description                                                                                                         |\n| ----------------- | -------------- | -------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |\n| `action`          | `string`       | `toggle` | `more-info`, `toggle`, `call-service`, `fire-event`, `none`, `navigate`, `url`                      | Action to perform                                                                                                   |\n| `entity`          | `string`       | none     | Any entity id                                                                                       | **Only valid for `action: more-info`** to override the entity on which you want to call `more-info`                 |\n| `navigation_path` | `string`       | none     | Eg: `/lovelace/0/`                                                                                  | Path to navigate to (e.g. `/lovelace/0/`) when action defined as navigate                                           |\n| `url_path`        | `string`       | none     | Eg: `https://www.google.com`                                                                        | URL to open on click when action is `url`.                                                                          |\n|                   |                |          |                                                                                                     |                                                                                                                     |\n| `service`         | `string`       | none     | Any service                                                                                         | Service to call (e.g. `remote.send_command`) when `action` defined as `call-service`                                |\n| `service_data`    | `map`          | none     | Any service data                                                                                    | Service data to include (e.g. `command: play_pause`) when `action` defined as `call-service`.                       |\n| `target`          | `map`          | none     | Any service target                                                                                  | Service target to include (e.g. `entity_id: remote.bedroom`) when `action` defined as `call-service`.               |\n|                   |                |          |                                                                                                     |                                                                                                                     |\n| `event_type`      | `string`       | none     | Any event                                                                                           | Event to call (e.g. `custom_event`) when `action` defined as `fire-event`                                           |\n| `event_data`      | `map`          | none     | Any event data                                                                                      | Event data to include when `action` defined as `fire-event`.                                                        |\n|                   |                |          |                                                                                                     |                                                                                                                     |\n| `repeat`          | `number`       | none     | Eg: `500`                                                                                           | **Only valid for `hold_action`** optionally set the action to repeat every N milliseconds while the button is held. |\n|                   |                |          |                                                                                                     |                                                                                                                     |\n| `confirmation`    | `boolean\\|map` | false    | [See confirmation object](https://www.home-assistant.io/lovelace/actions/#options-for-confirmation) | Present a confirmation dialog to confirm the action.                                                                |\n\n### Presets\n\nA preset is just a predefined [button config](#button-options) object that will be deep-merged with the config, just like the `base_config` option.\n\n**Built-in Presets**\n\nPresets are now supported by default only the `mushroom` preset is included.\n\n![example-mushroom-light](examples/mushroom-light.png)\n\n![example-mushroom-dark](examples/mushroom-dark.png)\n\n```yaml\ntype: entities\nentities:\n  - type: custom:paper-buttons-row\n    # apply to all buttons\n    preset: mushroom\n    base_config:\n      # the same as above applies to all buttons\n      preset: mushroom\n    buttons:\n      - entity: light.bedroom_light\n        # or override on a button level\n        preset: mushroom\n\n      - entity: lock.front_door\n        # set what state is considered active\n        active: unlocked\n        styles:\n          # override the inactive color\n          --pbs-button-rgb-color: red\n          # override the active color\n          --pbs-button-rgb-active-color: green\n\n      - icon: mdi:power\n```\n\n**User-defined Presets**\n\nPresets can be defined in the top level of your dashboard, using the \"Raw configuration editor\" mode.\n\n```yaml\npaper_buttons_row:\n  presets:\n    my_custom_preset:\n      ripple: fill\n      styles:\n        button:\n          color: red\n\nviews: ...\n```\n\n### Style Options\n\n| Name   | Type     | Requirement  | Description                                            |\n| ------ | -------- | ------------ | ------------------------------------------------------ |\n| button | `object` | **Optional** | CSS styles to apply to the button.                     |\n| icon   | `object` | **Optional** | CSS styles to apply to specifically the icon.          |\n| name   | `object` | **Optional** | CSS styles to apply to specifically the name field.    |\n| state  | `object` | **Optional** | CSS styles to apply to specifically the state field.   |\n| ripple | `object` | **Optional** | CSS styles to apply to specifically the ripple effect. |\n\nEach key can be templated e.g.\n\n```yaml\nstyles:\n  button:\n    color: >-\n      {% if is_state('light.bedroom', 'on') %}\n        red\n      {% else%}\n        cyan\n      {% endif %}\n```\n\n### CSS Variables\n\n**Base State**\n\n- `--pbs-button-color` – used to override the color of the button.\n- `--pbs-button-rgb-color` – same as above but expects a list of rgb values, e.g. `123, 123, 0`.\n- `--pbs-button-rgb-state-color` – this is set automatically to reference an `--rgb-state-*-color` variable.\n- `--pbs-button-rgb-default-color` - this is used to set the default color of the paper-buttons, it is not set by default.\n- `--rgb-state-default-color` – this is the default color provided by Home Assistant.\n\n**Base State (Background)**\n\n- `--pbs-button-bg-color` – used to override the background of the button default is not set.\n- `--pbs-button-rgb-bg-color` – same as above but expects a list of rgb values, e.g. `123, 123, 0`.\n- `--pbs-button-rgb-bg-opacity` – defaults to 1.\n\n**Active State**\n\n- `--paper-item-icon-active-color` – (deprecated) unset in 2022.12 was originally set to `#fdd835`.\n- `--pbs-button-active-color`\n- `--pbs-button-rgb-active-color`\n- `--pbs-button-rgb-state-color`\n- `--pbs-button-rgb-default-color`\n- `--rgb-state-default-color`\n\n**Active State (Background)**\n\n- `--pbs-button-bg-active-color`\n- `--pbs-button-rgb-bg-active-color`\n- `--pbs-button-rgb-bg-active-opacity`\n- `--pbs-button-rgb-bg-color`\n- `--pbs-button-rgb-bg-opacity`\n\n**Unavailable State**\n\n- `--pbs-button-unavailable-color`\n- `--pbs-button-rgb-unavailable-color`\n- `--rgb-disabled-color`\n\n### Extra Styles\n\nThe `extra_styles` option allow you to embed extra CSS into paper-buttons-row this allows you to specify custom animations and style the hover and active states among other things.\n\n**Animations & Hover/Active Effects**\n\n![example-embedded-hide](examples/example-animation-hover-active.gif)\n\nThere are two built-in animations `blink` and `rotating`.\n\n```yaml\n- type: custom:paper-buttons-row\n  extra_styles: |\n    /* define custom animation */\n    @keyframes bgswap1 {\n      0% {\n        background-image: url(\"/local/christmas-lights-ro.png\");\n      }\n      25% {\n        background-image: url(\"/local/christmas-lights-ro.png\");\n      }\n      50% {\n        background-image: url(\"/local/christmas-lights-gb.png\");\n      }\n      75% {\n        background-image: url(\"/local/christmas-lights-gb.png\");\n      }\n      100% {\n        background-image: url(\"/local/christmas-lights-ro.png\");\n      }\n    }\n    /* set hover and active effects for buttons */\n    paper-button:hover {\n      background-color: red;\n    }\n    paper-button:active {\n      background-color: yellow;\n    }\n    /* styles for the third button only */\n    paper-button:nth-child(3):hover {\n      background-color: green;\n    }\n    paper-button:nth-child(3):active {\n      background-color: purple;\n    }\n  buttons:\n    - icon: mdi:power\n      styles:\n        button:\n          animation: blink 2s ease infinite\n    - styles:\n        button:\n          width: 64px\n          height: 64px\n          background-size: cover\n          # use custom animation defined earlier\n          animation: bgswap1 2s ease infinite\n    - icon: mdi:power\n      styles:\n        button:\n          - animation: rotating 2s ease infinite\n```\n\n#### Data Attributes\n\nIf you use the [`extra_styles`](#extra-styles) option you can use data attributes to style the button based on the domain or state of the configured entity.\n\n- `data-domain` – The domain of the entity\n- `data-state` – The current templated state, which defaults to the entity state but could refer to an attribute if you configure the `state` option\n- `data-entity-state` – The state of the current entity.\n\n```css\npaper-button[data-state=\"on\"] {\n  color: red;\n}\n```\n\n### Global styles & base config\n\nYou can specify `styles` that apply to the actual flex-box used to contain each row of buttons. You can also specify a default `base_config` that is deep-merged with the config for each button, this helps reduce repetition in your configs.\n\n```yaml\ntype: custom:paper-buttons-row\n# styles applied to the row container\nstyles:\n  # override off/on colors\n  --pbs-button-bg-color: red\n  --pbs-button-bg-active-color: green\n  # align all buttons to the left\n  justify-content: flex-start\nbuttons:\n  - entity: light.bedroom_light\n```\n\n```yaml\ntype: custom:paper-buttons-row\nbase_config:\n  # will be applied to all configured buttons\n  state_styles:\n    \"on\":\n      # override color for the entire button\n      button:\n        color: yellow\n      # or override for the name only\n      name:\n        color: var(--primary-text-color)\n    \"off\":\n      button:\n        color: red\nbuttons:\n  - entity: light.bedroom_light\n  - entity: light.kitchen_light\n```\n\n### Layout\n\nThe pipe or bar `|` symbol is used to put elements next to each other, and an underscore `_` is used to place items below each other.\nYou can also define layouts using a list (row) and nested lists (columns).\n\nThese are some examples of simple layouts:\n\n![example-layout](examples/example-layout.png)\n\n```yaml\ntype: entities\nentities:\n  - type: custom:paper-buttons-row\n    buttons:\n      - entity: light.bedroom_light\n        layout: icon|name\n        # layout: [icon, name]\n\n      - entity: light.bedroom_light\n        layout: icon_name\n        # layout: [[icon, name]]\n\n      - entity: light.bedroom_light\n        layout: name|icon\n        # layout: [name, icon]\n\n      - entity: light.bedroom_light\n        layout: name_icon\n        # layout: [[name, icon]]\n```\n\nAdvanced example\n\n![example-layout-advanced](examples/example-layout-advanced.png)\n\n```yaml\ntype: entities\nentities:\n  - type: custom:paper-buttons-row\n    buttons:\n      - entity: light.bedroom_light\n        layout: icon_name|state\n        # layout: [[icon, name], [state]]\n```\n\n### Templating\n\n#### Template Object\n\n| Name        | Type     | Description                                                                       |\n| ----------- | -------- | --------------------------------------------------------------------------------- |\n| `entity`    | `string` | Optional: entity to extract data from, defaults to the rows configured entity.    |\n| `attribute` | `object` | Optional: extract an attribute from the entity, otherwise the state will be used. |\n| `prefix`    | `string` | Optional: string to append **before** the attribute/state.                        |\n| `postfix`   | `string` | Optional: string to append **after** the attribute/state.                         |\n| `case`      | `string` | Optional: change case of result must be one of `upper`, `lower`, `first`          |\n\n**Examples**\n\n![example-templating](examples/example-templating.png)\n\n```yaml\ntype: entities\nentities:\n  - type: custom:paper-buttons-row\n    buttons:\n      - entity: light.bedroom_light\n        layout: icon|name|state\n        name:\n          attribute: friendly_name\n          postfix: \": \"\n        state:\n          case: first\n```\n\nThe `state_text` and `state_styles` options will use the lowercase result of the template for the state field.\n\n```yaml\ntype: entities\nentities:\n  - type: \"custom:paper-buttons-row\"\n    buttons:\n      - entity: fan.bedroom\n        layout: icon|state\n        state:\n          attribute: speed\n        state_styles:\n          high:\n            color: red\n          medium:\n            color: yellow\n          low:\n            color: green\n        state_text:\n          high: III\n          medium: II\n          low: I\n        # ...\n```\n\n#### Jinja Templates\n\n_Note: that Jinja2 templates are slightly slower to load initially due to latency, as they are rendered in the backend, whereas the other are rendered in the frontend._\n\nJinja templates have access to a few special variables. Those are:\n\n- `config` - an object containing the entity row configuration.\n- `entity` - the entity_id from the current entity row configuration. This **must** be used instead of `config.entity` for the template to automatically update.\n- `user` - the username of the currently logged in user.\n- `browser` - the deviceID of the current browser (see [browser_mod](https://github.com/thomasloven/hass-browser_mod)).\n- `hash` - the hash part of the current URL.\n\n**Example**\n\n```yaml\ntype: entities\nentities:\n  - type: custom:paper-buttons-row\n    buttons:\n      - entity: light.bedroom_light\n        layout: icon|name|state\n        name: \"{{ state_attr(config.entity, 'friendly_name') }}: \"\n        state: \"{{ states(config.entity) | title }}\"\n```\n\n## Embedding in other entity rows\n\n![example-minimal-setup](examples/example-embedded.png)\n\nPaper Buttons Row can be embedded within most entity rows. As shown in the image above it inserts a `paper-buttons-row` row inline, this can be either before or after the final element.\n\n```yaml\ntype: entities\nentities:\n  - entity: light.bedroom_light\n    # add the following to a normal entity row to embed paper buttons.\n    extend_paper_buttons_row:\n      position: # can be either `center` or `right`, defaults to `center`.\n      # ... normal paper-buttons-row config goes here.\n```\n\nWhen extending entity rows there are options to control the position of the inserted buttons, as well as to hide the badge or state elements.\n\n![example-embedded-hide](examples/example-embedded-hide.png)\n\n```yaml\ntype: entities\nentities:\n  - entity: input_boolean.test\n  - entity: input_boolean.test\n    name: Hide State\n    extend_paper_buttons_row:\n      hide_state: true\n      buttons:\n        - icon: mdi:power\n  - entity: input_select.test\n  - entity: input_select.test\n    name: Hide Badge\n    extend_paper_buttons_row:\n      hide_badge: true\n      position: right\n      buttons:\n        - icon: mdi:close\n```\n\n<details>\n<summary>Full example for the image above</summary>\n\n```yaml\ntype: entities\nshow_header_toggle: false\nentities:\n  - entity: light.bedroom_light\n    extend_paper_buttons_row:\n      # position defaults to center.\n      buttons:\n        - entity: scene.daylight\n          icon: \"mdi:brightness-5\"\n          name: false\n        - entity: script.light_colour_flow\n          icon: \"mdi:all-inclusive\"\n          name: false\n        - entity: scene.evening\n          icon: \"mdi:brightness-3\"\n          name: false\n          styles:\n            button:\n              margin-right: 8px\n\n  - type: divider\n\n  - entity: media_player.family_room_tv\n    name: TV\n    extend_paper_buttons_row:\n      # position after power button.\n      position: right\n      # use base config to set the default margin for all buttons.\n      base_config:\n        styles:\n          button:\n            margin-left: 2px\n            margin-right: 2px\n      buttons:\n        - icon: \"mdi:volume-mute\"\n          # override left margin for first button.\n          styles:\n            button:\n              margin-left: 0px\n        - icon: \"mdi:volume-minus\"\n        - icon: \"mdi:volume-plus\"\n```\n\n</details>\n\n## Examples\n\n### Minimal Setup.\n\n![example-minimal-setup](examples/example-3.png)\n\n```yaml\ntype: entities\nentities:\n  - type: \"custom:paper-buttons-row\"\n    buttons:\n      - scene.daylight # simplest way to create a button.\n\n      - entity: scene.rave\n        icon: \"mdi:track-light\" # override or add a mdi icon.\n\n      - entity: script.light_colour_flow\n        icon: \"mdi:all-inclusive\"\n        name: false # makes the button icon only.\n\n      - entity: scene.evening\n        icon: \"mdi:brightness-3\"\n        name: false\n```\n\n---\n\n### Using style and state_styles.\n\n![example-styled-toggle-button](examples/example-1.gif)\n![example-styled-toggle-button-top-aligned](examples/example-1-1.gif)\n\n```yaml\ntype: entities\nentities:\n  - type: \"custom:paper-buttons-row\"\n    buttons:\n      - entity: light.desk_leds\n        icon: \"mdi:lightbulb\"\n        styles: # These are the default styles that can be overridden by state styles.\n          button:\n            border-radius: 10px\n            font-size: 16px\n        state_styles:\n          \"off\": # define a state then provide a style object.\n            button:\n              background-color: var(--table-row-alternative-background-color)\n            name:\n              color: orange\n          \"on\":\n            button:\n              background-color: var(--primary-color)\n            icon:\n              color: \"#fdd835\" # this will change the icon colour when the entities state is on.\n            ripple:\n              color: orange # colour the ripple effect.\n\n      - entity: light.monitor_leds\n        icon: \"mdi:lightbulb\"\n        layout: icon_name\n        # layout: [[icon, name]]\n        styles:\n          button:\n            background-color: var(--table-row-alternative-background-color)\n            border-radius: 10px\n            font-size: 1.2rem\n            padding: 8px\n          icon:\n            --mdc-icon-size: 40px # make the icon bigger.\n        state_styles:\n          \"on\":\n            button:\n              background-color: var(--primary-color)\n            icon:\n              color: \"#fdd835\"\n            ripple:\n              color: orange\n```\n\n---\n\n### Using state icons, state text and actions.\n\n![example-lock-toggle](examples/example-4.gif)\n\n```yaml\ntype: entities\nentities:\n  - type: \"custom:paper-buttons-row\"\n    buttons:\n      - entity: lock.front_door\n        layout: icon|state # show the state field\n        state_icons:\n          \"unlocked\": \"mdi:lock-open\"\n          \"locked\": \"mdi:lock\"\n        state_text:\n          \"unlocked\": \"Unlocked\"\n          \"locked\": \"Locked\"\n\n        state_styles:\n          \"unlocked\":\n            button:\n              color: green\n          \"locked\":\n            button:\n              color: red\n        styles:\n          button:\n            background-color: var(--table-row-alternative-background-color)\n            border-radius: 40px\n            padding: 10px\n            font-size: 1.2rem\n\n        tap_action:\n          action: call-service\n          service: lock.lock\n          service_data:\n            entity_id: lock.front_door\n\n        hold_action:\n          action: call-service\n          service: lock.unlock\n          service_data:\n            entity_id: lock.front_door\n\n          # it's also possible to add a confirmation dialog to the action.\n          confirmation:\n            exemptions:\n              - user: 22a1119b08c54960822a0c6b896bed2\n            text: Are you sure you want to unlock?\n```\n\n---\n\n### Multiple rows of buttons.\n\n![example-multiple-rows-of-buttons](examples/example-2.png)\n\n```yaml\ntype: entities\nentities:\n  - type: \"custom:paper-buttons-row\"\n    buttons:\n      - icon: \"mdi:chevron-up\"\n        tap_action:\n          action: call-service\n          service: esphome.family_room_node_transmit_panasonic\n          service_data:\n            command: 218145196\n\n  # for multiple rows define multiple `paper-buttons-row`s.\n  - type: \"custom:paper-buttons-row\"\n    buttons:\n      - icon: \"mdi:chevron-left\"\n        tap_action:\n          action: call-service\n          service: esphome.family_room_node_transmit_panasonic\n          service_data:\n            command: 218161644\n      - icon: \"mdi:checkbox-blank-circle-outline\"\n      - icon: \"mdi:chevron-right\"\n        tap_action:\n          action: call-service\n          service: esphome.family_room_node_transmit_panasonic\n          service_data:\n            command: 218108188\n\n  - type: \"custom:paper-buttons-row\"\n    buttons:\n      - icon: \"mdi:chevron-down\"\n        tap_action:\n          action: call-service\n          service: esphome.family_room_node_transmit_panasonic\n          service_data:\n            command: 218128748\n```\n\n```yaml\ntype: entities\nentities:\n  - type: \"custom:paper-buttons-row\"\n    buttons:\n      # multiple rows of buttons can also be defined in one paper-buttons-row\n      # by using a list of lists of buttons.\n      - - light.monitor_leds\n        - light.desk_leds\n\n      - - light.bedroom_light\n        - entity: light.bedroom_underglow\n          icon: \"mdi:lightbulb\"\n```\n\n## Installation\n\n```yaml\nresources:\n  - url: /hacsfiles/lovelace-paper-buttons-row/paper-buttons-row.js\n    type: module\n```\n"
  },
  {
    "path": "biome.json",
    "content": "{\n  \"$schema\": \"https://biomejs.dev/schemas/2.1.3/schema.json\",\n  \"files\": {\n    \"includes\": [\"**\", \"!**/pnpm-lock.yaml\", \"!**/dist/**/*\"]\n  },\n  \"formatter\": {\n    \"enabled\": true,\n    \"useEditorconfig\": true\n  },\n  \"assist\": { \"actions\": { \"source\": { \"organizeImports\": \"on\" } } },\n  \"linter\": {\n    \"enabled\": true,\n    \"rules\": {\n      \"recommended\": true,\n      \"correctness\": {\n        \"noUnusedImports\": \"warn\",\n        \"noUnusedVariables\": \"warn\"\n      },\n      \"style\": {\n        \"noParameterAssign\": \"off\",\n        \"useNamingConvention\": {\n          \"level\": \"warn\",\n          \"options\": {\n            \"strictCase\": false,\n            \"conventions\": [\n              {\n                \"selector\": {\n                  \"kind\": \"catchParameter\"\n                },\n                \"match\": \"_|err\"\n              },\n              {\n                \"match\": \".*\"\n              }\n            ]\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "hacs.json",
    "content": "{\n  \"name\": \"Paper Buttons Row\"\n}\n"
  },
  {
    "path": "info.md",
    "content": "This is a complete rewrite of the original [`button-entity-row`](https://github.com/custom-cards/button-entity-row) plugin, that is more consistent with Home Assistant's [button card](https://www.home-assistant.io/lovelace/button/), it uses **actions** including `tap_action`, `double_tap_action` and `hold_action` allowing for greater customisation of the buttons behaviour. It also retains the ability to style the button based on state, but adds the ability to style the icon, text, and ripple effect separately. There is a new option for **icon alignment** and the buttons have haptic feedback.\n\nCheck out the [documentation](https://github.com/jcwillox/lovelace-paper-buttons-row) for the configuration options and examples.\n\n## Images\n\n<img src=\"https://github.com/jcwillox/lovelace-paper-buttons-row/blob/master/examples/example-5.gif?raw=true\" width=\"400px\">\n<img src=\"https://github.com/jcwillox/lovelace-paper-buttons-row/blob/master/examples/example-3.png?raw=true\" width=\"400px\">\n<img src=\"https://github.com/jcwillox/lovelace-paper-buttons-row/blob/master/examples/example-1.gif?raw=true\" width=\"400px\">\n<img src=\"https://github.com/jcwillox/lovelace-paper-buttons-row/blob/master/examples/example-1-1.gif?raw=true\" width=\"400px\">\n<img src=\"https://github.com/jcwillox/lovelace-paper-buttons-row/blob/master/examples/example-4.gif?raw=true\" width=\"400px\">\n<img src=\"https://github.com/jcwillox/lovelace-paper-buttons-row/blob/master/examples/example-2.png?raw=true\" width=\"400px\">\n<img src=\"https://github.com/jcwillox/lovelace-paper-buttons-row/blob/master/examples/example-embedded.png?raw=true\" width=\"400px\">\n\nMore example images than you could poke a stick at, I know...\n\n## Features\n\n- Create icon, text, or icon-text buttons.\n- Add css styling to each button per state!\n- Style the icon, name, state, and ripple effect separately.\n- Change the icon alignment and layout of the icon, name and state.\n- Add actions for `tap_action`, `double_tap_action` and `hold_action`.\n- Create multiple rows of buttons.\n- Embed buttons in other entity rows.\n- Tooltip support, configure custom tooltips.\n- Templating support.\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"paper-buttons-row\",\n  \"version\": \"0.0.0-dev\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"scripts\": {\n    \"prepare\": \"husky\",\n    \"dev\": \"vite build --watch\",\n    \"build\": \"vite build\",\n    \"build:tsc\": \"tsc && vite build\",\n    \"typecheck\": \"tsc\",\n    \"lint\": \"eslint . --cache --max-warnings=0 --ext js,cjs,mjs,jsx,ts,tsx\",\n    \"lint:fix\": \"pnpm run lint --fix\",\n    \"format\": \"prettier --cache --write .\",\n    \"format:check\": \"prettier --cache --check .\"\n  },\n  \"lint-staged\": {\n    \"*\": \"biome check --write --no-errors-on-unmatched --files-ignore-unknown=true\"\n  },\n  \"dependencies\": {\n    \"card-tools\": \"github:thomasloven/lovelace-card-tools#477f3d4\",\n    \"custom-card-helpers\": \"1.9.0\",\n    \"deepmerge\": \"4.3.1\",\n    \"fast-deep-equal\": \"3.1.3\",\n    \"home-assistant-js-websocket\": \"9.5.0\",\n    \"lit\": \"2.8.0\",\n    \"vite\": \"6.1.6\"\n  },\n  \"devDependencies\": {\n    \"@biomejs/biome\": \"2.1.3\",\n    \"@types/node\": \"22.17.0\",\n    \"husky\": \"9.1.7\",\n    \"lint-staged\": \"15.5.2\",\n    \"typescript\": \"5.8.3\",\n    \"vite-plugin-compression\": \"0.5.1\"\n  },\n  \"packageManager\": \"pnpm@10.13.1\",\n  \"engines\": {\n    \"node\": \">=20.x\"\n  }\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "onlyBuiltDependencies:\n  - '@biomejs/biome'\n  - esbuild\n"
  },
  {
    "path": "src/action-handler.ts",
    "content": "import {\n  type ActionHandlerDetail,\n  type ActionHandlerOptions,\n  fireEvent,\n} from \"custom-card-helpers\";\nimport deepEqual from \"fast-deep-equal\";\nimport { noChange } from \"lit\";\nimport {\n  type AttributePart,\n  Directive,\n  type DirectiveParameters,\n  directive,\n} from \"lit/directive.js\";\n\ndeclare global {\n  interface Navigator {\n    msMaxTouchPoints: number;\n  }\n}\n\nconst isTouch =\n  \"ontouchstart\" in window ||\n  navigator.maxTouchPoints > 0 ||\n  navigator.msMaxTouchPoints > 0;\n\nexport interface CustomActionHandlerOptions extends ActionHandlerOptions {\n  disabled?: boolean;\n  repeat?: number;\n  stopPropagation?: boolean;\n}\n\ninterface Ripple extends HTMLElement {\n  primary: boolean;\n  disabled: boolean;\n  unbounded: boolean;\n  startPress: () => void;\n  endPress: () => void;\n}\n\ninterface IActionHandler extends HTMLElement {\n  holdTime: number;\n  bind: (\n    element: ActionHandlerElement,\n    options?: CustomActionHandlerOptions,\n  ) => void;\n}\n\ninterface ActionHandlerElement extends HTMLElement {\n  actionHandler?: {\n    options: ActionHandlerOptions;\n    start?: (ev: Event) => void;\n    end?: (ev: Event) => void;\n    handleKeyDown?: (ev: KeyboardEvent) => void;\n  };\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    \"action-handler\": ActionHandler;\n  }\n\n  interface HASSDomEvents {\n    action: ActionHandlerDetail;\n  }\n}\n\nclass ActionHandler extends HTMLElement implements IActionHandler {\n  public holdTime = 500;\n\n  public ripple: Ripple;\n\n  protected timer?: number;\n\n  protected held = false;\n\n  private cancelled = false;\n\n  private dblClickTimeout?: number;\n\n  private repeatTimeout: NodeJS.Timeout | undefined;\n\n  private isRepeating = false;\n\n  constructor() {\n    super();\n    this.ripple = document.createElement(\"mwc-ripple\") as Ripple;\n  }\n\n  public connectedCallback(): void {\n    Object.assign(this.style, {\n      position: \"fixed\",\n      width: isTouch ? \"100px\" : \"50px\",\n      height: isTouch ? \"100px\" : \"50px\",\n      transform: \"translate(-50%, -50%) scale(0)\",\n      pointerEvents: \"none\",\n      zIndex: \"999\",\n      background: \"var(--primary-color)\",\n      display: null,\n      opacity: \"0.2\",\n      borderRadius: \"50%\",\n      transition: \"transform 180ms ease-in-out\",\n    });\n\n    this.appendChild(this.ripple);\n    this.ripple.primary = true;\n\n    for (const ev of [\n      \"touchcancel\",\n      \"mouseout\",\n      \"mouseup\",\n      \"touchmove\",\n      \"mousewheel\",\n      \"wheel\",\n      \"scroll\",\n    ]) {\n      document.addEventListener(\n        ev,\n        () => {\n          this.cancelled = true;\n          if (this.timer) {\n            this._stopAnimation();\n            clearTimeout(this.timer);\n            this.timer = undefined;\n            if (this.isRepeating && this.repeatTimeout) {\n              clearInterval(this.repeatTimeout);\n              this.isRepeating = false;\n            }\n          }\n        },\n        { passive: true },\n      );\n    }\n  }\n\n  public bind(\n    element: ActionHandlerElement,\n    options: CustomActionHandlerOptions = {},\n  ): void {\n    if (\n      element.actionHandler &&\n      deepEqual(options, element.actionHandler.options)\n    ) {\n      return;\n    }\n\n    if (element.actionHandler) {\n      if (element.actionHandler.start) {\n        element.removeEventListener(\"touchstart\", element.actionHandler.start);\n        element.removeEventListener(\"mousedown\", element.actionHandler.start);\n      }\n      if (element.actionHandler.end) {\n        element.removeEventListener(\"touchend\", element.actionHandler.end);\n        element.removeEventListener(\"touchcancel\", element.actionHandler.end);\n        element.removeEventListener(\"click\", element.actionHandler.end);\n      }\n      if (element.actionHandler.handleKeyDown) {\n        element.removeEventListener(\n          \"keydown\",\n          element.actionHandler.handleKeyDown,\n        );\n      }\n    } else {\n      element.addEventListener(\"contextmenu\", (ev: Event) => {\n        const e = ev || window.event;\n        if (e.preventDefault) {\n          e.preventDefault();\n        }\n        if (e.stopPropagation) {\n          e.stopPropagation();\n        }\n        e.cancelBubble = true;\n        e.returnValue = false;\n        return false;\n      });\n    }\n\n    element.actionHandler = { options };\n\n    if (options.disabled) {\n      return;\n    }\n\n    element.actionHandler.start = (ev: Event) => {\n      if (options.stopPropagation) {\n        ev.stopPropagation();\n      }\n      this.cancelled = false;\n      let x: number;\n      let y: number;\n      if ((ev as TouchEvent).touches) {\n        x = (ev as TouchEvent).touches[0].clientX;\n        y = (ev as TouchEvent).touches[0].clientY;\n      } else {\n        x = (ev as MouseEvent).clientX;\n        y = (ev as MouseEvent).clientY;\n      }\n\n      if (options.hasHold) {\n        this.held = false;\n        this.timer = window.setTimeout(() => {\n          this._startAnimation(x, y);\n          this.held = true;\n          if (options.repeat && !this.isRepeating) {\n            this.isRepeating = true;\n            this.repeatTimeout = setInterval(() => {\n              fireEvent(element, \"action\", { action: \"hold\" });\n            }, options.repeat);\n          }\n        }, this.holdTime);\n      }\n    };\n\n    element.actionHandler.end = (ev: Event) => {\n      if (options.stopPropagation) {\n        ev.stopPropagation();\n      }\n      // Don't respond when moved or scrolled while touch\n      if (\n        ev.type === \"touchcancel\" ||\n        (ev.type === \"touchend\" && this.cancelled)\n      ) {\n        if (this.isRepeating && this.repeatTimeout) {\n          clearInterval(this.repeatTimeout);\n          this.isRepeating = false;\n        }\n        return;\n      }\n      const target = ev.target as HTMLElement;\n      // Prevent mouse event if touch event\n      if (ev.cancelable) {\n        ev.preventDefault();\n      }\n      if (options.hasHold) {\n        clearTimeout(this.timer);\n        if (this.isRepeating && this.repeatTimeout) {\n          clearInterval(this.repeatTimeout);\n        }\n        this.isRepeating = false;\n        this._stopAnimation();\n        this.timer = undefined;\n      }\n      if (options.hasHold && this.held) {\n        if (!options.repeat) {\n          fireEvent(target, \"action\", { action: \"hold\" });\n        }\n      } else if (options.hasDoubleClick) {\n        if (\n          (ev.type === \"click\" && (ev as MouseEvent).detail < 2) ||\n          !this.dblClickTimeout\n        ) {\n          this.dblClickTimeout = window.setTimeout(() => {\n            this.dblClickTimeout = undefined;\n            fireEvent(target, \"action\", { action: \"tap\" });\n          }, 250);\n        } else {\n          clearTimeout(this.dblClickTimeout);\n          this.dblClickTimeout = undefined;\n          fireEvent(target, \"action\", { action: \"double_tap\" });\n        }\n      } else {\n        fireEvent(target, \"action\", { action: \"tap\" });\n      }\n    };\n\n    element.actionHandler.handleKeyDown = (ev: KeyboardEvent) => {\n      if (![\"Enter\", \" \"].includes(ev.key)) {\n        return;\n      }\n      (ev.currentTarget as ActionHandlerElement).actionHandler?.end?.(ev);\n    };\n\n    element.addEventListener(\"touchstart\", element.actionHandler.start, {\n      passive: true,\n    });\n    element.addEventListener(\"touchend\", element.actionHandler.end);\n    element.addEventListener(\"touchcancel\", element.actionHandler.end);\n\n    element.addEventListener(\"mousedown\", element.actionHandler.start, {\n      passive: true,\n    });\n    element.addEventListener(\"click\", element.actionHandler.end);\n\n    element.addEventListener(\"keydown\", element.actionHandler.handleKeyDown);\n  }\n\n  private _startAnimation(x: number, y: number) {\n    Object.assign(this.style, {\n      left: `${x}px`,\n      top: `${y}px`,\n      transform: \"translate(-50%, -50%) scale(1)\",\n    });\n    this.ripple.disabled = false;\n    this.ripple.startPress();\n    this.ripple.unbounded = true;\n  }\n\n  private _stopAnimation() {\n    this.ripple.endPress();\n    this.ripple.disabled = true;\n    Object.assign(this.style, {\n      left: null,\n      top: null,\n      transform: \"translate(-50%, -50%) scale(0)\",\n    });\n  }\n}\n\ncustomElements.define(\"paper-buttons-row-action-handler\", ActionHandler);\n\nconst getActionHandler = (): ActionHandler => {\n  const body = document.body;\n  if (body.querySelector(\"paper-buttons-row-action-handler\")) {\n    return body.querySelector(\n      \"paper-buttons-row-action-handler\",\n    ) as ActionHandler;\n  }\n\n  const actionHandler = document.createElement(\n    \"paper-buttons-row-action-handler\",\n  );\n  body.appendChild(actionHandler);\n\n  return actionHandler as ActionHandler;\n};\n\nexport const actionHandlerBind = (\n  element: ActionHandlerElement,\n  options?: CustomActionHandlerOptions,\n): void => {\n  const actionHandler: ActionHandler = getActionHandler();\n  if (!actionHandler) {\n    return;\n  }\n  actionHandler.bind(element, options);\n};\n\nexport const actionHandler = directive(\n  class extends Directive {\n    update(part: AttributePart, [options]: DirectiveParameters<this>) {\n      actionHandlerBind(part.element as ActionHandlerElement, options);\n      return noChange;\n    }\n\n    render(_options?: CustomActionHandlerOptions) {}\n  },\n);\n"
  },
  {
    "path": "src/action.ts",
    "content": "import {\n  fireEvent,\n  forwardHaptic,\n  type HomeAssistant,\n  navigate,\n  toggleEntity,\n} from \"custom-card-helpers\";\nimport type { ButtonActionConfig, ButtonConfig } from \"./types\";\nimport { showToast } from \"./utils\";\n\nexport const handleAction = (\n  node: HTMLElement,\n  hass: HomeAssistant,\n  config: ButtonConfig,\n  action: string,\n): void => {\n  let actionConfig: ButtonActionConfig | 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  handleActionConfig(node, hass, config, actionConfig);\n};\n\nexport function handleActionConfig(\n  node: HTMLElement,\n  hass: HomeAssistant,\n  config: ButtonConfig,\n  actionConfig: ButtonActionConfig | undefined,\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(\"warning\");\n\n    if (\n      !confirm(\n        actionConfig.confirmation.text ||\n          `Are you sure you want to ${actionConfig.action}?`,\n      )\n    ) {\n      return;\n    }\n  }\n\n  switch (actionConfig.action) {\n    case \"more-info\": {\n      const entityId = actionConfig.entity || config.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(\"failure\");\n      }\n      break;\n    }\n    case \"navigate\":\n      if (!actionConfig.navigation_path) {\n        showToast(node, {\n          message: hass.localize(\n            \"ui.panel.lovelace.cards.actions.no_navigation_path\",\n          ),\n        });\n        forwardHaptic(\"failure\");\n        return;\n      }\n      navigate(node, actionConfig.navigation_path);\n      forwardHaptic(\"light\");\n      break;\n    case \"url\":\n      if (!actionConfig.url_path) {\n        showToast(node, {\n          message: hass.localize(\"ui.panel.lovelace.cards.actions.no_url\"),\n        });\n        forwardHaptic(\"failure\");\n        return;\n      }\n      window.open(actionConfig.url_path);\n      forwardHaptic(\"light\");\n      break;\n    case \"toggle\":\n      if (!config.entity) {\n        showToast(node, {\n          message: hass.localize(\n            \"ui.panel.lovelace.cards.actions.no_entity_toggle\",\n          ),\n        });\n        forwardHaptic(\"failure\");\n        return;\n      }\n      toggleEntity(hass, config.entity);\n      forwardHaptic(\"light\");\n      break;\n    case \"call-service\": {\n      if (!actionConfig.service) {\n        showToast(node, {\n          message: hass.localize(\"ui.panel.lovelace.cards.actions.no_service\"),\n        });\n        forwardHaptic(\"failure\");\n        return;\n      }\n      const [domain, service] = actionConfig.service.split(\".\", 2);\n      hass.callService(\n        domain,\n        service,\n        actionConfig.service_data,\n        actionConfig.target,\n      );\n      forwardHaptic(\"light\");\n      break;\n    }\n    case \"fire-event\": {\n      if (!actionConfig.event_type) {\n        showToast(node, {\n          message: \"No event to call specified\",\n        });\n        forwardHaptic(\"failure\");\n        return;\n      }\n      hass.callApi(\n        \"POST\",\n        `events/${actionConfig.event_type}`,\n        actionConfig.event_data || {},\n      );\n      forwardHaptic(\"light\");\n      break;\n    }\n    case \"fire-dom-event\": {\n      fireEvent(node, \"ll-custom\", actionConfig);\n      forwardHaptic(\"light\");\n    }\n  }\n}\n\nexport function hasAction(config?: ButtonActionConfig): boolean {\n  return config !== undefined && config.action !== \"none\";\n}\n"
  },
  {
    "path": "src/const.ts",
    "content": "export const DOMAINS_TOGGLE = new Set([\n  \"fan\",\n  \"input_boolean\",\n  \"light\",\n  \"switch\",\n  \"group\",\n  \"automation\",\n  \"cover\",\n  \"script\",\n  \"vacuum\",\n  \"lock\",\n]);\n\nexport const STATES_ON = new Set([\"open\", \"unlocked\", \"on\"]);\nexport const STATE_ON = \"on\";\nexport const STATE_OFF = \"off\";\n\nexport const STATE_UNAVAILABLE = \"unavailable\";\n\nexport const TEMPLATE_OPTIONS = [\"name\", \"icon\", \"image\", \"state\"];\n"
  },
  {
    "path": "src/entity-row.ts",
    "content": "import { provideHass } from \"card-tools/src/hass\";\nimport {\n  createThing,\n  fireEvent,\n  type HomeAssistant,\n} from \"custom-card-helpers\";\nimport type { LitElement, PropertyValues } from \"lit\";\nimport type { ExternalPaperButtonRowConfig } from \"./types\";\n\ninterface LovelaceElement extends LitElement {\n  hass?: HomeAssistant;\n  config?: Record<string, unknown>;\n  _config?: Record<string, unknown>;\n}\n\ntype FirstUpdatedFn = (\n  this: LovelaceElement,\n  changedProperties: PropertyValues,\n) => void;\n\nexport function createModule(element: string, firstUpdated: FirstUpdatedFn) {\n  customElements.whenDefined(element).then(() => {\n    const el = customElements.get(element) as CustomElementConstructor;\n    const oFirstUpdated = el.prototype.firstUpdated;\n\n    el.prototype.firstUpdated = function (changedProperties) {\n      oFirstUpdated.call(this, changedProperties);\n      firstUpdated.call(this, changedProperties);\n    };\n\n    fireEvent(window, \"ll-rebuild\", {});\n  });\n}\n\ncreateModule(\"hui-generic-entity-row\", function () {\n  if (this.config?.extend_paper_buttons_row && this.shadowRoot) {\n    const pbConfig = this.config\n      .extend_paper_buttons_row as ExternalPaperButtonRowConfig;\n\n    const paperButtons = createThing(\n      {\n        type: \"custom:paper-buttons-row\",\n        ...pbConfig,\n        is_extended_row: true,\n      },\n      true,\n    );\n\n    provideHass(paperButtons);\n\n    let el = this.shadowRoot.querySelector<HTMLElement>(\"slot\");\n    if (!el) return;\n\n    if (el.parentElement) {\n      if (el.parentElement.parentElement) {\n        if (\n          el.parentElement.classList.contains(\"state\") &&\n          el.parentElement.parentElement.classList.contains(\"text-content\")\n        ) {\n          el = el.parentElement.parentElement;\n        } else {\n          console.error(\"unexpected parent node found\");\n        }\n      } else if (el.parentElement.classList.contains(\"text-content\")) {\n        el = el.parentElement;\n      } else {\n        console.error(\"unexpected parent node found\");\n      }\n    }\n\n    if (pbConfig.hide_state) {\n      el.style.display = \"none\";\n    }\n\n    if (pbConfig.hide_badge) {\n      const el = this.shadowRoot.querySelector<HTMLElement>(\"state-badge\");\n      if (el) {\n        el.style.visibility = \"hidden\";\n        el.style.marginLeft = \"-48px\";\n      }\n    }\n\n    if (pbConfig.position === \"right\") {\n      insertAfter(paperButtons, el);\n    } else {\n      insertBefore(paperButtons, el);\n    }\n  }\n});\n\nfunction insertBefore(node: HTMLElement, element: Element) {\n  element.parentNode?.insertBefore(node, element);\n}\n\nfunction insertAfter(node: HTMLElement, element: Element) {\n  if (element.nextElementSibling) {\n    insertBefore(node, element.nextElementSibling);\n  } else {\n    element.parentNode?.appendChild(node);\n  }\n}\n"
  },
  {
    "path": "src/entity.ts",
    "content": "import { computeEntity, type HomeAssistant } from \"custom-card-helpers\";\nimport type { ButtonConfig } from \"./types\";\n\nexport const computeStateName = (stateObj) => {\n  if (stateObj?.attributes?.friendly_name) {\n    return stateObj.attributes.friendly_name;\n  }\n  return stateObj?.entity_id\n    ? computeEntity(stateObj.entity_id).replace(/_/g, \" \")\n    : \"Unknown\";\n};\n\nfunction computeActionTooltip(hass, state, config, isHold) {\n  if (!config || !config.action || config.action === \"none\") {\n    return \"\";\n  }\n  let tooltip = `${\n    isHold\n      ? hass.localize(\"ui.panel.lovelace.cards.picture-elements.hold\")\n      : hass.localize(\"ui.panel.lovelace.cards.picture-elements.tap\")\n  } `;\n  switch (config.action) {\n    case \"navigate\":\n      tooltip += `${hass.localize(\n        \"ui.panel.lovelace.cards.picture-elements.navigate_to\",\n        \"location\",\n        config.navigation_path,\n      )}`;\n      break;\n    case \"url\":\n      tooltip += `${hass.localize(\n        \"ui.panel.lovelace.cards.picture-elements.url\",\n        \"url_path\",\n        config.url_path,\n      )}`;\n      break;\n    case \"toggle\":\n      tooltip += `${hass.localize(\n        \"ui.panel.lovelace.cards.picture-elements.toggle\",\n        \"name\",\n        state,\n      )}`;\n      break;\n    case \"call-service\":\n      tooltip += `${hass.localize(\n        \"ui.panel.lovelace.cards.picture-elements.call_service\",\n        \"name\",\n        config.service,\n      )}`;\n      break;\n    case \"more-info\":\n      tooltip += `${hass.localize(\n        \"ui.panel.lovelace.cards.picture-elements.more_info\",\n        \"name\",\n        state,\n      )}`;\n      break;\n  }\n  return tooltip;\n}\n\nexport const computeTooltip = (config: ButtonConfig, hass?: HomeAssistant) => {\n  if (!hass || config.tooltip === false) {\n    return \"\";\n  }\n  if (config.tooltip) {\n    return config.tooltip;\n  }\n  let stateName = \"\";\n  let tooltip = \"\";\n  if (config.entity) {\n    stateName =\n      config.entity in hass.states\n        ? computeStateName(hass.states[config.entity])\n        : config.entity;\n  }\n  if (!config.tap_action && !config.hold_action) {\n    return stateName;\n  }\n  const tapTooltip = config.tap_action\n    ? computeActionTooltip(hass, stateName, config.tap_action, false)\n    : \"\";\n  const holdTooltip = config.hold_action\n    ? computeActionTooltip(hass, stateName, config.hold_action, true)\n    : \"\";\n  const newline = tapTooltip && holdTooltip ? \"\\n\" : \"\";\n  tooltip = tapTooltip + newline + holdTooltip;\n  return tooltip;\n};\n"
  },
  {
    "path": "src/get-lovelace.ts",
    "content": "import type { LovelaceConfig } from \"custom-card-helpers\";\n\ntype HuiRootElement = HTMLElement & {\n  lovelace: {\n    config: LovelaceConfig;\n    current_view: number;\n    [key: string]: unknown;\n  };\n  ___curView: number;\n};\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    \"hui-root\": HuiRootElement;\n  }\n}\n\nexport const getLovelace = (): HuiRootElement[\"lovelace\"] | null => {\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?.shadowRoot || resolver)\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  return null;\n};\n"
  },
  {
    "path": "src/main.ts",
    "content": "import { hass } from \"card-tools/src/hass\";\nimport {\n  type ActionHandlerEvent,\n  computeDomain,\n  type HomeAssistant,\n} from \"custom-card-helpers\";\nimport deepmerge from \"deepmerge\";\nimport type { HassEntity } from \"home-assistant-js-websocket\";\nimport { html, LitElement, type PropertyValues, unsafeCSS } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { ifDefined } from \"lit/directives/if-defined.js\";\nimport { type StyleInfo, styleMap } from \"lit/directives/style-map.js\";\nimport { handleAction, hasAction } from \"./action\";\nimport { actionHandler } from \"./action-handler\";\nimport {\n  DOMAINS_TOGGLE,\n  STATE_OFF,\n  STATE_ON,\n  STATE_UNAVAILABLE,\n  STATES_ON,\n  TEMPLATE_OPTIONS,\n} from \"./const\";\nimport { computeStateName, computeTooltip } from \"./entity\";\nimport { handleButtonPreset } from \"./presets\";\nimport styles from \"./styles.css?inline\";\nimport { renderTemplateObjects, subscribeTemplate } from \"./template\";\nimport type {\n  ButtonConfig,\n  ExternalButtonConfig,\n  ExternalButtonType,\n  ExternalPaperButtonRowConfig,\n  PaperButtonRowConfig,\n  StyleConfig,\n} from \"./types\";\nimport { arrayToObject } from \"./utils\";\nimport \"./entity-row\";\n\nconsole.groupCollapsed(\n  `%c ${__NAME__} %c ${__VERSION__} `,\n  \"color: white; background: #039be5; font-weight: 700;\",\n  \"color: #039be5; background: white; font-weight: 700;\",\n);\nconsole.info(`branch   : ${__BRANCH__}`);\nconsole.info(`commit   : ${__COMMIT__}`);\nconsole.info(`built at : ${__BUILD_TIME__}`);\nconsole.info(\"https://github.com/jcwillox/lovelace-paper-buttons-row\");\nconsole.groupEnd();\n\nconst computeStateIcon = (config: ButtonConfig) => {\n  if (config.state_icons && typeof config.state === \"string\")\n    return config.state_icons[config.state.toLowerCase()];\n  return undefined;\n};\n\nconst computeStateText = (config: ButtonConfig) => {\n  if (config.state_text && typeof config.state === \"string\")\n    return config.state_text[config.state.toLowerCase()] || config.state;\n  return config.state;\n};\n\nconst migrateIconAlignment = (alignment: string) => {\n  console.warn(\n    __NAME__,\n    \"'align_icon' and 'align_icons' is deprecated and will be removed in a future version\",\n  );\n  switch (alignment) {\n    case \"top\":\n      return [[\"icon\", \"name\"]];\n    case \"bottom\":\n      return [[\"name\", \"icon\"]];\n    case \"right\":\n      return [\"name\", \"icon\"];\n    default:\n      return [\"icon\", \"name\"];\n  }\n};\n\n@customElement(\"paper-buttons-row\")\nexport class PaperButtonsRow extends LitElement {\n  static readonly styles = unsafeCSS(styles);\n\n  @property() private hass?: HomeAssistant;\n  @property() private _config?: PaperButtonRowConfig;\n\n  _templates?: unknown[];\n  _entities?: string[];\n\n  // convert an externally set config to the correct internal structure\n  private _transformConfig(\n    config: ExternalPaperButtonRowConfig,\n  ): PaperButtonRowConfig {\n    // check valid config\n    if (!config) throw new Error(\"Invalid configuration\");\n    if (!config.buttons) throw new Error(\"Missing buttons.\");\n    if (!Array.isArray(config.buttons))\n      throw new Error(\"Buttons must be an array.\");\n    if (config.buttons.length <= 0)\n      throw new Error(\"At least one button required.\");\n\n    // deep copy config\n    config = JSON.parse(JSON.stringify(config));\n\n    // ensure we always have 1 row\n    if (config.buttons.every((item) => !Array.isArray(item))) {\n      config.buttons = [config.buttons as Array<ExternalButtonType>];\n    } else if (!config.buttons.every((item) => Array.isArray(item))) {\n      throw new Error(\"Cannot mix rows and buttons\");\n    }\n\n    if (config.styles === undefined) {\n      // ensure styles is not undefined\n      config.styles = {} as StyleConfig;\n    } else {\n      // ensure styles are an object\n      for (const key in config.styles) {\n        config.styles[key] = arrayToObject(config.styles[key]);\n      }\n    }\n\n    config.buttons = (config.buttons as Array<Array<ExternalButtonType>>).map(\n      (row) => {\n        return row.map((bConfig) => {\n          // handle when config is not defined as a dictionary.\n          if (typeof bConfig === \"string\") {\n            bConfig = { entity: bConfig };\n          }\n\n          bConfig = deepmerge(config.base_config || {}, bConfig);\n\n          // transform layout config\n          if (typeof bConfig.layout === \"string\") {\n            bConfig.layout = bConfig.layout\n              .split(\"|\")\n              .map((column) =>\n                column.includes(\"_\") ? column.split(\"_\") : column,\n              );\n          }\n\n          // ensure active is a list\n          if (typeof bConfig.active === \"string\") {\n            bConfig.active = [bConfig.active];\n          }\n\n          // migrate `style` to `styles`\n          if (bConfig.styles === undefined) {\n            bConfig.styles = bConfig.style;\n          }\n          if (bConfig.styles === undefined) {\n            // ensure styles is not undefined\n            bConfig.styles = {} as StyleConfig;\n          } else {\n            // ensure styles are an object\n            for (const key in bConfig.styles) {\n              bConfig.styles[key] = arrayToObject(bConfig.styles[key]);\n            }\n          }\n          if (bConfig.state_styles) {\n            // ensure styles are an object\n            for (const stateKey in bConfig.state_styles) {\n              for (const key in bConfig.state_styles[stateKey]) {\n                bConfig.state_styles[stateKey][key] = arrayToObject(\n                  bConfig.state_styles[stateKey][key],\n                );\n              }\n            }\n          }\n\n          // apply default services.\n          bConfig = this._defaultConfig(config, bConfig);\n\n          return bConfig;\n        });\n      },\n    );\n\n    return config as PaperButtonRowConfig;\n  }\n\n  setConfig(config: ExternalPaperButtonRowConfig) {\n    this._config = this._transformConfig(config);\n    if (!this.hass) {\n      this.hass = hass() as HomeAssistant;\n    }\n    this._entities = [];\n    this._templates = [];\n\n    // fix config.\n    this._config.buttons = this._config.buttons.map((row) => {\n      return row.map((config) => {\n        config = handleButtonPreset(config, this._config);\n\n        // create list of entities to monitor for changes.\n        if (config.entity) {\n          this._entities?.push(config.entity);\n        }\n\n        // subscribe template options\n        for (const key of TEMPLATE_OPTIONS) {\n          subscribeTemplate.call(this, config, config, key);\n        }\n\n        // subscribe template styles\n        for (const styles of Object.values(config.styles)) {\n          if (typeof styles === \"object\")\n            for (const key of Object.keys(styles)) {\n              subscribeTemplate.call(this, config, styles, key);\n            }\n        }\n\n        return config;\n      });\n    });\n  }\n\n  render() {\n    if (!this._config || !this.hass) {\n      return html``;\n    }\n\n    renderTemplateObjects(this._templates, this.hass);\n\n    return html`\n      ${\n        this._config.extra_styles\n          ? html`\n            <style>\n              ${this._config.extra_styles}\n            </style>\n          `\n          : \"\"\n      }\n      ${this._config.buttons.map((row) => {\n        return html`\n          <div\n            class=\"flex-box\"\n            style=\"${styleMap(this._config?.styles as StyleInfo)}\"\n          >\n            ${row.map((config) => {\n              const stateObj =\n                (config.entity !== undefined &&\n                  this.hass?.states[config.entity]) ||\n                undefined;\n              const domain = config.entity && computeDomain(config.entity);\n              const styles = this._getStyles(config);\n              const buttonStyles = {\n                ...this._getBaseStyles(),\n                ...this._getStateStyles(domain, stateObj),\n                ...(styles.button || {}),\n              } as StyleInfo;\n\n              const activeStates = config.active\n                ? new Set(config.active)\n                : STATES_ON;\n\n              return html`\n                <paper-button\n                  @action=\"${(ev: ActionHandlerEvent) =>\n                    this._handleAction(ev, config)}\"\n                  .actionHandler=\"${actionHandler({\n                    hasHold: hasAction(config.hold_action),\n                    hasDoubleClick: hasAction(config.double_tap_action),\n                    repeat: config.hold_action?.repeat,\n                    stopPropagation: !!this._config?.is_extended_row,\n                  })}\"\n                  style=\"${styleMap(buttonStyles)}\"\n                  class=\"${this._getClass(\n                    activeStates,\n                    config.state,\n                    stateObj?.state,\n                  )}\"\n                  title=\"${computeTooltip(config, this.hass)}\"\n                  data-domain=\"${ifDefined(domain)}\"\n                  data-entity-state=\"${ifDefined(stateObj?.state)}\"\n                  data-state=\"${ifDefined(\n                    typeof config.state === \"string\" &&\n                      config.state.toLowerCase(),\n                  )}\"\n                >\n                  ${config.layout?.map((column) => {\n                    if (Array.isArray(column))\n                      return html`\n                        <div class=\"flex-column\">\n                          ${column.map((row) =>\n                            this.renderElement(row, config, styles, stateObj),\n                          )}\n                        </div>\n                      `;\n                    return this.renderElement(column, config, styles, stateObj);\n                  })}\n\n                  <paper-ripple\n                    center\n                    style=\"${styleMap((styles.ripple || {}) as StyleInfo)}\"\n                    class=\"${this._getRippleClass(config)}\"\n                  ></paper-ripple>\n                </paper-button>\n              `;\n            })}\n          </div>\n        `;\n      })}\n    `;\n  }\n\n  renderElement(\n    item: string,\n    config: ButtonConfig,\n    styles: StyleConfig,\n    entity?: HassEntity,\n  ) {\n    const style: StyleInfo = styles?.[item] || {};\n    switch (item) {\n      case \"icon\":\n        return this.renderIcon(config, style, entity);\n      case \"name\":\n        return this.renderName(config, style, entity);\n      case \"state\":\n        return this.renderState(config, style);\n    }\n  }\n\n  renderIcon(config: ButtonConfig, style: StyleInfo, entity?: HassEntity) {\n    const icon =\n      config.icon !== false && (config.icon || config.entity)\n        ? computeStateIcon(config) || config.icon\n        : false;\n\n    return config.image\n      ? html`<img\n          src=\"${config.image}\"\n          class=\"image\"\n          style=\"${styleMap(style)}\"\n          alt=\"icon\"\n        />`\n      : icon || entity\n        ? html`\n          <ha-state-icon\n          style=\"${styleMap(style)}\"\n          .hass=${this.hass}\n          .stateObj=${entity}\n          .state=${entity}\n          .icon=\"${icon}\"\n        />`\n        : \"\";\n  }\n\n  renderName(config: ButtonConfig, style: StyleInfo, stateObj?: HassEntity) {\n    return config.name !== false && (config.name || config.entity)\n      ? html`\n        <span style=\"${styleMap(style)}\">\n            ${config.name || computeStateName(stateObj)}\n          </span>\n      `\n      : \"\";\n  }\n\n  renderState(config: ButtonConfig, style: StyleInfo) {\n    return config.state !== false\n      ? html`\n        <span style=\"${styleMap(style)}\"> ${computeStateText(config)} </span>\n      `\n      : \"\";\n  }\n\n  private _handleAction(ev: ActionHandlerEvent, config: ButtonConfig): void {\n    if (this.hass && config && ev.detail.action) {\n      if (this._config?.is_extended_row) {\n        ev.stopPropagation();\n      }\n      handleAction(this, this.hass, config, ev.detail.action);\n    }\n  }\n\n  _getClass(\n    activeStates: Set<string>,\n    state?: ButtonConfig[\"state\"],\n    entityState?: string,\n  ) {\n    if (typeof state === \"string\" && activeStates.has(state.toLowerCase())) {\n      return \"button-active\";\n    }\n    if (STATE_UNAVAILABLE === entityState) {\n      return \"button-unavailable\";\n    }\n    return \"\";\n  }\n\n  _getBaseStyles(): StyleInfo {\n    const hex = getComputedStyle(this).getPropertyValue(\"--state-icon-color\");\n    return {\n      \"--rgb-state-default-color\": this._hexToRgb(hex)?.join(\", \"),\n    };\n  }\n\n  _getStateStyles(domain?: string, stateObj?: HassEntity): StyleInfo {\n    if (!domain || !stateObj) return {};\n\n    if (stateObj.attributes.rgb_color) {\n      return {\n        \"--pbs-button-rgb-state-color\": stateObj.attributes.rgb_color,\n      };\n    }\n\n    const rgb = this._getStateColor(stateObj, domain);\n    if (rgb) {\n      return {\n        \"--pbs-button-rgb-state-color\": rgb.join(\", \"),\n      };\n    }\n\n    return {};\n  }\n\n  _getStateColor = (stateObj: HassEntity, domain?: string) => {\n    const styles = getComputedStyle(this);\n\n    // from `device_class`\n    if (stateObj.attributes.device_class) {\n      const hex = styles.getPropertyValue(\n        `--state-${domain}-${stateObj.attributes.device_class}-${stateObj.state}-color`,\n      );\n      if (hex) {\n        return this._hexToRgb(hex);\n      }\n    }\n\n    // from `state`\n    let hex = styles.getPropertyValue(\n      `--state-${domain}-${stateObj.state}-color`,\n    );\n    if (hex) return this._hexToRgb(hex);\n\n    // from `domain`\n    if (stateObj.state === STATE_ON || stateObj.state === STATE_OFF) {\n      const active = stateObj.state === STATE_ON ? \"active\" : \"inactive\";\n\n      hex = styles.getPropertyValue(`--state-${domain}-${active}-color`);\n      if (hex) return this._hexToRgb(hex);\n\n      if (stateObj.state === STATE_ON) {\n        hex = styles.getPropertyValue(\"--state-active-color\");\n        if (hex) return this._hexToRgb(hex);\n      }\n    }\n  };\n\n  _hexToRgb(hex: string) {\n    return hex.match(/[A-Za-z0-9]{2}/g)?.map((v) => Number.parseInt(v, 16));\n  }\n\n  _getRippleClass(config: ButtonConfig) {\n    switch (config.ripple) {\n      case \"none\":\n        return \"hidden\";\n      case \"circle\":\n        return \"circle\";\n      case \"fill\":\n        return \"\";\n    }\n    if (config.layout?.length === 1 && config.layout[0] === \"icon\") {\n      return \"circle\";\n    }\n    if (\n      config.name ||\n      (config.name !== false && config.entity) ||\n      config.layout?.includes(\"state\")\n    ) {\n      return \"\";\n    }\n    return \"circle\";\n  }\n\n  _getStyles(config: ButtonConfig): StyleConfig {\n    if (typeof config.state !== \"string\" || !config.state_styles) {\n      return config.styles;\n    }\n    const stateStyle = config.state_styles[config.state.toLowerCase()];\n    if (!stateStyle) {\n      return config.styles;\n    }\n    return deepmerge(config.styles, stateStyle);\n  }\n\n  _defaultConfig(\n    config: ExternalPaperButtonRowConfig,\n    bConfig: ExternalButtonConfig,\n  ) {\n    if (!bConfig.layout) {\n      // migrate align_icon to layout\n      const alignment = bConfig.align_icon || config.align_icons;\n      if (alignment) bConfig.layout = migrateIconAlignment(alignment);\n      else bConfig.layout = [\"icon\", \"name\"];\n    }\n\n    // default state template\n    if (!bConfig.state && bConfig.entity) {\n      bConfig.state = { case: \"upper\" };\n    }\n\n    if (bConfig.entity) {\n      const domain = computeDomain(bConfig.entity);\n      // default hold action\n      if (!bConfig.hold_action) {\n        bConfig.hold_action = { action: \"more-info\" };\n      }\n      // default tap action\n      if (!bConfig.tap_action) {\n        if (DOMAINS_TOGGLE.has(domain)) {\n          bConfig.tap_action = { action: \"toggle\" };\n        } else if (domain === \"scene\") {\n          bConfig.tap_action = {\n            action: \"call-service\",\n            service: \"scene.turn_on\",\n            service_data: {\n              entity_id: bConfig.entity,\n            },\n          };\n        } else {\n          bConfig.tap_action = { action: \"more-info\" };\n        }\n      }\n    }\n    return bConfig;\n  }\n\n  shouldUpdate(changedProps: PropertyValues) {\n    if (changedProps.has(\"_config\")) {\n      return true;\n    }\n    if (this._entities) {\n      const oldHass = changedProps.get(\"hass\") as HomeAssistant | undefined;\n      if (!oldHass) {\n        return true;\n      }\n      // only update if monitored entity changed state.\n      return this._entities.some(\n        (entity) => oldHass.states[entity] !== this.hass?.states[entity],\n      );\n    }\n    return false;\n  }\n}\n"
  },
  {
    "path": "src/presets.ts",
    "content": "import deepmerge from \"deepmerge\";\nimport { getLovelace } from \"./get-lovelace\";\nimport type { ButtonConfig, PaperButtonRowConfig } from \"./types\";\n\ndeclare module \"custom-card-helpers\" {\n  interface LovelaceConfig {\n    paper_buttons_row?: {\n      presets?: {\n        [key: string]: ButtonConfig;\n      };\n    };\n  }\n}\n\nlet lovelace = getLovelace();\n\nexport function handleButtonPreset(\n  bConfig: ButtonConfig,\n  config?: PaperButtonRowConfig,\n): ButtonConfig {\n  if (!lovelace) lovelace = getLovelace();\n  const userPresets = lovelace?.config?.paper_buttons_row?.presets || {};\n  const preset = bConfig.preset || config?.preset;\n  return preset\n    ? deepmerge(\n        {\n          mushroom: presetMushroom,\n        }[preset] ||\n          userPresets[preset] ||\n          {},\n        bConfig,\n      )\n    : bConfig;\n}\n\nconst presetMushroom: ButtonConfig = {\n  ripple: \"none\",\n  styles: {\n    button: {\n      \"min-width\": \"42px\",\n      \"min-height\": \"42px\",\n      \"border-radius\": \"12px\",\n      \"box-sizing\": \"border-box\",\n      transition: \"background-color 280ms ease-in-out 0s\",\n      \"--pbs-button-rgb-color\": \"var(--rgb-primary-text-color)\",\n      \"--pbs-button-rgb-default-color\": \"var(--rgb-primary-text-color)\",\n      \"--pbs-button-rgb-active-color\": \"var(--pbs-button-rgb-state-color)\",\n      \"--pbs-button-rgb-bg-color\": \"var(--pbs-button-rgb-color)\",\n      \"--pbs-button-rgb-bg-active-color\": \"var(--pbs-button-rgb-active-color)\",\n      \"--pbs-button-rgb-bg-opacity\": \"0.05\",\n      \"--pbs-button-rgb-bg-active-opacity\": \"0.2\",\n    },\n  },\n};\n"
  },
  {
    "path": "src/styles.css",
    "content": ".flex-box {\n  display: flex;\n  justify-content: space-evenly;\n  align-items: center;\n}\n\n.flex-column {\n  display: inline-flex;\n  flex-direction: column;\n  align-items: center;\n}\n\n.hidden {\n  display: none;\n}\n\npaper-button {\n  --pbs-button-rgb-fallback: 68, 115, 158;\n\n  color: var(\n    --pbs-button-color,\n    rgb(\n      var(\n        --pbs-button-rgb-color,\n        var(\n          --pbs-button-rgb-state-color,\n          var(\n            --pbs-button-rgb-default-color,\n            var(--rgb-state-default-color, var(--pbs-button-rgb-fallback))\n          )\n        )\n      )\n    )\n  );\n  background-color: var(\n    --pbs-button-bg-color,\n    rgba(var(--pbs-button-rgb-bg-color), var(--pbs-button-rgb-bg-opacity, 1))\n  );\n\n  padding: 6px;\n  cursor: pointer;\n  position: relative;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  user-select: none;\n}\n\nspan {\n  padding: 2px;\n  text-align: center;\n}\n\nha-icon {\n  padding: 2px;\n}\n\n.button-active {\n  color: var(\n    --paper-item-icon-active-color,\n    var(\n      --pbs-button-active-color,\n      rgb(\n        var(\n          --pbs-button-rgb-active-color,\n          var(\n            --pbs-button-rgb-state-color,\n            var(\n              --pbs-button-rgb-default-color,\n              var(--rgb-state-default-color, var(--pbs-button-rgb-fallback))\n            )\n          )\n        )\n      )\n    )\n  );\n  background-color: var(\n    --pbs-button-bg-active-color,\n    rgba(\n      var(--pbs-button-rgb-bg-active-color, var(--pbs-button-rgb-bg-color)),\n      var(\n        --pbs-button-rgb-bg-active-opacity,\n        var(--pbs-button-rgb-bg-opacity, 1)\n      )\n    )\n  );\n}\n\n.button-unavailable {\n  color: var(\n    --pbs-button-unavailable-color,\n    rgb(var(--pbs-button-rgb-unavailable-color, var(--rgb-disabled-color)))\n  );\n}\n\n.image {\n  position: relative;\n  display: inline-block;\n  width: 28px;\n  border-radius: 50%;\n  height: 28px;\n  text-align: center;\n  background-size: cover;\n  line-height: 28px;\n  vertical-align: middle;\n  box-sizing: border-box;\n}\n\n@keyframes blink {\n  0% {\n    opacity: 0;\n  }\n  50% {\n    opacity: 1;\n  }\n  100% {\n    opacity: 0;\n  }\n}\n\n/* Safari and Chrome */\n@-webkit-keyframes rotating {\n  from {\n    -webkit-transform: rotate(0deg);\n    -o-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n  to {\n    -webkit-transform: rotate(360deg);\n    -o-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n\n@keyframes rotating {\n  from {\n    -ms-transform: rotate(0deg);\n    -moz-transform: rotate(0deg);\n    -webkit-transform: rotate(0deg);\n    -o-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n  to {\n    -ms-transform: rotate(360deg);\n    -moz-transform: rotate(360deg);\n    -webkit-transform: rotate(360deg);\n    -o-transform: rotate(360deg);\n    transform: rotate(360deg);\n  }\n}\n\n[rotating] {\n  -webkit-animation: rotating 2s linear infinite;\n  -moz-animation: rotating 2s linear infinite;\n  -ms-animation: rotating 2s linear infinite;\n  -o-animation: rotating 2s linear infinite;\n  animation: rotating 2s linear infinite;\n}\n"
  },
  {
    "path": "src/template.ts",
    "content": "import { hasTemplate, subscribeRenderTemplate } from \"card-tools/src/templates\";\nimport type { PaperButtonsRow } from \"./main\";\n\nexport function renderTemplateObjects(templates, hass) {\n  for (const item of templates) {\n    item.callback(renderTemplateObject(item.template, hass));\n  }\n}\n\nexport function renderTemplateObject(template, hass) {\n  let state = hass.states[template.entity];\n\n  if (!state) return;\n\n  if (template.attribute) {\n    state = state.attributes[template.attribute];\n  } else {\n    state = state.state;\n  }\n\n  let result = (template.prefix || \"\") + state + (template.postfix || \"\");\n\n  if (template.case) {\n    result = handleCase(result, template.case);\n  }\n\n  return result;\n}\n\nfunction handleCase(text, text_case) {\n  switch (text_case) {\n    case \"upper\":\n      return text.toUpperCase();\n    case \"lower\":\n      return text.toLowerCase();\n    case \"first\":\n      return text[0].toUpperCase() + text.slice(1);\n  }\n}\n\nexport function subscribeTemplate(this: PaperButtonsRow, config, object, key) {\n  const option = object[key];\n\n  if (typeof option === \"object\") {\n    if (!option.entity) option.entity = config.entity;\n\n    if (option.entity !== config.entity) this._entities?.push(option.entity);\n\n    this._templates?.push({\n      template: option,\n      callback: (res) => {\n        if (res) {\n          object[key] = res;\n        }\n      },\n    });\n  } else if (hasTemplate(option)) {\n    subscribeRenderTemplate(\n      null,\n      (res) => {\n        object[key] = res;\n        this.requestUpdate();\n      },\n      {\n        template: option,\n        variables: { config: config },\n      },\n    );\n    object[key] = \"\";\n  }\n}\n"
  },
  {
    "path": "src/types.ts",
    "content": "import type { ActionConfig, LovelaceCard } from \"custom-card-helpers\";\nimport type { HapticType } from \"custom-card-helpers/src/haptic\";\nimport type { BaseActionConfig } from \"custom-card-helpers/src/types\";\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    \"hui-error-card\": LovelaceCard;\n  }\n\n  const __NAME__: string;\n  const __BRANCH__: string;\n  const __COMMIT__: string;\n  const __VERSION__: string;\n  const __BUILD_TIME__: string;\n}\n\nexport interface TemplateConfig {\n  entity?: string;\n  attribute?: string;\n  prefix?: string;\n  postfix?: string;\n  case?: \"upper\" | \"lower\" | \"first\";\n}\n\nexport type Template = string | TemplateConfig;\n\nexport type StyleConfig = Partial<\n  Record<\n    \"button\" | \"icon\" | \"name\" | \"state\" | \"ripple\",\n    Record<string, string | Template>\n  >\n>;\n\nexport interface ButtonConfig {\n  entity?: string;\n  name?: string | false | Template;\n  state?: false | string | Template;\n  icon?: false | string | Template;\n  image?: string | Template;\n  preset?: string;\n  active?: string[];\n  ripple?: \"fill\" | \"none\" | \"circle\";\n  layout?: Array<string | Array<string>>;\n  align_icon?: \"top\" | \"left\" | \"right\" | \"bottom\"; // deprecated\n  tooltip?: string | false;\n  tap_action?: ButtonActionConfig;\n  hold_action?: ButtonActionConfig;\n  double_tap_action?: ButtonActionConfig;\n  styles: StyleConfig;\n  state_styles?: Record<string, StyleConfig>;\n  state_icons?: Record<string, string>;\n  state_text?: Record<string, string>;\n}\n\nexport interface PaperButtonRowConfig {\n  type?: string;\n  preset?: string;\n  buttons: ButtonConfig[][];\n  align_icons?: \"top\" | \"left\" | \"right\" | \"bottom\";\n  base_config?: ButtonConfig;\n  styles: Record<string, string | Template>;\n  extra_styles?: string;\n  position?: \"center\" | \"right\";\n  hide_badge?: boolean;\n  hide_state?: boolean;\n  is_extended_row?: boolean;\n}\n\nexport interface ExternalButtonConfig\n  extends Omit<ButtonConfig, \"layout\" | \"active\" | \"style\" | \"styles\"> {\n  layout?: string | Array<string | Array<string>>;\n  active?: string | string[];\n  style?: StyleConfig;\n  styles?: StyleConfig;\n}\n\nexport type ExternalButtonType = string | ExternalButtonConfig;\n\nexport interface ExternalPaperButtonRowConfig\n  extends Omit<PaperButtonRowConfig, \"buttons\" | \"styles\"> {\n  buttons: Array<ExternalButtonType | Array<ExternalButtonType>>;\n  styles?: Record<string, string | Template>;\n  is_extended_row?: boolean;\n}\n\nexport interface FireEventActionConfig extends BaseActionConfig {\n  action: \"fire-event\";\n  event_type: string;\n  event_data?: Record<string, unknown>;\n  repeat?: number;\n  haptic?: HapticType;\n}\n\nexport type ButtonActionConfig = ActionConfig | FireEventActionConfig;\n"
  },
  {
    "path": "src/utils.ts",
    "content": "import { fireEvent } from \"custom-card-helpers\";\n\ndeclare global {\n  interface HASSDomEvents {\n    \"hass-notification\": ShowToastParams;\n  }\n}\n\nexport interface ShowToastParams {\n  message: string;\n  action?: ToastActionParams;\n  duration?: number;\n  dismissable?: boolean;\n}\n\nexport interface ToastActionParams {\n  action: () => void;\n  text: string;\n}\n\nexport const showToast = (el: HTMLElement, params: ShowToastParams) => {\n  return fireEvent(el, \"hass-notification\", params);\n};\n\nexport const arrayToObject = (data) =>\n  Array.isArray(data)\n    ? // biome-ignore lint/performance/noAccumulatingSpread: rework this\n      data.reduce((obj, item) => Object.assign(obj, item), {})\n    : data;\n"
  },
  {
    "path": "src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"ES2020\",\n    \"moduleResolution\": \"Node\",\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"strict\": true,\n    \"noEmit\": true,\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"experimentalDecorators\": true,\n    \"emitDecoratorMetadata\": true,\n    \"noImplicitAny\": false,\n    \"resolveJsonModule\": true\n  },\n  \"include\": [\"src\", \"vite.config.ts\"]\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "import { exec } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { defineConfig, type UserConfig } from \"vite\";\nimport viteCompression from \"vite-plugin-compression\";\nimport pkg from \"./package.json\";\n\nconst $ = async (command: string, env = \"\") =>\n  process.env[env] ?? (await promisify(exec)(command)).stdout.trim();\n\nconst all = async (obj: Record<string, string | Promise<string>>) =>\n  Object.fromEntries(\n    await Promise.all(\n      Object.entries(obj).map(async ([k, v]) => [k, JSON.stringify(await v)]),\n    ),\n  );\n\nexport default defineConfig(\n  async (): Promise<UserConfig> => ({\n    build: {\n      target: \"es6\",\n      lib: {\n        entry: \"src/main.ts\",\n        formats: [\"es\"],\n      },\n    },\n    esbuild: {\n      legalComments: \"none\",\n    },\n    plugins: [viteCompression({ verbose: false })],\n    define: await all({\n      __NAME__: pkg.name.toUpperCase(),\n      __BRANCH__: $(\"git rev-parse --abbrev-ref HEAD\", \"GITHUB_REF_NAME\"),\n      __VERSION__: $(\"git describe --tags --dirty --always\", \"VERSION\"),\n      __COMMIT__: $(\"git rev-parse HEAD\", \"GITHUB_SHA\"),\n      __BUILD_TIME__: new Date().toISOString(),\n    }),\n  }),\n);\n"
  }
]