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
This commit is contained in:
458
light-button-card.js
Normal file
458
light-button-card.js
Normal file
@@ -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 = `
|
||||
<ha-card>
|
||||
<div class="button-container">
|
||||
<div class="icon-container">
|
||||
<ha-icon class="icon"></ha-icon>
|
||||
</div>
|
||||
<div class="name"></div>
|
||||
<div class="state-text"></div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
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 = `
|
||||
<div class="card-config"></div>
|
||||
`;
|
||||
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',
|
||||
});
|
||||
Reference in New Issue
Block a user