Replace datalist with custom autocomplete dropdown for HA entity field (mobile fix)

This commit is contained in:
2026-04-14 01:38:42 -05:00
parent b6a3533fed
commit a9d0b3fc63
+64 -17
View File
@@ -210,12 +210,18 @@ function addInstallRow() {
{% if ha_enabled %} {% if ha_enabled %}
<div class="form-group"> <div class="form-group">
<label for="edit-ha-entity">Home Assistant Entity ID</label> <label for="edit-ha-entity">Home Assistant Entity ID</label>
<input type="text" id="edit-ha-entity" name="ha_entity_id" <div style="position:relative;">
value="{{ device.ha_entity_id or '' }}" <input type="text" id="edit-ha-entity" name="ha_entity_id"
placeholder="e.g. sensor.tv_remote_battery" value="{{ device.ha_entity_id or '' }}"
list="ha-entities-list" autocomplete="off"> placeholder="e.g. sensor.tv_remote_battery"
<datalist id="ha-entities-list"></datalist> autocomplete="off">
<small class="text-muted" id="ha-entities-status" style="display:block;margin-top:0.25rem;font-size:0.8rem;"></small> <div id="ha-entity-dropdown"
style="display:none;position:absolute;left:0;right:0;top:100%;z-index:200;
background:var(--bg-card);border:1px solid var(--border);border-radius:4px;
max-height:220px;overflow-y:auto;box-shadow:0 4px 12px rgba(0,0,0,0.15);"></div>
</div>
<small class="text-muted" id="ha-entities-status"
style="display:block;margin-top:0.25rem;font-size:0.8rem;"></small>
</div> </div>
{% endif %} {% endif %}
<button class="btn btn-primary" type="submit">Save Changes</button> <button class="btn btn-primary" type="submit">Save Changes</button>
@@ -237,24 +243,65 @@ function addInstallRow() {
{% if ha_enabled %} {% if ha_enabled %}
<script> <script>
(function() { (function() {
var datalist = document.getElementById('ha-entities-list'); var input = document.getElementById('edit-ha-entity');
var dropdown = document.getElementById('ha-entity-dropdown');
var status = document.getElementById('ha-entities-status'); var status = document.getElementById('ha-entities-status');
if (!datalist) return; if (!input || !dropdown) return;
function populate(entities) { var allEntities = [];
datalist.innerHTML = '';
entities.forEach(function(e) { function renderDropdown(query) {
var opt = document.createElement('option'); var q = query.toLowerCase();
opt.value = e.entity_id; var matches = q
if (e.friendly_name && e.friendly_name !== e.entity_id) opt.label = e.friendly_name; ? allEntities.filter(function(e) {
datalist.appendChild(opt); return e.entity_id.toLowerCase().indexOf(q) !== -1
|| (e.friendly_name && e.friendly_name.toLowerCase().indexOf(q) !== -1);
})
: allEntities;
if (!matches.length) { dropdown.style.display = 'none'; return; }
dropdown.innerHTML = '';
matches.slice(0, 60).forEach(function(e) {
var item = document.createElement('div');
item.style.cssText = 'padding:0.5rem 0.75rem;cursor:pointer;border-bottom:1px solid var(--border);';
var label = document.createElement('div');
label.style.cssText = 'font-size:0.875rem;word-break:break-all;';
label.textContent = e.entity_id;
item.appendChild(label);
if (e.friendly_name && e.friendly_name !== e.entity_id) {
var sub = document.createElement('div');
sub.style.cssText = 'font-size:0.75rem;color:var(--text-muted);';
sub.textContent = e.friendly_name;
item.appendChild(sub);
}
item.addEventListener('mousedown', function(ev) { ev.preventDefault(); });
item.addEventListener('click', function() {
input.value = e.entity_id;
dropdown.style.display = 'none';
input.focus();
});
item.addEventListener('mouseover', function() { this.style.background = 'var(--bg-hover)'; });
item.addEventListener('mouseout', function() { this.style.background = ''; });
dropdown.appendChild(item);
}); });
dropdown.style.display = 'block';
}
input.addEventListener('input', function() { renderDropdown(this.value); });
input.addEventListener('focus', function() { if (allEntities.length) renderDropdown(this.value); });
input.addEventListener('blur', function() { setTimeout(function() { dropdown.style.display = 'none'; }, 150); });
input.addEventListener('keydown', function(e) { if (e.key === 'Escape') dropdown.style.display = 'none'; });
document.addEventListener('click', function(e) {
if (!input.contains(e.target) && !dropdown.contains(e.target)) dropdown.style.display = 'none';
});
function onEntities(entities) {
allEntities = entities;
if (status) status.textContent = entities.length ? entities.length + ' battery entities available' : ''; if (status) status.textContent = entities.length ? entities.length + ' battery entities available' : '';
} }
try { try {
var cached = sessionStorage.getItem('ha_battery_entities'); var cached = sessionStorage.getItem('ha_battery_entities');
if (cached) { populate(JSON.parse(cached)); return; } if (cached) { onEntities(JSON.parse(cached)); return; }
} catch(e) {} } catch(e) {}
if (status) status.textContent = 'Loading HA entities\u2026'; if (status) status.textContent = 'Loading HA entities\u2026';
@@ -262,7 +309,7 @@ function addInstallRow() {
.then(function(r) { return r.json(); }) .then(function(r) { return r.json(); })
.then(function(entities) { .then(function(entities) {
try { sessionStorage.setItem('ha_battery_entities', JSON.stringify(entities)); } catch(e) {} try { sessionStorage.setItem('ha_battery_entities', JSON.stringify(entities)); } catch(e) {}
populate(entities); onEntities(entities);
}) })
.catch(function() { if (status) status.textContent = ''; }); .catch(function() { if (status) status.textContent = ''; });
}()); }());