← back to Mfr Review Viewer Corruption
public/kv-list.js
88 lines
/* kv-list.js — compact key/value record list. Zero dependencies.
*
* Renders each record as ONE scannable row: a fixed identity block on the left
* (title + subline + optional badge) and every visible field as inline
* uppercase-micro-label + value chips flowing to the right, wrapping responsively.
*
* Standalone extraction of three-view.js's `listCompact()` (CRCP TK-10526),
* renamed .kv-* so it drops into any page without dragging the rest of three-view.
*
* KVList.render({
* mount: '#kv', // selector | element (required)
* rows: [ {...}, ... ], // record objects, in display order (required)
* fields: [ {k,l,calc,money,num,pct,date,html,suf}, ... ], // field metadata (required)
* title: row => ({name, sub?, href?}), // left identity block
* badgeHtml:row => '<span class="kv-badge">…</span>' | '', // optional badge
* fvis: k => bool, // optional per-field visibility filter
* rowClick: (row, el) => {}, // optional row click handler
* empty: 'No matches.', // optional empty message
* })
*/
(function (global) {
function esc(s) {
return String(s == null ? '' : s)
.replace(/&/g, '&').replace(/</g, '<')
.replace(/>/g, '>').replace(/"/g, '"');
}
function fmt(v, f) {
if (f.html) return v == null ? '—' : String(v); // page-formatted HTML passes through
if (v == null || v === '') return '—';
if (f.money) return '$' + (+v).toLocaleString() + (f.suf || '');
if (f.pct) return (+v).toFixed(2) + '%';
if (f.num) return (+v).toLocaleString() + (f.suf || '');
if (f.date) { try { return new Date(v).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }); } catch (e) { return '—'; } }
return esc(v) + (f.suf || '');
}
var fval = function (r, f) { return f.calc ? f.calc(r) : r[f.k]; };
function resolve(m) {
if (!m) return null;
return typeof m === 'string' ? document.querySelector(m) : m;
}
function render(O) {
var mount = resolve(O.mount);
if (!mount) { console.warn('[kv-list] mount not found:', O.mount); return; }
var rows = O.rows || [];
var fields = (O.fields || []).filter(function (f) {
if (f.k === 'title' || f.badge) return false;
return O.fvis ? O.fvis(f.k) : true;
});
if (!rows.length) {
mount.innerHTML = '<div class="kv-empty">' + esc(O.empty || 'No matches.') + '</div>';
return;
}
mount.innerHTML = '<div class="kv-rows">' + rows.map(function (r, i) {
var t = O.title ? O.title(r) : { name: r.name || r.title || '', sub: '' };
var nm = t.href
? '<a href="' + esc(t.href) + '" target="_blank" rel="noopener noreferrer">' + esc(t.name) + '</a>'
: esc(t.name);
var badge = O.badgeHtml ? (O.badgeHtml(r) || '') : '';
return '<div class="kv-lrow' + (O.rowClick ? ' kv-click' : '') + '" data-kvri="' + i + '">'
+ '<div class="kv-lmain"><span class="kv-la">' + nm + '</span>'
+ (t.sub ? '<span class="kv-lsub">' + esc(t.sub) + '</span>' : '') + badge + '</div>'
+ '<div class="kv-lmeta">'
+ fields.map(function (f) {
return '<span class="kv-lm"><span class="kv-lmk">' + esc(f.l) + '</span>' + fmt(fval(r, f), f) + '</span>';
}).join('')
+ '</div></div>';
}).join('') + '</div>';
if (O.rowClick) {
mount.querySelectorAll('.kv-lrow').forEach(function (el) {
el.addEventListener('click', function (e) {
if (e.target.closest('a')) return;
O.rowClick(rows[+el.dataset.kvri], el);
});
});
}
}
global.KVList = { render: render, fmt: fmt };
})(typeof window !== 'undefined' ? window : this);