app
Component Widgets
Public Made by Adomby adom
Reusable component widgets for pinouts, thermal vias, molecule relationships, reviewed chip outlines and lightweight four-level LOD cache listings.
← Commit history
Add <adom-table>: a generic filterable/sortable table widget
Every table on the wiki so far has been a bespoke section frame -- the
stm32f103c8t6 pinout, the repo-ownership list, the footprint variant tables.
Each copy re-implements filtering, sorting, height reporting and theming, and
each one drifts. This is that frame once, on the adom-widget runtime.
Also ships adom-table-demo.json so the registry entry is verifiable.
2 files changed
+205
widgets/adom-table-demo.jsonadded+58@@ -0,0 +1,58 @@+[+ {+ "mpn": "STM32F103C8T6",+ "package": "LQFP-48",+ "pins": 48,+ "vendor": "ST",+ "status": "active"+ },+ {+ "mpn": "ATTINY85-20SU",+ "package": "SOIC-8",+ "pins": 8,+ "vendor": "Microchip",+ "status": "active"+ },+ {+ "mpn": "SN74GTL2003PWR",+ "package": "TSSOP-20",+ "pins": 20,+ "vendor": "TI",+ "status": "active"+ },+ {+ "mpn": "RT6252BHGH6F",+ "package": "WDFN-6",+ "pins": 6,+ "vendor": "Richtek",+ "status": "active"+ },+ {+ "mpn": "LD1117D33CTR",+ "package": "DPAK",+ "pins": 3,+ "vendor": "ST",+ "status": "active"+ },+ {+ "mpn": "AMS1117-3.3",+ "package": "SOT-223",+ "pins": 4,+ "vendor": "AMS",+ "status": "nrnd"+ },+ {+ "mpn": "74HC08D",+ "package": "SOIC-14",+ "pins": 14,+ "vendor": "Nexperia",+ "status": "active"+ },+ {+ "mpn": "NE555D",+ "package": "SOIC-8",+ "pins": 8,+ "vendor": "TI",+ "status": "nrnd"+ }+]
widgets/adom-table.jsadded+147@@ -0,0 +1,147 @@+/*!+ * <adom-table> — render a JSON array as a filterable, sortable table.+ *+ * Written because the same table has now been hand-built three times as a bespoke section+ * frame (adom/stm32f103c8t6's pinout, adom/repo-ownership's ownership list, and the+ * footprint variant tables). Each copy re-implements filtering, sorting, height reporting+ * and theming, and each one drifts. This is that frame, once, on the runtime.+ *+ * Data (resolved by the runtime, in its own order of preference):+ * 1. an inline <script type="application/json"> child <- preferred on a wiki README,+ * because the readme frame is an opaque origin and a fetch from there is blocked+ * 2. src="/api/pages/<owner>/<slug>/files/<file>.json" <- works in a SECTION frame+ * 3. el.data = [...] <- dashboards / adom-lbr+ *+ * Accepted shapes:+ * [ {col: value, ...}, ... ] columns inferred from the first row+ * { columns: [...], rows: [ {...}, ... ] } explicit column order+ * { columns: [{key,label,align}], rows: [...] } explicit labels+ *+ * Attributes:+ * columns="a,b,c" restrict and order columns (overrides the data's own order)+ * heading="..." caption above the table+ * filter show the filter box (default on; filter="off" hides it)+ * facet="owner" add a dropdown that filters on one column's distinct values+ * sort="col" initial sort column; prefix "-" for descending+ * dim="key:value" render rows matching key=value at reduced opacity (e.g. archived)+ */+(function () {+ 'use strict';+ if (!window.AdomWidget) return;+ var esc = window.AdomWidget.esc;++ function normalise(data, attrColumns) {+ var rows = [], cols = null;+ if (Array.isArray(data)) rows = data;+ else if (data && Array.isArray(data.rows)) { rows = data.rows; cols = data.columns || null; }+ else if (data && typeof data === 'object') {+ // a bare object of arrays, e.g. {live:[...], dormant:[...]} - concatenate and tag+ var keys = Object.keys(data).filter(function (k) { return Array.isArray(data[k]); });+ if (keys.length) {+ keys.forEach(function (k) {+ data[k].forEach(function (r) { rows.push(Object.assign({ _group: k }, r)); });+ });+ }+ }+ if (attrColumns) cols = attrColumns.split(',').map(function (s) { return s.trim(); });+ if (!cols) cols = rows.length ? Object.keys(rows[0]).filter(function (k) { return k[0] !== '_'; }) : [];+ cols = cols.map(function (c) {+ return typeof c === 'string' ? { key: c, label: c.replace(/[_-]/g, ' ').toUpperCase() } : c;+ });+ return { cols: cols, rows: rows };+ }++ window.AdomWidget.define('adom-table', {+ attributes: ['columns', 'heading', 'filter', 'facet', 'sort', 'dim'],+ loadingText: 'Loading table',+ emptyText: 'No data supplied to <adom-table>',+ css: [+ '.bar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin:0 0 9px}',+ '.bar input,.bar select{background:var(--bg,#0d1117);color:inherit;border:1px solid var(--line,#30363d);',+ 'border-radius:6px;padding:6px 9px;font:inherit}',+ '.bar input{min-width:200px}',+ '.muted{opacity:.62;font-size:12px}',+ 'table{border-collapse:collapse;width:100%;font-size:13px}',+ 'th{text-align:left;font-size:10.5px;letter-spacing:.7px;opacity:.6;padding:6px 8px;',+ 'border-bottom:1px solid var(--line,#30363d);cursor:pointer;user-select:none;white-space:nowrap}',+ 'th[aria-sort]:after{content:"";opacity:.8}',+ 'th[aria-sort="ascending"]:after{content:" \\2191"}',+ 'th[aria-sort="descending"]:after{content:" \\2193"}',+ 'td{padding:5px 8px;border-bottom:1px solid var(--line2,#161b22);vertical-align:top}',+ 'tr.dim td{opacity:.45}',+ 'caption{text-align:left;font-weight:600;padding:0 0 8px}'+ ].join(''),+ render: function (data, ctx) {+ var n = normalise(data, this.getAttribute('columns'));+ this._n = n;+ var heading = this.getAttribute('heading');+ var showFilter = this.getAttribute('filter') !== 'off';+ var facet = this.getAttribute('facet');+ var bar = '';+ if (showFilter || facet) {+ bar = '<div class=bar>' ++ (showFilter ? '<input part=filter type=search placeholder="filter…">' : '') ++ (facet ? '<select part=facet><option value="">all ' + esc(facet) + '</option></select>' : '') ++ '<span class=muted data-count></span></div>';+ }+ return bar + '<table>' ++ (heading ? '<caption>' + esc(heading) + '</caption>' : '') ++ '<thead><tr>' + n.cols.map(function (c) {+ return '<th data-k="' + esc(c.key) + '">' + esc(c.label) + '</th>';+ }).join('') + '</tr></thead><tbody></tbody></table>';+ },+ mounted: function (root) {+ var el = this, n = el._n, tb = root.querySelector('tbody');+ var q = root.querySelector('input'), sel = root.querySelector('select');+ var count = root.querySelector('[data-count]');+ var dim = (el.getAttribute('dim') || '').split(':');+ var s = (el.getAttribute('sort') || '').replace(/^-/, '');+ var dir = (el.getAttribute('sort') || '')[0] === '-' ? -1 : 1;++ if (sel) {+ var f = el.getAttribute('facet');+ var vals = {};+ n.rows.forEach(function (r) { if (r[f] != null) vals[r[f]] = 1; });+ Object.keys(vals).sort().forEach(function (v) {+ var o = document.createElement('option'); o.textContent = v; sel.appendChild(o);+ });+ }+ function draw() {+ var term = (q && q.value || '').toLowerCase();+ var fv = sel && sel.value;+ var f = el.getAttribute('facet');+ var out = n.rows.filter(function (r) {+ if (fv && String(r[f]) !== fv) return false;+ if (!term) return true;+ return n.cols.some(function (c) {+ return String(r[c.key] == null ? '' : r[c.key]).toLowerCase().indexOf(term) >= 0;+ });+ });+ if (s) out = out.slice().sort(function (a, b) {+ var A = a[s], B = b[s];+ if (typeof A === 'number' && typeof B === 'number') return (A - B) * dir;+ return String(A).localeCompare(String(B)) * dir;+ });+ tb.innerHTML = out.map(function (r) {+ var isDim = dim.length === 2 && String(r[dim[0]]) === dim[1];+ return '<tr' + (isDim ? ' class=dim' : '') + '>' + n.cols.map(function (c) {+ return '<td>' + esc(r[c.key] == null ? '' : r[c.key]) + '</td>';+ }).join('') + '</tr>';+ }).join('');+ if (count) count.textContent = out.length + ' of ' + n.rows.length;+ root.querySelectorAll('th').forEach(function (th) {+ if (th.dataset.k === s) th.setAttribute('aria-sort', dir > 0 ? 'ascending' : 'descending');+ else th.removeAttribute('aria-sort');+ });+ }+ root.querySelectorAll('th').forEach(function (th) {+ th.addEventListener('click', function () {+ dir = (s === th.dataset.k) ? -dir : 1; s = th.dataset.k; draw();+ });+ });+ if (q) q.addEventListener('input', draw);+ if (sel) sel.addEventListener('change', draw);+ draw();+ }+ });+}());