← back to Dw Hero Admin
fix(filter): preserve focus on prodFilter — split renderProducts() into renderProductsHeader() + renderProductsGrid()
4adc64635fa86a824bcbdac703a1c52646826b46 · 2026-05-14 08:41:55 -0700 · SteveStudio2
Old code wiped productsWrap.innerHTML on every keystroke, which destroyed the input the user was typing into and stole focus after each character. Header is now rendered once on site-pick; grid + count update on each filter keystroke, but the input DOM node and its caret are untouched.
Files touched
Diff
commit 4adc64635fa86a824bcbdac703a1c52646826b46
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Thu May 14 08:41:55 2026 -0700
fix(filter): preserve focus on prodFilter — split renderProducts() into renderProductsHeader() + renderProductsGrid()
Old code wiped productsWrap.innerHTML on every keystroke, which destroyed the input the user was typing into and stole focus after each character. Header is now rendered once on site-pick; grid + count update on each filter keystroke, but the input DOM node and its caret are untouched.
---
public/index.html | 38 +++++++++++++++++++++++++-------------
1 file changed, 25 insertions(+), 13 deletions(-)
diff --git a/public/index.html b/public/index.html
index 4185dd0..0c32b51 100644
--- a/public/index.html
+++ b/public/index.html
@@ -166,7 +166,8 @@
els.siteMeta.textContent = data.productCount + ' products · newest first';
els.slotsWrap.style.display = 'block';
renderSlots();
- renderProducts();
+ renderProductsHeader();
+ renderProductsGrid();
}).catch(function (e) {
els.productsWrap.innerHTML = '<div class="empty-state">Error: ' + e.message + '</div>';
});
@@ -244,20 +245,35 @@
flashStatus('unsaved', '');
}
- function renderProducts() {
- var q = (state.filter || '').toLowerCase();
- var items = state.products.filter(function (p) {
- if (!q) return true;
- return ((p.title || '') + ' ' + (p.sku || '') + ' ' + (p.vendor || '') + ' ' + (p.tags || []).join(' ')).toLowerCase().indexOf(q) !== -1;
- });
+ // Header is rendered ONCE per site pick — preserves focus on the search
+ // input across keystroke-driven re-renders of the grid.
+ function renderProductsHeader() {
els.productsWrap.innerHTML = '\
<div class="products-header">\
- <h3>All products — newest first (' + items.length + ')</h3>\
- <div class="product-search"><input id="prodFilter" type="search" placeholder="Filter pattern, vendor, tag..." value="' + escapeHtml(q) + '"></div>\
+ <h3 id="prodCount">All products — newest first</h3>\
+ <div class="product-search"><input id="prodFilter" type="search" placeholder="Filter pattern, vendor, tag..." value="' + escapeHtml(state.filter || '') + '"></div>\
</div>\
<div class="products" id="prodGrid"></div>\
';
+ var pf = document.getElementById('prodFilter');
+ pf.addEventListener('input', function () {
+ state.filter = pf.value;
+ renderProductsGrid();
+ });
+ }
+
+ // Only re-renders the grid + count — never touches the header DOM, so
+ // the user's keystroke focus stays in #prodFilter while typing.
+ function renderProductsGrid() {
+ var q = (state.filter || '').toLowerCase();
+ var items = state.products.filter(function (p) {
+ if (!q) return true;
+ return ((p.title || '') + ' ' + (p.sku || '') + ' ' + (p.vendor || '') + ' ' + (p.tags || []).join(' ')).toLowerCase().indexOf(q) !== -1;
+ });
+ var count = document.getElementById('prodCount');
+ if (count) count.textContent = 'All products — newest first (' + items.length + ')';
var grid = document.getElementById('prodGrid');
+ if (!grid) return;
grid.innerHTML = items.slice(0, 400).map(function (p) {
return '<div class="product" draggable="true" data-sku="' + escapeHtml(p.sku) + '">\
<img src="' + escapeHtml(p.image_url) + '" alt="' + escapeHtml(p.title || '') + '" loading="lazy">\
@@ -272,10 +288,6 @@
});
el.addEventListener('dragend', function () { el.classList.remove('dragging'); });
});
- var pf = document.getElementById('prodFilter');
- if (pf) {
- pf.addEventListener('input', function () { state.filter = pf.value; renderProducts(); });
- }
}
function flashStatus(msg, cls) {
← ea04db8 initial scaffold — DW hero-grid admin :9772 with drag-drop i
·
back to Dw Hero Admin
·
css: flex-shrink:0 on .topbar and .slots-wrap 393ad7b →