← Commit history

Publish 1.1.4

Ray ·04a7342bc6 ·2mo ago ·parent df406b9
3 files changed +40−4
package.json+1−1
@@ -2,7 +2,7 @@   "slug": "component-widgets",   "title": "Component Widgets",   "type": "app",-  "version": "1.1.3",+  "version": "1.1.4",   "description": "Reusable, dependency-free web-component widgets to drop into any component or molecule page: <adom-pinout> (datasheet-style pin diagram + synced table), <adom-heatsink-via> (exposed-pad thermal via array, only when the part has an EP), <adom-molecule> (ganged part set + nets), and <adom-chip-thumbnail> (rendered part image). Each is one custom element, self-contained (Shadow DOM, no deps), theme-aware (light + dark), fed by inline JSON or el.data, and all on one lbr-hover cross-highlight bus so a pin hovered in one lights the same pin everywhere. Symbol, footprint, and 3D stay with adom-lbr; the datasheet renders via the native viewer. Installs the widget JS, iframe-embeddable viewers, example data, and an AI skill to ~/.claude/skills/component-widgets. Live reference page renders them all as a readme.html.",   "license": "MIT",   "dependencies": {},
page.json+2−2
@@ -3,8 +3,8 @@   "slug": "component-widgets",   "type": "app",   "title": "Component Widgets",-  "brief": "One home for the reusable, readme-instantiable component/molecule widgets (pinout, heat-sink via, chip thumbnail, molecule) \u2014 self-contained web components on the shared adom-lbr hover bus.",-  "version": "1.1.1",+  "brief": "One home for the reusable, readme-instantiable component/molecule widgets (pinout, heat-sink via, chip thumbnail, molecule) — self-contained web components on the shared adom-lbr hover bus.",+  "version": "1.1.4",   "visibility": "private",   "author": {     "name": "Ray",
skills/component-widgets/widgets/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&nbsp;↓</button></span></div>           <div class="body">             <div class="diagram">               <div class="readout" data-readout>hover a pin&nbsp;→</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}