From bb5a5099eb188ad117f7d45521ad714faf2eaa19 Mon Sep 17 00:00:00 2001 From: nearxos Date: Tue, 6 Jan 2026 23:47:01 +0200 Subject: [PATCH] Clean up: Remove build files, update README with current features - Removed TypeScript source and build configuration files - Updated README with all current features (icon picker, icon size, show/hide options) - Updated example configuration - Card is now standalone JavaScript file ready to use --- README.md | 96 +++++--- example-configuration.yaml | 13 +- light-button-card.js | 458 +++++++++++++++++++++++++++++++++++++ package.json | 31 --- src/light-button-card.ts | 356 ---------------------------- tsconfig.json | 18 -- webpack.config.js | 32 --- 7 files changed, 526 insertions(+), 478 deletions(-) create mode 100644 light-button-card.js delete mode 100644 package.json delete mode 100644 src/light-button-card.ts delete mode 100644 tsconfig.json delete mode 100644 webpack.config.js diff --git a/README.md b/README.md index 423ba58..5850b2e 100644 --- a/README.md +++ b/README.md @@ -6,22 +6,24 @@ A custom Lovelace card for Home Assistant that combines a button entity (for tog - **Button Toggle**: Click the card to send a toggle command to your button entity - **Status Display**: Shows the current state from your binary sensor entity -- **Custom Icons**: Support for custom icons with separate icons for on/off states -- **Visual Feedback**: Status indicator dot and icon color changes based on state -- **Card Editor**: Full UI editor support in Lovelace +- **Custom Icons**: Icon picker with Material Design Icons support +- **Icon Size Control**: Adjustable icon size (16px to 500px) +- **Visual Feedback**: Icon color changes based on state (yellow when on, gray when off) +- **Show/Hide Options**: Toggle visibility of name and status text +- **Card Editor**: Full visual editor support in Lovelace with filtered entity pickers ## Installation ### Manual Installation -1. Copy `dist/light-button-card.js` to your `www/community/light-button-card/` directory in Home Assistant +1. Copy `light-button-card.js` to your `www/light-button-card/` directory in Home Assistant 2. Add the resource to your Lovelace configuration: - -```yaml -resources: - - url: /local/light-button-card/light-button-card.js - type: module -``` + - Go to **Settings** → **Dashboards** → **Resources** + - Click **"+ ADD RESOURCE"** + - Enter the URL: `/local/light-button-card/light-button-card.js` + - Set Type to: **JavaScript Module** + - Click **CREATE** +3. Refresh your browser (hard refresh: Ctrl+Shift+R) ### HACS Installation (if published) @@ -45,15 +47,26 @@ binary_sensor_entity: binary_sensor.plc_light_1_status ```yaml type: custom:light-button-card -button_entity: button.plc_light_1 # Required: Button entity to toggle +button_entity: button.plc_light_1 # Required: Button entity to toggle binary_sensor_entity: binary_sensor.plc_light_1_status # Required: Binary sensor for status -name: Living Room Light # Optional: Display name -icon: mdi:lightbulb # Optional: Default icon (MDI format) -icon_on: mdi:lightbulb # Optional: Icon when state is "on" -icon_off: mdi:lightbulb-outline # Optional: Icon when state is "off" -show_name: true # Optional: Show/hide name (default: true) +name: Living Room Light # Optional: Display name +icon: mdi:lightbulb # Optional: Icon (MDI format) +icon_size: 64 # Optional: Icon size in pixels (16-500, default: 64) +show_name: true # Optional: Show/hide name (default: true) +show_status: true # Optional: Show/hide status text (default: true) ``` +## Visual Editor + +The card includes a full visual editor with: +- **Button Entity Picker**: Filtered to show only button entities +- **Binary Sensor Entity Picker**: Filtered to show only binary sensor entities +- **Icon Picker**: Dropdown with Material Design Icons +- **Icon Size Field**: Number input for icon size (16-500px) +- **Name Field**: Text input for card name +- **Show Name Switch**: Toggle name visibility +- **Show Status Switch**: Toggle status text visibility + ## Icon Configuration Icons use Material Design Icons (MDI) format. You can use any icon from [Material Design Icons](https://materialdesignicons.com/). @@ -64,6 +77,7 @@ Examples: - `mdi:lamp` - Lamp - `mdi:ceiling-light` - Ceiling light - `mdi:wall-sconce` - Wall sconce +- `mdi:light-recessed` - Recessed light ## Usage Example @@ -72,9 +86,10 @@ type: custom:light-button-card button_entity: button.plc_kitchen_light binary_sensor_entity: binary_sensor.plc_kitchen_light_status name: Kitchen Light -icon_on: mdi:lightbulb -icon_off: mdi:lightbulb-outline +icon: mdi:lightbulb +icon_size: 90 show_name: true +show_status: true ``` ## How It Works @@ -84,25 +99,19 @@ show_name: true 2. **Binary Sensor Entity**: The card continuously monitors the binary sensor entity to display the current state (on/off) of the PLC output. 3. **Visual Feedback**: - - The icon changes color and style based on the state - - A status indicator dot shows green when on, gray when off - - The state text displays the current state + - The icon changes color based on the state (yellow when on, gray when off) + - Icon size is customizable from 16px to 500px + - Name and status text can be shown or hidden + - Hover effects provide visual feedback -## Development +## File Structure -### Building - -```bash -npm install -npm run build ``` - -The compiled file will be in `dist/light-button-card.js`. - -### Development Mode - -```bash -npm run watch +HomeAssistant-Light-Buttons/ +├── light-button-card.js # Main card file (ready to use) +├── example-configuration.yaml # Example YAML configuration +├── hacs.json # HACS metadata +└── README.md # This file ``` ## Requirements @@ -111,7 +120,24 @@ npm run watch - Button entity (for toggling) - Binary sensor entity (for status) +## Troubleshooting + +### Card not appearing +1. Clear browser cache (Ctrl+Shift+R) +2. Reload Lovelace resources (Settings → Dashboards → Resources → Reload resources) +3. Verify the resource URL is `/local/light-button-card/light-button-card.js` +4. Check browser console (F12) for errors + +### Icon size not changing +1. Hard refresh browser (Ctrl+Shift+R) +2. Enter the size value directly in the field +3. Check that the value is between 16 and 500 + +### Can only type one character in editor +1. Clear browser cache completely +2. Hard refresh (Ctrl+Shift+R) +3. The editor should maintain focus while typing + ## License MIT - diff --git a/example-configuration.yaml b/example-configuration.yaml index 29e2d69..0aa9ac4 100644 --- a/example-configuration.yaml +++ b/example-configuration.yaml @@ -5,16 +5,17 @@ type: custom:light-button-card button_entity: button.plc_light_1 binary_sensor_entity: binary_sensor.plc_light_1_status name: Living Room Light -icon_on: mdi:lightbulb -icon_off: mdi:lightbulb-outline +icon: mdi:lightbulb +icon_size: 64 show_name: true +show_status: true -# Another example with different icon +# Another example with different settings # type: custom:light-button-card # button_entity: button.plc_light_2 # binary_sensor_entity: binary_sensor.plc_light_2_status # name: Kitchen Light -# icon_on: mdi:ceiling-light -# icon_off: mdi:ceiling-light-outline +# icon: mdi:ceiling-light +# icon_size: 90 # show_name: true - +# show_status: false diff --git a/light-button-card.js b/light-button-card.js new file mode 100644 index 0000000..a556a76 --- /dev/null +++ b/light-button-card.js @@ -0,0 +1,458 @@ +// Light Button Card for Home Assistant +// Based on: https://developers.home-assistant.io/docs/frontend/custom-ui/custom-card/ + +class LightButtonCard extends HTMLElement { + set hass(hass) { + this._hass = hass; + this._updateContent(); + } + + setConfig(config) { + if (!config.button_entity || !config.binary_sensor_entity) { + throw new Error('button_entity and binary_sensor_entity are required'); + } + this._config = { + icon: 'mdi:lightbulb', + name: '', + show_name: true, + show_status: true, + icon_size: 64, + ...config, + }; + this._updateContent(); + } + + _updateContent() { + if (!this._config || !this._hass) { + return; + } + + if (!this.content) { + this.innerHTML = ` + +
+
+ +
+
+
+
+
+ `; + this.content = this.querySelector('.button-container'); + this.icon = this.querySelector('ha-icon'); + this.nameDiv = this.querySelector('.name'); + this.stateText = this.querySelector('.state-text'); + + // Add click handler + this.querySelector('ha-card').addEventListener('click', () => this._handleClick()); + + // Add styles + if (!this.styleSheet) { + const style = document.createElement('style'); + style.textContent = ` + ha-card { + padding: 16px; + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; + transition: transform 0.2s, box-shadow 0.2s; + } + ha-card:hover { + transform: scale(1.02); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + } + ha-card:active { + transform: scale(0.98); + } + .button-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + } + .icon-container { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: auto; + } + ha-icon { + color: var(--primary-color); + transition: color 0.3s, transform 0.2s; + display: inline-flex !important; + align-items: center; + justify-content: center; + --mdc-icon-size: var(--icon-size, 64px) !important; + } + ha-icon svg, + ha-icon ha-svg-icon, + ha-icon ha-svg-icon svg { + width: 100% !important; + height: 100% !important; + max-width: none !important; + max-height: none !important; + } + .icon-container.on ha-icon { + color: var(--paper-item-icon-active-color, #fdd835); + filter: drop-shadow(0 0 8px rgba(253, 216, 53, 0.6)); + } + .icon-container.off ha-icon { + color: var(--disabled-text-color, #9e9e9e); + } + .name { + font-size: 14px; + font-weight: 500; + color: var(--primary-text-color); + text-align: center; + } + .state-text { + font-size: 12px; + color: var(--secondary-text-color); + text-transform: uppercase; + letter-spacing: 0.5px; + } + `; + this.appendChild(style); + this.styleSheet = style; + } + } + + const state = this._getState(); + const isOn = this._isOn(); + const icon = this._getIcon(); + const iconSize = this._config.icon_size || 64; + + // Update icon + this.icon.setAttribute('icon', icon); + this.icon.style.setProperty('--icon-size', `${iconSize}px`); + this.icon.style.width = `${iconSize}px`; + this.icon.style.height = `${iconSize}px`; + this.icon.style.minWidth = `${iconSize}px`; + this.icon.style.minHeight = `${iconSize}px`; + this.icon.style.maxWidth = `${iconSize}px`; + this.icon.style.maxHeight = `${iconSize}px`; + this.icon.style.setProperty('--mdc-icon-size', `${iconSize}px`); + + // Ensure internal SVG scales - check multiple possible structures + const svgIcon = this.icon.querySelector('ha-svg-icon'); + if (svgIcon) { + svgIcon.style.width = `${iconSize}px`; + svgIcon.style.height = `${iconSize}px`; + svgIcon.style.setProperty('--mdc-icon-size', `${iconSize}px`); + const svg = svgIcon.querySelector('svg'); + if (svg) { + svg.style.width = `${iconSize}px`; + svg.style.height = `${iconSize}px`; + svg.setAttribute('width', `${iconSize}`); + svg.setAttribute('height', `${iconSize}`); + } + } + const svg = this.icon.querySelector('svg'); + if (svg) { + svg.style.width = `${iconSize}px`; + svg.style.height = `${iconSize}px`; + svg.setAttribute('width', `${iconSize}`); + svg.setAttribute('height', `${iconSize}`); + } + + // Update icon container class + const iconContainer = this.querySelector('.icon-container'); + if (isOn) { + iconContainer.classList.add('on'); + iconContainer.classList.remove('off'); + } else { + iconContainer.classList.add('off'); + iconContainer.classList.remove('on'); + } + + // Update name + if (this._config.show_name && this._config.name) { + this.nameDiv.textContent = this._config.name; + this.nameDiv.style.display = 'block'; + } else { + this.nameDiv.style.display = 'none'; + } + + // Update state text + if (this._config.show_status) { + this.stateText.textContent = state; + this.stateText.style.display = 'block'; + } else { + this.stateText.style.display = 'none'; + } + } + + _getState() { + if (!this._hass || !this._config.binary_sensor_entity) { + return 'unknown'; + } + const stateObj = this._hass.states[this._config.binary_sensor_entity]; + if (!stateObj) { + return 'unknown'; + } + return stateObj.state; + } + + _isOn() { + const state = this._getState(); + return state === 'on' || state === 'true' || state === '1'; + } + + _getIcon() { + return this._config.icon || 'mdi:lightbulb'; + } + + async _handleClick() { + if (!this._hass || !this._config.button_entity) { + return; + } + + const buttonEntity = this._hass.states[this._config.button_entity]; + if (!buttonEntity) { + console.error(`Button entity ${this._config.button_entity} not found`); + return; + } + + // Call the button press service + await this._hass.callService('button', 'press', { + entity_id: this._config.button_entity, + }); + } + + getCardSize() { + return 3; + } + + static getStubConfig() { + return { + button_entity: 'button.example', + binary_sensor_entity: 'binary_sensor.example', + icon: 'mdi:lightbulb', + name: 'Light', + show_name: true, + show_status: true, + icon_size: 64, + }; + } +} + +customElements.define('light-button-card', LightButtonCard); + +// Card editor +class LightButtonCardEditor extends HTMLElement { + set hass(hass) { + this._hass = hass; + if (this.content && this._elements) { + // Update hass on existing elements + this._elements.buttonPicker.hass = hass; + this._elements.sensorPicker.hass = hass; + } else if (this.content) { + this._updateEditor(); + } + } + + setConfig(config) { + this._config = { ...config }; + if (!this.content) { + this._updateEditor(); + } else { + // Update values without rebuilding + this._updateEditorValues(); + } + } + + _updateEditorValues() { + if (!this._elements) return; + + if (this._elements.buttonPicker) { + this._elements.buttonPicker.value = this._config.button_entity || ''; + } + if (this._elements.sensorPicker) { + this._elements.sensorPicker.value = this._config.binary_sensor_entity || ''; + } + if (this._elements.iconPicker) { + this._elements.iconPicker.value = this._config.icon || 'mdi:lightbulb'; + } + if (this._elements.iconSizeField) { + this._elements.iconSizeField.value = this._config.icon_size || 64; + } + if (this._elements.nameField) { + this._elements.nameField.value = this._config.name || ''; + } + if (this._elements.showNameSwitch) { + this._elements.showNameSwitch.checked = this._config.show_name !== false; + } + if (this._elements.showStatusSwitch) { + this._elements.showStatusSwitch.checked = this._config.show_status !== false; + } + } + + _updateEditor() { + if (!this._hass) { + return; + } + + if (!this.content) { + this.innerHTML = ` +
+ `; + this.content = this.querySelector('.card-config'); + + // Add styles + const style = document.createElement('style'); + style.textContent = ` + .card-config { + display: flex; + flex-direction: column; + gap: 16px; + } + `; + this.appendChild(style); + } + + // Clear and rebuild editor only if elements don't exist + if (!this._elements) { + this.content.innerHTML = ''; + this._elements = {}; + + // Button Entity Picker + const buttonPicker = document.createElement('ha-entity-picker'); + buttonPicker.label = 'Button Entity'; + buttonPicker.hass = this._hass; + buttonPicker.value = this._config.button_entity || ''; + buttonPicker.configValue = 'button_entity'; + buttonPicker.required = true; + buttonPicker.includeDomains = ['button']; + buttonPicker.addEventListener('value-changed', (ev) => { + this._config.button_entity = ev.detail.value; + this._fireConfigChanged(); + }); + this.content.appendChild(buttonPicker); + this._elements.buttonPicker = buttonPicker; + + // Binary Sensor Entity Picker + const sensorPicker = document.createElement('ha-entity-picker'); + sensorPicker.label = 'Binary Sensor Entity'; + sensorPicker.hass = this._hass; + sensorPicker.value = this._config.binary_sensor_entity || ''; + sensorPicker.configValue = 'binary_sensor_entity'; + sensorPicker.required = true; + sensorPicker.includeDomains = ['binary_sensor']; + sensorPicker.addEventListener('value-changed', (ev) => { + this._config.binary_sensor_entity = ev.detail.value; + this._fireConfigChanged(); + }); + this.content.appendChild(sensorPicker); + this._elements.sensorPicker = sensorPicker; + + // Icon Picker + const iconPicker = document.createElement('ha-icon-picker'); + iconPicker.label = 'Icon'; + iconPicker.value = this._config.icon || 'mdi:lightbulb'; + iconPicker.configValue = 'icon'; + iconPicker.addEventListener('value-changed', (ev) => { + this._config.icon = ev.detail.value; + this._fireConfigChanged(); + }); + this.content.appendChild(iconPicker); + this._elements.iconPicker = iconPicker; + + // Icon Size field + const iconSizeField = document.createElement('ha-textfield'); + iconSizeField.label = 'Icon Size (pixels)'; + iconSizeField.type = 'number'; + iconSizeField.value = this._config.icon_size || 64; + iconSizeField.configValue = 'icon_size'; + iconSizeField.min = 16; + iconSizeField.max = 500; + iconSizeField.step = 1; + iconSizeField.addEventListener('input', (ev) => { + const value = parseInt(ev.target.value); + if (!isNaN(value) && value >= 16) { + this._config.icon_size = value; + this._fireConfigChanged(); + } + }); + this.content.appendChild(iconSizeField); + this._elements.iconSizeField = iconSizeField; + + // Name field + const nameField = document.createElement('ha-textfield'); + nameField.label = 'Name (optional)'; + nameField.value = this._config.name || ''; + nameField.configValue = 'name'; + nameField.addEventListener('input', (ev) => { + this._config.name = ev.target.value; + this._fireConfigChanged(); + }); + this.content.appendChild(nameField); + this._elements.nameField = nameField; + + // Show Name switch + const showNameContainer = document.createElement('div'); + showNameContainer.style.display = 'flex'; + showNameContainer.style.alignItems = 'center'; + showNameContainer.style.justifyContent = 'space-between'; + const showNameLabel = document.createElement('div'); + showNameLabel.textContent = 'Show Name'; + showNameLabel.style.flex = '1'; + const showNameSwitch = document.createElement('ha-switch'); + showNameSwitch.checked = this._config.show_name !== false; + showNameSwitch.configValue = 'show_name'; + showNameSwitch.addEventListener('change', (ev) => { + this._config.show_name = ev.target.checked; + this._fireConfigChanged(); + }); + showNameContainer.appendChild(showNameLabel); + showNameContainer.appendChild(showNameSwitch); + this.content.appendChild(showNameContainer); + this._elements.showNameSwitch = showNameSwitch; + + // Show Status switch + const showStatusContainer = document.createElement('div'); + showStatusContainer.style.display = 'flex'; + showStatusContainer.style.alignItems = 'center'; + showStatusContainer.style.justifyContent = 'space-between'; + const showStatusLabel = document.createElement('div'); + showStatusLabel.textContent = 'Show Status'; + showStatusLabel.style.flex = '1'; + const showStatusSwitch = document.createElement('ha-switch'); + showStatusSwitch.checked = this._config.show_status !== false; + showStatusSwitch.configValue = 'show_status'; + showStatusSwitch.addEventListener('change', (ev) => { + this._config.show_status = ev.target.checked; + this._fireConfigChanged(); + }); + showStatusContainer.appendChild(showStatusLabel); + showStatusContainer.appendChild(showStatusSwitch); + this.content.appendChild(showStatusContainer); + this._elements.showStatusSwitch = showStatusSwitch; + } + } + + _fireConfigChanged() { + const event = new CustomEvent('config-changed', { + bubbles: true, + composed: true, + detail: { config: { ...this._config } } + }); + this.dispatchEvent(event); + } +} + +customElements.define('light-button-card-editor', LightButtonCardEditor); + +// Register card for card picker +LightButtonCard.getConfigElement = () => { + return document.createElement('light-button-card-editor'); +}; + +window.customCards = window.customCards || []; +window.customCards.push({ + type: 'light-button-card', + name: 'Light Button Card', + description: 'A card with button toggle and binary sensor status', +}); diff --git a/package.json b/package.json deleted file mode 100644 index 78f26e5..0000000 --- a/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "homeassistant-light-buttons", - "version": "1.0.0", - "description": "Custom Lovelace card for Home Assistant with button toggle and binary sensor status", - "main": "dist/light-button-card.js", - "scripts": { - "build": "webpack --mode production", - "watch": "webpack --mode development --watch" - }, - "keywords": [ - "home-assistant", - "lovelace", - "custom-card" - ], - "author": "", - "license": "MIT", - "devDependencies": { - "@types/node": "^20.0.0", - "ts-loader": "^9.5.0", - "typescript": "^5.0.0", - "webpack": "^5.88.0", - "webpack-cli": "^5.1.0" - }, - "dependencies": { - "lit": "^2.8.0" - }, - "peerDependencies": { - "home-assistant-js-websocket": "^8.0.0" - } -} - diff --git a/src/light-button-card.ts b/src/light-button-card.ts deleted file mode 100644 index 87bbfae..0000000 --- a/src/light-button-card.ts +++ /dev/null @@ -1,356 +0,0 @@ -import { LitElement, html, css, TemplateResult } from 'lit'; -import { customElement, property, state } from 'lit/decorators.js'; - -// Type definitions for Home Assistant -interface HomeAssistant { - states: { [key: string]: HassEntity }; - callService(domain: string, service: string, data?: any): Promise; -} - -interface HassEntity { - entity_id: string; - state: string; - attributes: { [key: string]: any }; -} - -interface LovelaceCardEditor { - setConfig(config: any): void; -} - -interface LightButtonCardConfig { - type: string; - button_entity: string; - binary_sensor_entity: string; - icon?: string; - icon_on?: string; - icon_off?: string; - name?: string; - show_name?: boolean; -} - -// Helper function to fire events -function fireEvent(node: HTMLElement, type: string, detail?: any): void { - const event = new CustomEvent(type, { - detail, - bubbles: true, - composed: true, - }); - node.dispatchEvent(event); -} - -@customElement('light-button-card') -export class LightButtonCard extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - @state() private _config!: LightButtonCardConfig; - - static getStubConfig(): Partial { - return { - button_entity: 'button.example', - binary_sensor_entity: 'binary_sensor.example', - icon: 'mdi:lightbulb', - name: 'Light', - show_name: true, - }; - } - - public setConfig(config: LightButtonCardConfig): void { - if (!config.button_entity || !config.binary_sensor_entity) { - throw new Error('button_entity and binary_sensor_entity are required'); - } - this._config = { - icon: 'mdi:lightbulb', - icon_on: 'mdi:lightbulb', - icon_off: 'mdi:lightbulb-outline', - name: '', - show_name: true, - ...config, - }; - } - - static get styles() { - return css` - ha-card { - padding: 16px; - display: flex; - flex-direction: column; - align-items: center; - cursor: pointer; - transition: transform 0.2s, box-shadow 0.2s; - } - - ha-card:hover { - transform: scale(1.02); - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); - } - - ha-card:active { - transform: scale(0.98); - } - - .button-container { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; - } - - .icon-container { - position: relative; - display: flex; - align-items: center; - justify-content: center; - width: 64px; - height: 64px; - } - - ha-icon { - width: 64px; - height: 64px; - color: var(--primary-color); - transition: color 0.3s, transform 0.2s; - } - - .icon-container.on ha-icon { - color: var(--paper-item-icon-active-color, #fdd835); - filter: drop-shadow(0 0 8px rgba(253, 216, 53, 0.6)); - } - - .icon-container.off ha-icon { - color: var(--disabled-text-color, #9e9e9e); - } - - .status-indicator { - width: 12px; - height: 12px; - border-radius: 50%; - position: absolute; - top: 0; - right: 0; - background-color: var(--disabled-text-color, #9e9e9e); - border: 2px solid var(--card-background-color, #ffffff); - transition: background-color 0.3s; - } - - .status-indicator.on { - background-color: #4caf50; - box-shadow: 0 0 8px rgba(76, 175, 80, 0.6); - } - - .name { - font-size: 14px; - font-weight: 500; - color: var(--primary-text-color); - text-align: center; - } - - .state-text { - font-size: 12px; - color: var(--secondary-text-color); - text-transform: uppercase; - letter-spacing: 0.5px; - } - `; - } - - private _getState(): string { - if (!this.hass || !this._config.binary_sensor_entity) { - return 'unknown'; - } - const stateObj = this.hass.states[this._config.binary_sensor_entity]; - if (!stateObj) { - return 'unknown'; - } - return stateObj.state; - } - - private _isOn(): boolean { - const state = this._getState(); - return state === 'on' || state === 'true' || state === '1'; - } - - private _getIcon(): string { - if (this._isOn()) { - return this._config.icon_on || this._config.icon || 'mdi:lightbulb'; - } - return this._config.icon_off || this._config.icon || 'mdi:lightbulb-outline'; - } - - private async _handleClick(): Promise { - if (!this.hass || !this._config.button_entity) { - return; - } - - const buttonEntity = this.hass.states[this._config.button_entity]; - if (!buttonEntity) { - console.error(`Button entity ${this._config.button_entity} not found`); - return; - } - - // Call the button press service - await this.hass.callService('button', 'press', { - entity_id: this._config.button_entity, - }); - - // Fire event for card updates - fireEvent(this, 'hass-more-info', { entityId: this._config.button_entity }); - } - - protected render(): TemplateResult { - if (!this._config || !this.hass) { - return html``; - } - - const isOn = this._isOn(); - const state = this._getState(); - const icon = this._getIcon(); - - return html` - -
-
- -
-
- ${this._config.show_name && this._config.name - ? html`
${this._config.name}
` - : ''} -
${state}
-
-
- `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'light-button-card': LightButtonCard; - } -} - -// Card editor for Lovelace UI -@customElement('light-button-card-editor') -export class LightButtonCardEditor extends LitElement implements LovelaceCardEditor { - @property({ attribute: false }) public hass?: HomeAssistant; - @property({ attribute: false }) private _config?: LightButtonCardConfig; - - public setConfig(config: LightButtonCardConfig): void { - this._config = config; - } - - static get styles() { - return css` - .card-config { - display: flex; - flex-direction: column; - gap: 16px; - } - `; - } - - protected render(): TemplateResult { - if (!this.hass || !this._config) { - return html``; - } - - return html` -
- - - - - - - - - - - - - - Show Name - -
- `; - } - - private _valueChanged(ev: Event): void { - if (!this._config || !this.hass) { - return; - } - - const target = ev.target as any; - const configValue = target.configValue; - - if (configValue === 'show_name') { - this._config = { - ...this._config, - [configValue]: target.checked, - }; - } else { - this._config = { - ...this._config, - [configValue]: target.value || target.checked, - }; - } - - fireEvent(this, 'config-changed', { config: this._config }); - } -} - -declare global { - interface HTMLElementTagNameMap { - 'light-button-card-editor': LightButtonCardEditor; - } -} - -// Register the card editor -if (customElements.get('light-button-card-editor') === undefined) { - customElements.define('light-button-card-editor', LightButtonCardEditor); -} - -// Card configuration helper for Lovelace -(window as any).customCards = (window as any).customCards || []; -(window as any).customCards.push({ - type: 'light-button-card', - name: 'Light Button Card', - description: 'A card with button toggle and binary sensor status', -}); - diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 5d2fe81..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "ES2020", - "lib": ["ES2020", "DOM"], - "moduleResolution": "node", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "declaration": true, - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} - diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index 269a06e..0000000 --- a/webpack.config.js +++ /dev/null @@ -1,32 +0,0 @@ -const path = require('path'); - -module.exports = { - entry: './src/light-button-card.ts', - output: { - filename: 'light-button-card.js', - path: path.resolve(__dirname, 'dist'), - library: { - type: 'module', - }, - }, - experiments: { - outputModule: true, - }, - module: { - rules: [ - { - test: /\.ts$/, - use: 'ts-loader', - exclude: /node_modules/, - }, - ], - }, - resolve: { - extensions: ['.ts', '.js'], - }, - mode: 'production', - optimization: { - minimize: false, - }, -}; -