← back to Rentv Sheet Enrich
Live console: Width presets (Tight/Cozy/Wide/Fit + auto-refit on window resize) + Text size S/M/L, replacing the raw density slider; fix column drag-to-reorder (preventDefault stops text-selection hijack)
00fc8a62949e6f8cc1696eae546222af93a110f0 · 2026-08-13 16:27:15 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit 00fc8a62949e6f8cc1696eae546222af93a110f0
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Aug 13 16:27:15 2026 -0700
Live console: Width presets (Tight/Cozy/Wide/Fit + auto-refit on window resize) + Text size S/M/L, replacing the raw density slider; fix column drag-to-reorder (preventDefault stops text-selection hijack)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
public_live/index.html | 39 +++++++++++++++++++++++++++++++++------
1 file changed, 33 insertions(+), 6 deletions(-)
diff --git a/public_live/index.html b/public_live/index.html
index 86a83e2..32c8e78 100644
--- a/public_live/index.html
+++ b/public_live/index.html
@@ -32,6 +32,7 @@
button{cursor:pointer} button:hover{border-color:var(--accent)}
.seg{display:inline-flex;border:1px solid var(--line);border-radius:8px;overflow:hidden}
.seg button{border:0;border-radius:0;background:var(--panel2)} .seg button.on{background:var(--accent);color:#04121f;font-weight:700}
+ .segsm{flex:1} .segsm button{flex:1;padding:5px 8px;font-size:11px} .segsm button+button{border-left:1px solid var(--line)}
.grow{flex:1}
.sync{display:flex;align-items:center;gap:7px;color:var(--dim);font-size:12px}
.dot{width:9px;height:9px;border-radius:50%;background:#3fb950} .dot.stale{background:#d29922} .dot.err{background:#f85149}
@@ -201,6 +202,7 @@ let FILTERS={}, SEARCH='', SORT=LS.get('sort',null), GROUP=LS.get('group',''), H
// the Group dropdown still works within a session for anyone who wants it; it just won't stick across refreshes.
GROUP=''; LS.set('group','');
let LOOKUP=null; // when set to a column index, the main area shows that field's full value lookup
+let WIDTHPRESET=LS.get('widthpreset',''); // '', tight, cozy, wide, fit — 'fit' re-fits columns to the window on resize
const MAXROWS=2500;
function pk(){return 'p'+GID} // per-tab localStorage namespace
@@ -291,8 +293,8 @@ function buildRail(){
<div class="rrow"><label>Sort</label><select id="sortsel"><option value="">— none —</option>${opts}</select>
<button id="sortdir" title="asc/desc">${SORT&&SORT.dir<0?'▼':'▲'}</button></div>
<div class="rrow"><label>Group</label><select id="groupsel"><option value="">— none —</option>${opts}</select></div>
- <div class="rrow"><label>Density</label><input type="range" id="dens" min="10" max="18" value="${getComputedStyle(document.documentElement).getPropertyValue('--fs').trim().replace('px','')||13}"></div>
- <div class="rrow"><label>Columns</label><button id="fitcols" title="size every column to fit its content">⤢ Fit to content</button><button id="resetcols" title="reset widths + order to default">↺ Reset</button></div>
+ <div class="rrow"><label>Width</label><div class="seg segsm" id="wseg" title="column width"><button data-w="tight">Tight</button><button data-w="cozy">Cozy</button><button data-w="wide">Wide</button><button data-w="fit" title="fit all columns on screen (re-fits when you resize the window)">Fit</button></div></div>
+ <div class="rrow"><label>Text</label><div class="seg segsm" id="tseg" title="text size"><button data-t="12">S</button><button data-t="14">M</button><button data-t="16">L</button></div><button id="fitcols" title="size each column to its content">⤢ Autofit</button><button id="resetcols" title="reset widths + order">↺ Reset</button></div>
<details class="sec" open><summary>Fields (${H.filter(Boolean).length}) · <span class="mini" id="allf">all</span> / <span class="mini" id="nonef">none</span></summary>
<div id="ftogs"></div></details>
<details class="sec" open><summary>Filters — click a value</summary><div id="facets"></div></details>`;
@@ -366,8 +368,11 @@ function wireRail(){
$('#sortsel').onchange=e=>{SORT=e.target.value===''?null:{col:+e.target.value,dir:SORT?SORT.dir:1};LS.set('sort.'+curKey(),SORT);render()};
$('#sortdir').onclick=()=>{if(SORT){SORT.dir*=-1;LS.set('sort.'+curKey(),SORT);$('#sortdir').textContent=SORT.dir<0?'▼':'▲';render()}};
$('#groupsel').onchange=e=>{GROUP=e.target.value;LS.set('group',GROUP);render()};
- $('#dens').oninput=e=>{document.documentElement.style.setProperty('--fs',e.target.value+'px');LS.set('dens',e.target.value)};
- $('#fitcols').onclick=fitAllCols; $('#resetcols').onclick=resetLayout;
+ $$('#wseg button').forEach(b=>b.onclick=()=>setWidthPreset(b.dataset.w));
+ $$('#tseg button').forEach(b=>b.onclick=()=>{document.documentElement.style.setProperty('--fs',b.dataset.t+'px');LS.set('dens',b.dataset.t);markTextBtns()});
+ $('#fitcols').onclick=()=>{WIDTHPRESET='';LS.set('widthpreset','');fitAllCols();markWidthBtns()};
+ $('#resetcols').onclick=()=>{WIDTHPRESET='';LS.set('widthpreset','');resetLayout()};
+ markWidthBtns(); markTextBtns();
$('#alls').onclick=()=>{SELECTED=new Set(META.tabs.map(t=>t.gid));loadData()};
$('#nones').onclick=()=>{SELECTED=new Set([GID]);loadData()};
$('#allf').onclick=()=>{HIDDEN.clear();LS.set('hidden.'+curKey(),[]);buildRail();render()};
@@ -420,6 +425,23 @@ function reorder(from,to){ // shared: move column 'from' to 'to's slot; used b
ORDER=order; LS.set('order.'+curKey(),ORDER); render(); renderFields(); // sync BOTH views
}
const moveCol=reorder, moveFieldTo=reorder;
+// WIDTH PRESETS: one click sizes every visible column. 'fit' also re-fits on window resize.
+function setWidthPreset(mode){
+ WIDTHPRESET=mode; LS.set('widthpreset',mode);
+ const cols=visCols();
+ if(mode==='fit'){ applyFit(); }
+ else if(mode==='cozy'){ COLW={}; LS.set('colw.'+curKey(),COLW); render(); }
+ else{ const w=mode==='tight'?120:280; cols.forEach(c=>COLW[c.i]=w); LS.set('colw.'+curKey(),COLW); render(); }
+ markWidthBtns();
+}
+function applyFit(){ // distribute the viewport width across visible columns so the whole table fits on screen
+ const cols=visCols(); if(!cols.length)return;
+ const wrap=$('#wrap'); const avail=((wrap&&wrap.clientWidth)||window.innerWidth)-44-24; // action col + scrollbar/padding
+ const w=Math.max(64, Math.floor(avail/cols.length));
+ cols.forEach(c=>COLW[c.i]=w); LS.set('colw.'+curKey(),COLW); render();
+}
+function markWidthBtns(){ const b=document.querySelectorAll('#wseg button'); b.forEach(x=>x.classList.toggle('on',x.dataset.w===WIDTHPRESET)); }
+function markTextBtns(){ const fs=(getComputedStyle(document.documentElement).getPropertyValue('--fs').trim().replace('px','')||'14'); document.querySelectorAll('#tseg button').forEach(x=>x.classList.toggle('on',x.dataset.t===fs)); }
function fitAllCols(){ // expand every visible column to fit its content
visCols().forEach(c=>{ let max=String(DATA.headers[c.i]||'').length;
document.querySelectorAll(`#grid td[data-c="${c.i}"]`).forEach(td=>{const t=td.textContent||'';if(t.length>max)max=t.length});
@@ -521,12 +543,15 @@ function wireGrid(){
// header: click = sort, drag = MOVE column (manual drag, 5px threshold; order persists per tab)
$$('#grid thead th[data-c]').forEach(th=>th.addEventListener('mousedown',e=>{
if(e.target.classList.contains('rz'))return; // resize handle is separate
+ e.preventDefault(); // stop text-selection from hijacking the drag
+ document.body.style.cursor='grabbing';
const startX=e.clientX, col=+th.dataset.c, ths=[...document.querySelectorAll('#grid thead th[data-c]')];
let moved=false;
const at=x=>ths.find(t=>{const r=t.getBoundingClientRect();return x>=r.left&&x<=r.right});
const mv=ev=>{ if(!moved&&Math.abs(ev.clientX-startX)>5){moved=true;th.classList.add('dragging')}
if(moved){ths.forEach(t=>t.classList.remove('droptarget'));const tg=at(ev.clientX);if(tg&&tg!==th)tg.classList.add('droptarget')} };
const up=ev=>{ document.removeEventListener('mousemove',mv);document.removeEventListener('mouseup',up);
+ document.body.style.cursor='';
th.classList.remove('dragging');ths.forEach(t=>t.classList.remove('droptarget'));
if(moved){const tg=at(ev.clientX);if(tg&&+tg.dataset.c!==col)moveCol(col,+tg.dataset.c);}
else{SORT=(SORT&&SORT.col===col)?{col,dir:-SORT.dir}:{col,dir:1};LS.set('sort.'+curKey(),SORT);render();$('#sortsel')&&($('#sortsel').value=col);} };
@@ -545,7 +570,7 @@ function wireGrid(){
const mv=ev=>{ newW=Math.max(60,startW+(ev.clientX-startX)); guide.style.left=(startX+(newW-startW))+'px'; };
const up=()=>{ document.removeEventListener('mousemove',mv);document.removeEventListener('mouseup',up);
guide.remove(); document.body.classList.remove('resizing');
- COLW[ci]=Math.round(newW); LS.set('colw.'+curKey(),COLW); render(); }; // full re-render = exact width, no re-proportioning
+ COLW[ci]=Math.round(newW); WIDTHPRESET=''; LS.set('widthpreset',''); LS.set('colw.'+curKey(),COLW); render(); markWidthBtns(); }; // manual width exits any preset
document.addEventListener('mousemove',mv);document.addEventListener('mouseup',up);
}));
// double-click a resize handle to auto-fit the column to its content
@@ -628,8 +653,10 @@ $('#act-copy').onclick=()=>{navigator.clipboard?.writeText(($('#d-subject').valu
function applyTheme(t){document.body.classList.toggle('light',t==='light');$('#theme').textContent=t==='light'?'☀️':'🌙'}
$('#theme').onclick=()=>{const t=document.body.classList.contains('light')?'dark':'light';LS.set('theme',t);applyTheme(t)};
applyTheme(LS.get('theme', (window.matchMedia&&matchMedia('(prefers-color-scheme: light)').matches)?'light':'dark'));
+// Fit mode = table always fits the window: re-fit columns to the viewport when it resizes
+window.addEventListener('resize',()=>{ if(WIDTHPRESET==='fit'){ clearTimeout(window._rzfit); window._rzfit=setTimeout(applyFit,120); } });
document.addEventListener('keydown',e=>{if(e.key==='Escape')closeDraft()});
-(async()=>{await loadMeta();await loadData()})();
+(async()=>{await loadMeta();await loadData(); if(WIDTHPRESET==='fit')applyFit();})();
</script>
</body>
</html>
← 2a21996 Live console: FORCE grouping off on every load (kills the bl
·
back to Rentv Sheet Enrich
·
Live console: email cells are now clickable mailto links; co dfed233 →