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
pinout: JSON export (copy + download) + public toJSON() — structured pin map for AI/code (John's ask)
2 files changed
+74−2
files/pinout-widget.js+37−1@@ -115,7 +115,7 @@ const heading = this.getAttribute('heading') || d.mpn || ''; root.innerHTML = `<style>${this._css()}</style> <div class="card">- ${heading ? `<div class="hd"><span class="mpn">${esc(heading)}</span>${d.package ? `<span class="pkg">${esc(d.package)}</span>` : ''}<span class="count">${pins.length} pins</span></div>` : ''}+ <div class="hd">${heading ? `<span class="mpn">${esc(heading)}</span>` : ''}${d.package ? `<span class="pkg">${esc(d.package)}</span>` : ''}<span class="count">${pins.length} pins</span><span class="tools"><button class="tbtn" data-act="copy" title="Copy the pin map as JSON">copy</button><button class="tbtn" data-act="dl" title="Download the pin map as JSON">JSON ↓</button></span></div> <div class="body"> <div class="diagram"> <div class="readout" data-readout>hover a pin →</div>@@ -189,6 +189,35 @@ /** Public: drive the highlight from outside (e.g. a linked footprint/symbol widget). */ highlight(n) { this._set(n, false); } + /** Public: the canonical pin map as a plain object — the structured handle for code/AI.+ * Types are resolved (inferred filled in) so consumers get a complete map. No fabricated fields. */+ toJSON() {+ const d = this._data || {};+ const pins = (Array.isArray(d.pins) ? d.pins : [])+ .map(p => ({ number: p.number, name: p.name, type: (p.type || inferType(p.name)).toLowerCase(), ...(p.xy ? { xy: p.xy } : {}) }))+ .sort((a, b) => (Number(a.number) - Number(b.number)) || String(a.number).localeCompare(String(b.number)));+ const out = {};+ if (d.mpn) out.mpn = d.mpn;+ if (d.package) out.package = d.package;+ out.pins = pins;+ return out;+ }++ _exportJson() { return JSON.stringify(this.toJSON(), null, 2); }+ _exportName() { const d = this._data || {}; return ((d.mpn || 'pinout') + (d.package ? '-' + d.package : '')).replace(/[^\w.-]+/g, '_') + '.pins.json'; }+ _download() {+ const blob = new Blob([this._exportJson()], { type: 'application/json' });+ const url = URL.createObjectURL(blob), a = document.createElement('a');+ a.href = url; a.download = this._exportName(); document.body.appendChild(a); a.click();+ a.remove(); setTimeout(() => URL.revokeObjectURL(url), 1000);+ }+ async _copy(btn) {+ const txt = this._exportJson(), done = () => { if (!btn) return; const o = btn.textContent; btn.textContent = 'copied'; btn.classList.add('ok'); setTimeout(() => { btn.textContent = o; btn.classList.remove('ok'); }, 1100); };+ try { await navigator.clipboard.writeText(txt); done(); return; } catch (e) { /* sandboxed / denied — fall back */ }+ try { const ta = document.createElement('textarea'); ta.value = txt; ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); done(); }+ catch (e2) { this._download(); } // last resort: hand them the file+ }+ // adom-lbr shared cross-highlight bus: BroadcastChannel('lbr-hover') + parent relay. _busInit() { this._self = 'w' + Math.random().toString(36).slice(2);@@ -207,6 +236,8 @@ _wire(pins) { this._pinsRendered = pins; const root = this.shadowRoot;+ const dl = root.querySelector('.tbtn[data-act="dl"]'); if (dl) dl.onclick = () => this._download();+ const cp = root.querySelector('.tbtn[data-act="copy"]'); if (cp) cp.onclick = () => this._copy(cp); root.querySelectorAll('tr[data-pin]').forEach(r => { r.onmouseenter = () => this._set(r.dataset.pin, true); r.onmouseleave = () => this._set('', true); }); root.querySelectorAll('svg .pin[data-pin]').forEach(g => { g.onmouseenter = () => this._set(g.dataset.pin, true); g.onmouseleave = () => this._set('', true); }); // Match the pin table's height to the chip diagram column so it fills the@@ -234,6 +265,11 @@ .hd .mpn{font:700 17px ui-monospace,'SF Mono',Menlo,monospace;letter-spacing:-.2px} .hd .pkg{font:600 11px ui-monospace,monospace;color:var(--accent);background:color-mix(in srgb,var(--accent) 12%,transparent);border:1px solid color-mix(in srgb,var(--accent) 30%,transparent);padding:2px 8px;border-radius:6px} .hd .count{margin-left:auto;font:12px ui-monospace,monospace;color:var(--dim2)}+ .hd .tools{display:flex;gap:6px;align-self:center}+ .tbtn{font:600 10.5px ui-monospace,monospace;letter-spacing:.03em;color:var(--dim);background:var(--panel);+ border:1px solid var(--line);border-radius:6px;padding:3px 8px;cursor:pointer;transition:color .12s,border-color .12s,background .12s;white-space:nowrap}+ .tbtn:hover{color:var(--text);border-color:var(--line2)}+ .tbtn.ok{color:var(--accent);border-color:color-mix(in srgb,var(--accent) 45%,transparent)} .body{display:grid;grid-template-columns:minmax(300px,1fr) minmax(220px,1fr);gap:20px;align-items:start} @media(max-width:620px){.body{grid-template-columns:1fr}} .readout{height:24px;display:flex;align-items:center;gap:9px;font:600 13px ui-monospace,monospace;color:var(--dim);padding:0 4px}
readme.html+37−1@@ -364,7 +364,7 @@ h2 .anchor:hover{opacity:1;color:var(--teal)} const heading = this.getAttribute('heading') || d.mpn || ''; root.innerHTML = `<style>${this._css()}</style> <div class="card">- ${heading ? `<div class="hd"><span class="mpn">${esc(heading)}</span>${d.package ? `<span class="pkg">${esc(d.package)}</span>` : ''}<span class="count">${pins.length} pins</span></div>` : ''}+ <div class="hd">${heading ? `<span class="mpn">${esc(heading)}</span>` : ''}${d.package ? `<span class="pkg">${esc(d.package)}</span>` : ''}<span class="count">${pins.length} pins</span><span class="tools"><button class="tbtn" data-act="copy" title="Copy the pin map as JSON">copy</button><button class="tbtn" data-act="dl" title="Download the pin map as JSON">JSON ↓</button></span></div> <div class="body"> <div class="diagram"> <div class="readout" data-readout>hover a pin →</div>@@ -438,6 +438,35 @@ h2 .anchor:hover{opacity:1;color:var(--teal)} /** Public: drive the highlight from outside (e.g. a linked footprint/symbol widget). */ highlight(n) { this._set(n, false); } + /** Public: the canonical pin map as a plain object — the structured handle for code/AI.+ * Types are resolved (inferred filled in) so consumers get a complete map. No fabricated fields. */+ toJSON() {+ const d = this._data || {};+ const pins = (Array.isArray(d.pins) ? d.pins : [])+ .map(p => ({ number: p.number, name: p.name, type: (p.type || inferType(p.name)).toLowerCase(), ...(p.xy ? { xy: p.xy } : {}) }))+ .sort((a, b) => (Number(a.number) - Number(b.number)) || String(a.number).localeCompare(String(b.number)));+ const out = {};+ if (d.mpn) out.mpn = d.mpn;+ if (d.package) out.package = d.package;+ out.pins = pins;+ return out;+ }++ _exportJson() { return JSON.stringify(this.toJSON(), null, 2); }+ _exportName() { const d = this._data || {}; return ((d.mpn || 'pinout') + (d.package ? '-' + d.package : '')).replace(/[^\w.-]+/g, '_') + '.pins.json'; }+ _download() {+ const blob = new Blob([this._exportJson()], { type: 'application/json' });+ const url = URL.createObjectURL(blob), a = document.createElement('a');+ a.href = url; a.download = this._exportName(); document.body.appendChild(a); a.click();+ a.remove(); setTimeout(() => URL.revokeObjectURL(url), 1000);+ }+ async _copy(btn) {+ const txt = this._exportJson(), done = () => { if (!btn) return; const o = btn.textContent; btn.textContent = 'copied'; btn.classList.add('ok'); setTimeout(() => { btn.textContent = o; btn.classList.remove('ok'); }, 1100); };+ try { await navigator.clipboard.writeText(txt); done(); return; } catch (e) { /* sandboxed / denied — fall back */ }+ try { const ta = document.createElement('textarea'); ta.value = txt; ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); done(); }+ catch (e2) { this._download(); } // last resort: hand them the file+ }+ // adom-lbr shared cross-highlight bus: BroadcastChannel('lbr-hover') + parent relay. _busInit() { this._self = 'w' + Math.random().toString(36).slice(2);@@ -456,6 +485,8 @@ h2 .anchor:hover{opacity:1;color:var(--teal)} _wire(pins) { this._pinsRendered = pins; const root = this.shadowRoot;+ const dl = root.querySelector('.tbtn[data-act="dl"]'); if (dl) dl.onclick = () => this._download();+ const cp = root.querySelector('.tbtn[data-act="copy"]'); if (cp) cp.onclick = () => this._copy(cp); root.querySelectorAll('tr[data-pin]').forEach(r => { r.onmouseenter = () => this._set(r.dataset.pin, true); r.onmouseleave = () => this._set('', true); }); root.querySelectorAll('svg .pin[data-pin]').forEach(g => { g.onmouseenter = () => this._set(g.dataset.pin, true); g.onmouseleave = () => this._set('', true); }); // Match the pin table's height to the chip diagram column so it fills the@@ -483,6 +514,11 @@ h2 .anchor:hover{opacity:1;color:var(--teal)} .hd .mpn{font:700 17px ui-monospace,'SF Mono',Menlo,monospace;letter-spacing:-.2px} .hd .pkg{font:600 11px ui-monospace,monospace;color:var(--accent);background:color-mix(in srgb,var(--accent) 12%,transparent);border:1px solid color-mix(in srgb,var(--accent) 30%,transparent);padding:2px 8px;border-radius:6px} .hd .count{margin-left:auto;font:12px ui-monospace,monospace;color:var(--dim2)}+ .hd .tools{display:flex;gap:6px;align-self:center}+ .tbtn{font:600 10.5px ui-monospace,monospace;letter-spacing:.03em;color:var(--dim);background:var(--panel);+ border:1px solid var(--line);border-radius:6px;padding:3px 8px;cursor:pointer;transition:color .12s,border-color .12s,background .12s;white-space:nowrap}+ .tbtn:hover{color:var(--text);border-color:var(--line2)}+ .tbtn.ok{color:var(--accent);border-color:color-mix(in srgb,var(--accent) 45%,transparent)} .body{display:grid;grid-template-columns:minmax(300px,1fr) minmax(220px,1fr);gap:20px;align-items:start} @media(max-width:620px){.body{grid-template-columns:1fr}} .readout{height:24px;display:flex;align-items:center;gap:9px;font:600 13px ui-monospace,monospace;color:var(--dim);padding:0 4px}