const LUMA_VACUUM_VERSION = '0.2.0'; class LumaVacuumCard extends HTMLElement { set hass(hass) { this._hass = hass; this._render(); } setConfig(config) { if (!config.entity) throw new Error('Entity is required'); this._config = { entity: config.entity, name: config.name || '', size: config.size || '1x2', accent_color: config.accent_color || '', icon: config.icon || '', image: config.image || '', tap_action: config.tap_action || { action: 'more-info' }, hold_action: config.hold_action || { action: 'more-info' }, ...config }; this._render(); } getCardSize() { return 4; } static getStubConfig() { return { entity: '', name: '', size: '1x2' }; } connectedCallback() { if (!this.shadowRoot) this.attachShadow({ mode: 'open' }); this._render(); } _getState() { return this._hass?.states?.[this._config?.entity]; } _getPrefix() { return this._config?.entity?.replace(/^vacuum\./, '') || ''; } _getSensorState(suffix) { const id = `sensor.${this._getPrefix()}_${suffix}`; return this._hass?.states?.[id]; } _callService(service, data = {}) { if (!this._hass || !this._config) return; this._hass.callService('vacuum', service, { entity_id: this._config.entity, ...data }); } _handleClick() { if (!this._hass || !this._config) return; const ev = new Event('hass-more-info', { composed: true }); ev.detail = { entityId: this._config.entity }; this.dispatchEvent(ev); } _render() { if (!this.shadowRoot || !this._config) return; const s = this._getState(); const prefix = this._getPrefix(); const statusSensor = this._getSensorState('status'); const statusText = statusSensor?.state || s?.state || 'unavailable'; const isCleaning = ['cleaning', 'segment_cleaning', 'zoned_cleaning', 'spot_cleaning', 'segment_mopping', 'robot_status_mopping'].includes(statusText); const isReturning = ['returning_home', 'docking'].includes(statusText); const errorSensor = this._getSensorState('vacuum_error'); const hasError = errorSensor && errorSensor.state !== 'none' && errorSensor.state !== ''; const batterySensor = this._getSensorState('battery'); const battery = batterySensor ? parseInt(batterySensor.state) || 0 : 0; const accent = hasError ? '#e05050' : isCleaning ? '#7EB8DA' : isReturning ? '#f0a060' : 'rgba(157,170,184,0.7)'; const displayStatus = { charging: 'Charged', charging_complete: 'Charged', cleaning: 'Cleaning', segment_cleaning: 'Cleaning', zoned_cleaning: 'Cleaning', returning_home: 'Returning', docking: 'Returning', washing_the_mop: 'Washing mop', going_to_wash_the_mop: 'Washing mop', emptying_the_bin: 'Emptying bin', docked: 'Docked', idle: 'Idle', paused: 'Paused', error: 'Error', }[statusText] || statusText; const statusLine = hasError ? `⚠ ${errorSensor.state.replace(/_/g, ' ')} · ${battery}%` : `${displayStatus} · ${battery}%`; const statusColor = hasError ? '#e05050' : isCleaning ? accent : 'rgba(232,232,232,0.50)'; const name = this._config.name || s?.attributes?.friendly_name || ''; const glowHtml = isCleaning ? '
' : hasError ? '' : ''; const actions = ['Dock', 'Mop Only', 'V&M Fast', 'After Meals', 'Vac & Mop', 'Custom']; const actionsHtml = actions.map((label, i) => { const cls = i === 5 ? 'action-btn custom' : 'action-btn'; return ``; }).join(''); this.shadowRoot.innerHTML = `