← Commit history
README.md+24−39
@@ -1,56 +1,41 @@ # Component Widgets -One home for the reusable widgets you can drop into any component or molecule **readme HTML**. Each is a self-contained web component — no build, no dependencies — instantiated with one tag.+One home for the reusable widgets you can drop into any component or molecule **readme**. Each is a self-contained web component — no build, no dependencies — with a matching **iframe viewer** for embedding in wiki readmes. -**Shared contract:** feed each widget three ways — `src="…json"`, an inline `<script type="application/json">`, or `el.data = {…}` from JS. Shadow-DOM isolated, light + dark. They all speak the **adom-lbr cross-highlight bus** (`BroadcastChannel('lbr-hover')` + parent relay), so any widgets on a page light up together on hover — pinout, footprint, 3D, all keyed on pin number.+**Shared contract:** feed each widget via `src="…json"`, an inline `<script type="application/json">`, or `el.data = {…}`. Shadow-DOM isolated, light + dark. They all speak the **adom-lbr cross-highlight bus** (`BroadcastChannel('lbr-hover')` + parent relay), so any widgets on a page light up together on hover — pinout, footprint, 3D, all keyed on pin number. -Assets (load once): [`pinout-widget.js`](files/pinout-widget.js) · [`heatsink-via-widget.js`](files/heatsink-via-widget.js) · [`chip-thumbnail-widget.js`](files/chip-thumbnail-widget.js) · [`molecule-widget.js`](files/molecule-widget.js)+## Embedding in a wiki readme -## The widgets--### `<adom-pinout>` — pinout diagram-Datasheet-style chip diagram: pins on the sides, colored by type, exposed pad in the center.-```html-<adom-pinout src="stm32.json"></adom-pinout>-<!-- { mpn, package, pins:[{number,name,type?,xy?}] } · degrades rich xy → inferred quad/dual -->-```+The readme sandbox embeds via **iframes** (the same pattern as the adom-lbr slots), not inline scripts. Each widget ships a `*-viewer.html` that takes `?src=` / `?part=` (or `postMessage`) and auto-grows the host via `readme-height`: -### `<adom-heatsink-via>` — exposed-pad thermal vias · **new**-The recommended thermal-via array under the exposed pad — **only renders when the part has an exposed pad**, otherwise an empty state. ```html-<adom-heatsink-via><script type="application/json">-{ "mpn":"MCP23017", "package":"QFN-28-1EP…EP4.25x4.25mm" }   <!-- EP from name / pads / explicit ep:{} -->-</script></adom-heatsink-via>+<iframe src="files/pinout-viewer.html?src=pinout-stm32f103.json" style="width:100%;border:0"></iframe>+<iframe src="files/heatsink-viewer.html?part=MCP23017" style="width:100%;border:0"></iframe> ``` -### `<adom-chip-thumbnail>` — rendered part image · **new**-```html-<adom-chip-thumbnail src="thumb.png" mpn="74HC00" package="SOIC-14"></adom-chip-thumbnail>-```+Outside the wiki (any HTML page) you can also use the raw tag: `<script src="files/pinout-widget.js">` then `<adom-pinout>`. -### `<adom-molecule>` — ganged molecule · **new**-A molecule as its ganged part set + how they connect. Hover a net to light its parts; hover a part to light its nets.-```html-<adom-molecule><script type="application/json">-{ "name":"…", "components":[{ref,mpn,value?,package?,type?}], "nets":[{name,pins:[{ref,pin}]}] }-</script></adom-molecule>-```+## The widgets -### Symbol · Footprint · 3D — **adom-lbr**-Provided by **adom-lbr**'s viewers (canonical adom-lbr JSON, footprint layer stackup KiCad+Altium, first-class pin table, per-lead 3D highlight). Embed the adom-lbr slot/viewer — don't reimplement. They already share the `lbr-hover` bus.+| Widget | Tag | Viewer | |+|---|---|---|---|+| **Pinout** | `<adom-pinout>` | `pinout-viewer.html` | datasheet-style chip diagram, EP centered |+| **Heat-sink via** · new | `<adom-heatsink-via>` | `heatsink-viewer.html` | thermal-via array under the EP; **only when the part has one** |+| **Chip thumbnail** · new | `<adom-chip-thumbnail>` | `thumbnail-viewer.html` | rendered part image (glb2png) |+| **Ganged molecule** · new | `<adom-molecule>` | `molecule-viewer.html` | a molecule's part set + nets; hover a net → its parts |+| **Symbol · Footprint · 3D** | — | **adom-lbr** | its viewers own these (layer stackup, pin table, per-lead 3D). Embed, don't rebuild. |+| **Datasheet** | — | native viewer | push the PDF; renders inline with pop-out | -### Datasheet — native viewer-Push the part's PDF; the native viewer renders it inline with pop-out. No widget to build.+Data shapes: pinout `{mpn,package,pins:[{number,name,type?,xy?}]}` · heat-sink `{mpn,package}` (EP from name) / `{pads:[{number,x_mm,y_mm}]}` / `{ep:{w_mm,h_mm}}` · molecule `{name,components:[{ref,mpn,value?,package?,type?}],nets:[{name,pins:[{ref,pin}]}]}` · thumbnail `{src,mpn,package}`.  --- -## Live (dogfood)+## Live++<iframe src="files/pinout-viewer.html?src=pinout-stm32f103.json&theme=dark" style="width:100%;max-width:700px;border:0" height="470" scrolling="no"></iframe>++<iframe src="files/heatsink-viewer.html?src=heatsink-mcp23017.json&theme=dark" style="width:100%;max-width:700px;border:0" height="300" scrolling="no"></iframe> -<div>-<script src="files/pinout-widget.js"></script>-<script src="files/heatsink-via-widget.js"></script>-<adom-pinout theme="dark"><script type="application/json">{"mpn":"NE555","package":"DIP-8","pins":[{"number":1,"name":"GND"},{"number":2,"name":"TRIG"},{"number":3,"name":"OUT"},{"number":4,"name":"RESET"},{"number":5,"name":"CTRL"},{"number":6,"name":"THRES"},{"number":7,"name":"DISCH"},{"number":8,"name":"VCC"}]}</script></adom-pinout>-<adom-heatsink-via theme="dark"><script type="application/json">{"mpn":"MCP23017","package":"QFN-28-1EP_6x6mm_P0.65mm_EP4.25x4.25mm","ep":{"w_mm":4.25,"h_mm":4.25,"number":29}}</script></adom-heatsink-via>-</div>+<iframe src="files/molecule-viewer.html?src=mol-stm32f103.json&theme=dark" style="width:100%;max-width:700px;border:0" height="300" scrolling="no"></iframe> -*If the block above renders as two live widgets, readme-instantiation works end-to-end. If not, the readme sandbox's CSP needs to allow page-asset scripts (a wiki-side toggle).*+<iframe src="files/thumbnail-viewer.html?src=thumb-74hc00.png&mpn=74HC00&package=SOIC-14&theme=dark" style="width:230px;border:0" height="250" scrolling="no"></iframe>
files/heatsink-mcp23017.jsonadded+1
@@ -0,0 +1 @@+{"mpn":"MCP23017","package":"QFN-28-1EP_6x6mm_P0.65mm_EP4.25x4.25mm","ep":{"w_mm":4.25,"h_mm":4.25,"number":29}}
files/heatsink-viewer.htmladded+32
@@ -0,0 +1,32 @@+<!doctype html>+<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">+<title>Heat-sink via viewer</title>+<style>html,body{margin:0;background:transparent}adom-heatsink-via{display:block}.msg{padding:22px;color:#8b97ac;font:13px/1.6 ui-monospace,monospace}</style></head>+<body>+<!-- Embeddable heat-sink-via viewer. ?part=<mpn> (Reef pads) · ?src=<json> · postMessage {type:'heatsink',data} · ?theme= -->+<adom-heatsink-via id="w"></adom-heatsink-via>+<div class="msg" id="msg" hidden></div>+<script src="heatsink-via-widget.js"></script>+<script>+const REEF='https://reef-k0fjw2gwsaek.adom.cloud';+const w=document.getElementById('w'), msg=document.getElementById('msg'), P=new URLSearchParams(location.search);+const show=t=>{msg.hidden=false; msg.textContent=t;};+async function loadPart(key){+  try{+    const d=await (await fetch(REEF+'/reef/page/'+encodeURIComponent(key))).json();+    if(!d.resolved) return show('no match for '+key);+    const v=d.variants[d.primary||0], pkgKey=(v.packageOptions[0]||{}).package_key, pkgClass=(v.packageOptions[0]||{}).package_class||'';+    const pk=await (await fetch(REEF+'/api/package/'+encodeURIComponent(pkgKey))).json();+    w.data={ mpn:v.name, package:pk.package_key||pkgClass, pads:Array.isArray(pk.pads)?pk.pads:[] };+  }catch(e){ show('error: '+e); }+}+async function loadSrc(url){ try{ w.data=await (await fetch(url)).json(); }catch(e){ show('error: '+e); } }+if(P.get('theme')) w.setAttribute('theme', P.get('theme'));+if(P.get('part')) loadPart(P.get('part'));+else if(P.get('src')) loadSrc(P.get('src'));+else show('pass ?part=<mpn> or ?src=<json-url>, or postMessage {type:"heatsink", data:{…}}');+addEventListener('message', e=>{ const m=e.data||{}; if(m.type==='heatsink' && m.data){ msg.hidden=true; w.data=m.data; } });+function ph(){ try{ parent.postMessage({type:'readme-height', height:Math.max(120,Math.min(16000,document.body.scrollHeight||200))}, '*'); }catch(_){} }+addEventListener('load',ph); setTimeout(ph,300); if('ResizeObserver' in window) new ResizeObserver(ph).observe(document.body);+</script>+</body></html>
files/mol-stm32f103.jsonadded+1
@@ -0,0 +1 @@+{"name": "STM32F103 \u00b7 minimum support", "components": [{"ref": "U1", "mpn": "STM32F103C8T6", "value": "STM32F103C8", "package": "LQFP-48", "type": "mcu"}, {"ref": "C1", "value": "100nF", "package": "0402", "type": "cap"}, {"ref": "C2", "value": "100nF", "package": "0402", "type": "cap"}, {"ref": "C3", "value": "100nF", "package": "0402", "type": "cap"}, {"ref": "C4", "value": "4.7uF", "package": "0805", "type": "cap"}, {"ref": "C5", "value": "10nF", "package": "0402", "type": "cap"}, {"ref": "C6", "value": "1uF", "package": "0603", "type": "cap"}, {"ref": "Y1", "value": "8MHz", "package": "HC-49", "type": "xtal"}, {"ref": "C7", "value": "20pF", "package": "0402", "type": "cap"}, {"ref": "C8", "value": "20pF", "package": "0402", "type": "cap"}, {"ref": "R1", "value": "10k", "package": "0402", "type": "res"}, {"ref": "C9", "value": "100nF", "package": "0402", "type": "cap"}], "nets": [{"name": "VDD", "pins": [{"ref": "U1", "pin": "VDD_1"}, {"ref": "U1", "pin": "VDD_2"}, {"ref": "U1", "pin": "VDD_3"}, {"ref": "C1", "pin": "1"}, {"ref": "C2", "pin": "1"}, {"ref": "C3", "pin": "1"}, {"ref": "C4", "pin": "1"}]}, {"name": "VDDA", "pins": [{"ref": "U1", "pin": "VDDA"}, {"ref": "C5", "pin": "1"}, {"ref": "C6", "pin": "1"}]}, {"name": "OSC_IN", "pins": [{"ref": "U1", "pin": "PD0"}, {"ref": "Y1", "pin": "1"}, {"ref": "C7", "pin": "1"}]}, {"name": "OSC_OUT", "pins": [{"ref": "U1", "pin": "PD1"}, {"ref": "Y1", "pin": "2"}, {"ref": "C8", "pin": "1"}]}, {"name": "NRST", "pins": [{"ref": "U1", "pin": "NRST"}, {"ref": "C9", "pin": "1"}]}, {"name": "BOOT0", "pins": [{"ref": "U1", "pin": "BOOT0"}, {"ref": "R1", "pin": "1"}]}, {"name": "GND", "pins": [{"ref": "U1", "pin": "VSS_1"}, {"ref": "U1", "pin": "VSSA"}, {"ref": "C1", "pin": "2"}, {"ref": "C4", "pin": "2"}, {"ref": "C9", "pin": "2"}, {"ref": "R1", "pin": "2"}]}]}\ No newline at end of file
files/molecule-viewer.htmladded+21
@@ -0,0 +1,21 @@+<!doctype html>+<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">+<title>Molecule viewer</title>+<style>html,body{margin:0;background:transparent}adom-molecule{display:block}.msg{padding:22px;color:#8b97ac;font:13px/1.6 ui-monospace,monospace}</style></head>+<body>+<!-- Embeddable ganged-molecule viewer. ?src=<json> · postMessage {type:'molecule',data} · ?theme= -->+<adom-molecule id="w"></adom-molecule>+<div class="msg" id="msg" hidden></div>+<script src="molecule-widget.js"></script>+<script>+const w=document.getElementById('w'), msg=document.getElementById('msg'), P=new URLSearchParams(location.search);+const show=t=>{msg.hidden=false; msg.textContent=t;};+async function loadSrc(url){ try{ w.data=await (await fetch(url)).json(); }catch(e){ show('error: '+e); } }+if(P.get('theme')) w.setAttribute('theme', P.get('theme'));+if(P.get('src')) loadSrc(P.get('src'));+else show('pass ?src=<json-url>, or postMessage {type:"molecule", data:{…}}');+addEventListener('message', e=>{ const m=e.data||{}; if(m.type==='molecule' && m.data){ msg.hidden=true; w.data=m.data; } });+function ph(){ try{ parent.postMessage({type:'readme-height', height:Math.max(120,Math.min(16000,document.body.scrollHeight||200))}, '*'); }catch(_){} }+addEventListener('load',ph); setTimeout(ph,300); if('ResizeObserver' in window) new ResizeObserver(ph).observe(document.body);+</script>+</body></html>
files/pinout-viewer.htmladded+54
@@ -0,0 +1,54 @@+<!doctype html>+<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">+<title>Pinout viewer</title>+<style>+  html,body{margin:0;background:transparent}+  adom-pinout{display:block}+  .msg{padding:22px;color:#8b97ac;font:13px/1.6 ui-monospace,'JetBrains Mono',monospace}+</style></head>+<body>+<!--+  Embeddable per-slot pinout viewer (wiki lbr-slot / modern-LBR pattern).+  Drive it three ways:+    ?part=<mpn|symbolKey>   → fetch Reef /reef/page and render+    ?src=<json-url>         → fetch the pin-data JSON and render+    postMessage {type:'pinout', data:{...}}   → host feeds data directly+  Cross-slot linking:+    posts   {type:'pinout-pinhover', pin}  to the parent on hover+    accepts {type:'pinout-highlight', pin}  from the parent to drive the highlight+  ?theme=light|dark+-->+<adom-pinout id="w"></adom-pinout>+<div class="msg" id="msg" hidden></div>+<script src="pinout-widget.js"></script>+<script>+const REEF='https://reef-k0fjw2gwsaek.adom.cloud';+const w=document.getElementById('w'), msg=document.getElementById('msg'), P=new URLSearchParams(location.search);+const show=t=>{msg.hidden=false; msg.textContent=t;};+async function loadPart(key){+  try{+    const d=await (await fetch(REEF+'/reef/page/'+encodeURIComponent(key))).json();+    if(!d.resolved) return show('no match for '+key);+    const v=d.variants[d.primary||0];+    w.data={ mpn:v.name, package:(v.packageOptions[0]||{}).package_class||'', pins:(v.pinList||[]).map(p=>({number:p.number,name:p.name})) };+  }catch(e){ show('error: '+e); }+}+async function loadSrc(url){ try{ w.data=await (await fetch(url)).json(); }catch(e){ show('error: '+e); } }++if(P.get('theme')) w.setAttribute('theme', P.get('theme'));+if(P.get('part')) loadPart(P.get('part'));+else if(P.get('src')) loadSrc(P.get('src'));+else show('pass ?part=<mpn> or ?src=<json-url>, or postMessage {type:"pinout", data:{…}}');++// cross-slot linking (symbol ↔ footprint ↔ 3D ↔ pinout)+addEventListener('message', e=>{+  const m=e.data||{};+  if(m.type==='pinout' && m.data){ msg.hidden=true; w.data=m.data; }+  else if(m.type==='pinout-highlight' && w.highlight){ w.highlight(m.pin); }+});+w.addEventListener('pinhover', e=>{ try{ parent.postMessage({type:'pinout-pinhover', pin:e.detail?e.detail.number:null}, '*'); }catch(_){} });+// auto-grow the host readme iframe+function ph(){ try{ parent.postMessage({type:'readme-height', height:Math.max(120,Math.min(16000,document.body.scrollHeight||200))}, '*'); }catch(_){} }+addEventListener('load',ph); setTimeout(ph,300); if('ResizeObserver' in window) new ResizeObserver(ph).observe(document.body);+</script>+</body></html>
files/thumbnail-viewer.htmladded+19
@@ -0,0 +1,19 @@+<!doctype html>+<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">+<title>Chip thumbnail viewer</title>+<style>html,body{margin:0;background:transparent}adom-chip-thumbnail{display:block}.msg{padding:22px;color:#8b97ac;font:13px/1.6 ui-monospace,monospace}</style></head>+<body>+<!-- Embeddable chip-thumbnail viewer. ?src=<img>&mpn=&package= · postMessage {type:'thumbnail',data} · ?theme= -->+<adom-chip-thumbnail id="w"></adom-chip-thumbnail>+<div class="msg" id="msg" hidden></div>+<script src="chip-thumbnail-widget.js"></script>+<script>+const w=document.getElementById('w'), msg=document.getElementById('msg'), P=new URLSearchParams(location.search);+if(P.get('theme')) w.setAttribute('theme', P.get('theme'));+if(P.get('src')){ w.setAttribute('src', P.get('src')); if(P.get('mpn')) w.setAttribute('mpn', P.get('mpn')); if(P.get('package')) w.setAttribute('package', P.get('package')); }+else { msg.hidden=false; msg.textContent='pass ?src=<image-url>&mpn=&package=, or postMessage {type:"thumbnail", data:{…}}'; }+addEventListener('message', e=>{ const m=e.data||{}; if(m.type==='thumbnail' && m.data){ msg.hidden=true; w.data=m.data; } });+function ph(){ try{ parent.postMessage({type:'readme-height', height:Math.max(120,Math.min(16000,document.body.scrollHeight||200))}, '*'); }catch(_){} }+addEventListener('load',ph); setTimeout(ph,400); if('ResizeObserver' in window) new ResizeObserver(ph).observe(document.body);+</script>+</body></html>