@wafertools/wafermap 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/AGENTS.md +6 -6
  2. package/CHANGELOG.md +212 -0
  3. package/README.md +5 -5
  4. package/dist/packages/canvas-adapter/chartPopulation.d.ts +15 -10
  5. package/dist/packages/canvas-adapter/chartPopulation.js +1 -1
  6. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +7 -0
  7. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
  8. package/dist/packages/canvas-adapter/charts/binCluster.d.ts +7 -1
  9. package/dist/packages/canvas-adapter/charts/binCluster.js +1 -1
  10. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
  11. package/dist/packages/canvas-adapter/charts/capability.js +1 -1
  12. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +55 -3
  13. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
  14. package/dist/packages/canvas-adapter/charts/correlation.js +1 -2
  15. package/dist/packages/canvas-adapter/charts/groupedBarPlot.d.ts +2 -0
  16. package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
  17. package/dist/packages/canvas-adapter/charts/histogram.d.ts +16 -0
  18. package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
  19. package/dist/packages/canvas-adapter/charts/palette.d.ts +2 -0
  20. package/dist/packages/canvas-adapter/charts/palette.js +1 -1
  21. package/dist/packages/canvas-adapter/charts/plotChart.d.ts +27 -0
  22. package/dist/packages/canvas-adapter/charts/plotChart.js +1 -0
  23. package/dist/packages/canvas-adapter/charts/pointInteractions.d.ts +45 -0
  24. package/dist/packages/canvas-adapter/charts/pointInteractions.js +1 -0
  25. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +2 -0
  26. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
  27. package/dist/packages/canvas-adapter/charts/scatter.d.ts +19 -0
  28. package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
  29. package/dist/packages/canvas-adapter/charts/sweep.d.ts +12 -0
  30. package/dist/packages/canvas-adapter/charts/sweep.js +1 -1
  31. package/dist/packages/canvas-adapter/charts/testPassRate.d.ts +7 -0
  32. package/dist/packages/canvas-adapter/charts/testPassRate.js +1 -1
  33. package/dist/packages/canvas-adapter/charts/trend.d.ts +11 -0
  34. package/dist/packages/canvas-adapter/charts/trend.js +1 -1
  35. package/dist/packages/canvas-adapter/confirmDialog.d.ts +13 -0
  36. package/dist/packages/canvas-adapter/confirmDialog.js +1 -0
  37. package/dist/packages/canvas-adapter/dataTab.d.ts +131 -0
  38. package/dist/packages/canvas-adapter/dataTab.js +1 -0
  39. package/dist/packages/canvas-adapter/dieList.d.ts +39 -4
  40. package/dist/packages/canvas-adapter/dieList.js +6 -7
  41. package/dist/packages/canvas-adapter/drilldown.d.ts +20 -3
  42. package/dist/packages/canvas-adapter/drilldown.js +1 -1
  43. package/dist/packages/canvas-adapter/guideWindow.d.ts +96 -0
  44. package/dist/packages/canvas-adapter/guideWindow.js +1 -0
  45. package/dist/packages/canvas-adapter/identityHeader.d.ts +16 -0
  46. package/dist/packages/canvas-adapter/identityHeader.js +1 -1
  47. package/dist/packages/canvas-adapter/index.d.ts +3 -3
  48. package/dist/packages/canvas-adapter/index.js +1 -1
  49. package/dist/packages/canvas-adapter/insightsTab.d.ts +73 -54
  50. package/dist/packages/canvas-adapter/insightsTab.js +1 -1
  51. package/dist/packages/canvas-adapter/maplessSummary.d.ts +2 -2
  52. package/dist/packages/canvas-adapter/plotEditor.d.ts +29 -0
  53. package/dist/packages/canvas-adapter/plotEditor.js +1 -0
  54. package/dist/packages/canvas-adapter/plotItems.d.ts +32 -0
  55. package/dist/packages/canvas-adapter/plotItems.js +1 -0
  56. package/dist/packages/canvas-adapter/plotModal.d.ts +30 -0
  57. package/dist/packages/canvas-adapter/plotModal.js +1 -0
  58. package/dist/packages/canvas-adapter/plotStore.d.ts +52 -0
  59. package/dist/packages/canvas-adapter/plotStore.js +1 -0
  60. package/dist/packages/canvas-adapter/plotTab.d.ts +30 -0
  61. package/dist/packages/canvas-adapter/plotTab.js +1 -0
  62. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +3 -2
  63. package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
  64. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +21 -2
  65. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
  66. package/dist/packages/canvas-adapter/summaryPanel.d.ts +27 -8
  67. package/dist/packages/canvas-adapter/summaryPanel.js +2 -4
  68. package/dist/packages/canvas-adapter/summaryRail.d.ts +7 -0
  69. package/dist/packages/canvas-adapter/summaryRail.js +1 -0
  70. package/dist/packages/canvas-adapter/sweepEditor.d.ts +17 -0
  71. package/dist/packages/canvas-adapter/sweepEditor.js +1 -0
  72. package/dist/packages/canvas-adapter/tableExport.d.ts +22 -0
  73. package/dist/packages/canvas-adapter/tableExport.js +3 -0
  74. package/dist/packages/canvas-adapter/toolbar.d.ts +25 -97
  75. package/dist/packages/canvas-adapter/toolbar.js +2 -2
  76. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
  77. package/dist/packages/canvas-adapter/userGuideHtml.js +337 -42
  78. package/dist/packages/canvas-adapter/version.d.ts +2 -2
  79. package/dist/packages/canvas-adapter/version.js +1 -1
  80. package/dist/packages/canvas-adapter/viewSwitch.d.ts +13 -0
  81. package/dist/packages/canvas-adapter/viewSwitch.js +1 -0
  82. package/dist/packages/canvas-adapter/virtualTable.d.ts +44 -0
  83. package/dist/packages/canvas-adapter/virtualTable.js +1 -0
  84. package/dist/packages/core/classify.d.ts +3 -2
  85. package/dist/packages/core/classify.js +1 -1
  86. package/dist/packages/core/passBins.d.ts +17 -4
  87. package/dist/packages/core/passBins.js +1 -1
  88. package/dist/packages/core/ringCount.d.ts +22 -0
  89. package/dist/packages/core/ringCount.js +1 -0
  90. package/dist/packages/core/tableCsv.d.ts +43 -0
  91. package/dist/packages/core/tableCsv.js +3 -0
  92. package/dist/packages/renderer/binColors.d.ts +6 -6
  93. package/dist/packages/renderer/binColors.js +1 -1
  94. package/dist/packages/renderer/buildView.d.ts +1 -1
  95. package/dist/packages/renderer/buildView.js +1 -1
  96. package/dist/packages/renderer/buildWaferMap.js +1 -1
  97. package/dist/packages/stats/analyzeWaferLot.js +1 -1
  98. package/dist/packages/stats/analyzeWaferMap.js +1 -1
  99. package/dist/packages/stats/binPareto.d.ts +1 -1
  100. package/dist/packages/stats/binPareto.js +1 -1
  101. package/dist/packages/stats/histogram.d.ts +27 -0
  102. package/dist/packages/stats/histogram.js +1 -1
  103. package/dist/packages/stats/index.d.ts +2 -0
  104. package/dist/packages/stats/index.js +1 -1
  105. package/dist/packages/stats/lotDrift.js +1 -1
  106. package/dist/packages/stats/lotPattern.js +1 -1
  107. package/dist/packages/stats/math.d.ts +9 -6
  108. package/dist/packages/stats/math.js +1 -1
  109. package/dist/packages/stats/patternClassification.d.ts +1 -1
  110. package/dist/packages/stats/patternClassification.js +1 -1
  111. package/dist/packages/stats/plotData.d.ts +258 -0
  112. package/dist/packages/stats/plotData.js +1 -0
  113. package/dist/packages/stats/plotId.d.ts +3 -0
  114. package/dist/packages/stats/plotId.js +1 -0
  115. package/dist/packages/stats/plotSpec.d.ts +119 -0
  116. package/dist/packages/stats/plotSpec.js +4 -0
  117. package/dist/packages/stats/presentation.d.ts +2 -0
  118. package/dist/packages/stats/presentation.js +1 -1
  119. package/dist/packages/stats/regions.d.ts +6 -0
  120. package/dist/packages/stats/regions.js +1 -1
  121. package/dist/packages/stats/renderSummaryReport.d.ts +8 -3
  122. package/dist/packages/stats/renderSummaryReport.js +15 -15
  123. package/dist/packages/stats/reportHtml.d.ts +1 -1
  124. package/dist/packages/stats/reportHtml.js +36 -29
  125. package/dist/packages/stats/scatter.d.ts +6 -0
  126. package/dist/packages/stats/scatter.js +1 -1
  127. package/dist/packages/stats/sweep.d.ts +37 -1
  128. package/dist/packages/stats/sweep.js +1 -1
  129. package/dist/packages/stats/sweepText.d.ts +14 -0
  130. package/dist/packages/stats/sweepText.js +1 -0
  131. package/dist/packages/stats/sweepXFromName.d.ts +14 -0
  132. package/dist/packages/stats/sweepXFromName.js +1 -1
  133. package/dist/packages/stats/synthesis.d.ts +14 -4
  134. package/dist/packages/stats/synthesis.js +1 -1
  135. package/dist/packages/stats/testPassRate.d.ts +6 -0
  136. package/dist/packages/stats/testPassRate.js +1 -1
  137. package/dist/packages/stats/trend.js +1 -1
  138. package/dist/packages/stats/types.d.ts +1 -1
  139. package/dist/packages/stats/yield.d.ts +15 -6
  140. package/dist/packages/stats/yield.js +1 -1
  141. package/llms.txt +15 -3
  142. package/package.json +3 -3
@@ -1,13 +1,12 @@
1
- import{hasPosition as F,isPositionedDie as V}from"../core/dies.js";import{testsPresent as G,testValue as H}from"../core/dieTable.js";import{classifyDie as Y}from"../core/classify.js";import{isParametricTest as _,getTestPassStatus as S}from"../renderer/buildWaferMap.js";import{resolveMetadataColumns as U}from"../stats/metadataColumns.js";import{LEADING as N,TRACKING as X,wireControlHover as Q,controlStyle as q,SPACE as J,RADIUS as P,FONT as g,CLR as d,saveTextFile as Z}from"./toolbar.js";import{csvField as k}from"./summaryPanel.js";import{fmt as j}from"../renderer/fmt.js";import{testLabel as E,markedTestLabel as M,derivedFields as ee,derivedKeyText as te,derivedTestSource as ne,isDerivedTest as ie,DERIVED_KEY as le}from"../renderer/testLabel.js";const L=(t,a)=>a?`${t} (${a})`:t,ae=5e4,z=new WeakSet;function re(t){if(z.has(t))return;z.add(t);const a=t.createElement("style");a.textContent=`
1
+ import{requireRingCount as F}from"../core/ringCount.js";import{hasPosition as T,isPositionedDie as M}from"../core/dies.js";import{testsPresent as K,testValue as I}from"../core/dieTable.js";import{classifyDie as z}from"../core/classify.js";import{isParametricTest as B,getTestPassStatus as y}from"../renderer/buildWaferMap.js";import{resolveMetadataColumns as O}from"../stats/metadataColumns.js";import{LEADING as N,TRACKING as W,wireControlHover as j,controlStyle as V,SPACE as G,RADIUS as P,FONT as w,CLR as c}from"./toolbar.js";import{exportCsv as Y}from"./tableExport.js";import{fmt as _}from"../renderer/fmt.js";import{testLabel as L,markedTestLabel as H,derivedFields as U,derivedKeyText as X,derivedTestSource as q,isDerivedTest as Q,DERIVED_KEY as J}from"../renderer/testLabel.js";const $=(t,r)=>r?`${t} (${r})`:t,Z=5e4,k=new WeakSet;export function ensureStylesInjected(t){if(k.has(t))return;k.add(t);const r=t.createElement("style");r.textContent=`
2
2
  .wmap-dielist-table { width: 100%; border-collapse: collapse; font-size: 12px; font-variant-numeric: tabular-nums; }
3
3
  .wmap-dielist-th {
4
4
  text-align: left; padding: 4px 8px; position: sticky; top: 0;
5
- background: ${d.panelBg}; color: ${d.label}; font-weight: 600;
6
- border-bottom: 1px solid ${d.menuBorder}; white-space: nowrap;
5
+ background: ${c.panelBg}; color: ${c.label}; font-weight: 600;
6
+ border-bottom: 1px solid ${c.menuBorder}; white-space: nowrap;
7
7
  }
8
8
  .wmap-dielist-td {
9
- padding: 4px 8px; color: ${d.text};
10
- border-bottom: 1px solid ${d.menuBorder}; white-space: nowrap;
9
+ padding: 4px 8px; color: ${c.text};
10
+ border-bottom: 1px solid ${c.menuBorder}; white-space: nowrap;
11
11
  }
12
- `,t.head.appendChild(a)}function s(t,a,n,i){const r=t.createElement(a);return n&&Object.assign(r.style,n),i!==void 0&&(r.textContent=i),r}function oe(t){return F(t)?String(t.x):""}function se(t){return F(t)?String(t.y):""}function de(t,a){return a!==void 0?a.filter(n=>n.testNumber!==void 0):G(t,"values").map(n=>({testNumber:n,name:E(void 0,n)}))}function ce(t,a,n){if(!a)return;const i=new Map;for(const r of t){if(!V(r))continue;const u=a(r);u&&i.set(r,Y(r,u,{ringCount:n}))}return i.size>0?i:void 0}export function buildDieListSection(t,a,n={}){if(!t.length)return null;const i=n.ownerDocument??document;re(i);const r=de(t,a),u=n.maxRows??ae,C=Math.max(0,t.length-u),K=C>0?t.slice(0,u):t,m=s(i,"div",{display:"flex",flexDirection:"column",gap:J.md,flex:"1",minHeight:"0",minWidth:"0"}),v=s(i,"div",{display:"flex",alignItems:"center",justifyContent:"space-between",flexShrink:"0"});v.appendChild(s(i,"div",{fontSize:g.meta,fontWeight:"700",letterSpacing:X,textTransform:"uppercase",color:d.label},n.title??`Die list (${t.length})`));const h=s(i,"button",{fontSize:g.body,padding:"3px 8px",borderRadius:P.control,...q("outlined"),background:d.menuBg},"Export CSV");h.type="button",Q(h),v.appendChild(h),m.appendChild(v),n.note&&m.appendChild(s(i,"div",{fontSize:g.body,color:d.label,lineHeight:N.base,flexShrink:"0"},n.note));const w=ce(t,n.getWafer,n.ringCount??4),B=t.some(e=>e.edgeExcluded===!0),O=[...n.extraColumn?[n.extraColumn.label]:[],"X","Y","Ring","Quadrant","Edge excluded","Site","Hard bin","Soft bin",...r.map(e=>L(E(e,e.testNumber),e.unit))],{columns:W,truncatedKeys:x}=U({dies:t,waferMetadata:n.waferMetadata,metadataFields:n.metadataFields,dieKeys:n.metadataColumns,waferKeys:n.waferMetadataColumns==="none"?"none":void 0,waferPlacement:n.waferMetadataColumns??"csv",reservedLabels:O}),y=[...n.extraColumn?[{label:n.extraColumn.label,get:e=>n.extraColumn.get(e)??""}]:[],{label:"X",get:oe},{label:"Y",get:se},...w?[{label:"Ring",get:e=>{const l=w.get(e);return l?String(l.ring):""}},{label:"Quadrant",get:e=>w.get(e)?.quadrant??""}]:[],...B?[{label:"Edge excluded",get:e=>e.edgeExcluded?"Yes":"No"}]:[],{label:"Site",get:e=>e.siteNum!==void 0?String(e.siteNum):""},{label:"Hard bin",get:e=>e.hbin!==void 0?String(e.hbin):""},{label:"Soft bin",get:e=>e.sbin!==void 0?String(e.sbin):""},...r.map(e=>{const l=!_(e);return{label:L(M(e,e.testNumber),e.unit),...ie(e)?{csvLabel:`${L(E(e,e.testNumber),e.unit)} [derived from ${ne(e)??le}]`}:{},get:o=>{if(l){const p=S(o,e.testNumber,e);return p===void 0?"":p?"PASS":"FAIL"}const c=H(o,e.testNumber);if(c!==void 0)return j(c,e.unit);const b=S(o,e.testNumber,e);return b===void 0?"":b?"PASS":"FAIL"},csvGet:o=>{if(l){const p=S(o,e.testNumber,e);return p===void 0?"":p?"PASS":"FAIL"}const c=H(o,e.testNumber);if(c!==void 0)return j(c,void 0,"engineering");const b=S(o,e.testNumber,e);return b===void 0?"":b?"PASS":"FAIL"}}}),...W.map(e=>({label:e.label,get:e.get,csvOnly:e.csvOnly}))],$=y.filter(e=>!e.csvOnly),A=te(r.map(e=>({label:M(e,e.testNumber),...ee(e)})));A&&m.appendChild(s(i,"div",{fontSize:g.body,color:d.label,lineHeight:N.base,flexShrink:"0"},A));const D=s(i,"div",{overflow:"auto",flex:"1",minHeight:"0",minWidth:"0",border:`1px solid ${d.menuBorder}`,borderRadius:P.control,...n.maxHeight?{maxHeight:n.maxHeight}:{}}),f=i.createElement("table");f.className="wmap-dielist-table",f.setAttribute("aria-label",n.title??`Die list (${t.length} dies)`);const R=s(i,"thead"),T=s(i,"tr");for(const e of $){const l=i.createElement("th");l.className="wmap-dielist-th",l.scope="col",l.textContent=e.label,T.appendChild(l)}R.appendChild(T),f.appendChild(R);const I=s(i,"tbody");for(const e of K){const l=s(i,"tr");for(const o of $){const c=i.createElement("td");c.className="wmap-dielist-td",c.textContent=o.get(e),l.appendChild(c)}I.appendChild(l)}if(f.appendChild(I),D.appendChild(f),m.appendChild(D),C>0||x.length>0){const e=[];C>0&&e.push(`Showing the first ${u.toLocaleString()} of ${t.length.toLocaleString()} dies. The CSV export contains all ${t.length.toLocaleString()}.`),x.length>0&&e.push(`${x.length} further metadata field${x.length===1?"":"s"} not shown.`),m.appendChild(s(i,"div",{fontSize:g.body,color:d.label,lineHeight:N.base,flexShrink:"0"},e.join(" ")))}return h.addEventListener("click",()=>{const e=[y.map(l=>k(l.csvLabel??l.label)).join(",")];for(const l of t)e.push(y.map(o=>k((o.csvGet??o.get)(l))).join(","));Z(e.join(`
13
- `),"die-list.csv","text/csv",n.onSaveText)}),m}
12
+ `,t.head.appendChild(r)}function d(t,r,n,i){const l=t.createElement(r);return n&&Object.assign(l.style,n),i!==void 0&&(l.textContent=i),l}function ee(t){return T(t)?String(t.x):""}function te(t){return T(t)?String(t.y):""}function ne(t,r){return r!==void 0?r.filter(n=>n.testNumber!==void 0):K(t,"values").map(n=>({testNumber:n,name:L(void 0,n)}))}function ie(t,r,n){if(!r)return;const i=F(n,"the die list"),l=new Map;for(const m of t){if(!M(m))continue;const C=r(m);C&&l.set(m,z(m,C,{ringCount:i}))}return l.size>0?l:void 0}export function resolveDieColumns(t,r,n={}){const i=ne(t,r),l=ie(t,n.getWafer,n),m=t.some(e=>e.edgeExcluded===!0),C=[...n.extraColumn?[n.extraColumn.label]:[],"X","Y","Ring","Quadrant","Edge excluded","Site","Hard bin","Soft bin",...i.map(e=>$(L(e,e.testNumber),e.unit))],{columns:p,truncatedKeys:v}=O({dies:t,waferMetadata:n.waferMetadata,metadataFields:n.metadataFields,dieKeys:n.metadataColumns,waferKeys:n.waferMetadataColumns==="none"?"none":void 0,waferPlacement:n.waferMetadataColumns??"csv",reservedLabels:C}),a=[...n.extraColumn?[{label:n.extraColumn.label,get:e=>n.extraColumn.get(e)??""}]:[],{label:"X",get:ee},{label:"Y",get:te},...l?[{label:"Ring",get:e=>{const x=l.get(e);return x?String(x.ring):""}},{label:"Quadrant",get:e=>l.get(e)?.quadrant??""}]:[],...m?[{label:"Edge excluded",get:e=>e.edgeExcluded?"Yes":"No"}]:[],{label:"Site",get:e=>e.siteNum!==void 0?String(e.siteNum):""},{label:"Hard bin",get:e=>e.hbin!==void 0?String(e.hbin):""},{label:"Soft bin",get:e=>e.sbin!==void 0?String(e.sbin):""},...i.map(e=>{const x=!B(e);return{test:e,label:$(H(e,e.testNumber),e.unit),...Q(e)?{csvLabel:`${$(L(e,e.testNumber),e.unit)} [derived from ${q(e)??J}]`}:{},get:s=>{if(x){const u=y(s,e.testNumber,e);return u===void 0?"":u?"PASS":"FAIL"}const g=I(s,e.testNumber);if(g!==void 0)return _(g,e.unit);const h=y(s,e.testNumber,e);return h===void 0?"":h?"PASS":"FAIL"},csvGet:s=>{if(x){const u=y(s,e.testNumber,e);return u===void 0?"":u?"PASS":"FAIL"}const g=I(s,e.testNumber);if(g!==void 0)return g;const h=y(s,e.testNumber,e);return h===void 0?"":h?"PASS":"FAIL"}}}),...p.map(e=>({label:e.label,get:e.get,csvOnly:e.csvOnly}))],E=a.filter(e=>!e.csvOnly);return{columns:a,visibleColumns:E,testColumns:i,truncatedKeys:v}}export function buildDieListSection(t,r,n={}){if(!t.length)return null;const i=n.ownerDocument??document;ensureStylesInjected(i);const l=n.maxRows??Z,m=Math.max(0,t.length-l),C=m>0?t.slice(0,l):t,p=d(i,"div",{display:"flex",flexDirection:"column",gap:G.md,flex:"1",minHeight:"0",minWidth:"0"}),v=d(i,"div",{display:"flex",alignItems:"center",justifyContent:"space-between",flexShrink:"0"});v.appendChild(d(i,"div",{fontSize:w.meta,fontWeight:"700",letterSpacing:W,textTransform:"uppercase",color:c.label},n.title??`Die list (${t.length})`));const a=d(i,"button",{fontSize:w.body,padding:"3px 8px",borderRadius:P.control,...V("outlined"),background:c.menuBg},"Export CSV");a.type="button",j(a),v.appendChild(a),p.appendChild(v),n.note&&p.appendChild(d(i,"div",{fontSize:w.body,color:c.label,lineHeight:N.base,flexShrink:"0"},n.note));const{columns:E,visibleColumns:e,testColumns:x,truncatedKeys:s}=resolveDieColumns(t,r,n),g=X(x.map(o=>({label:H(o,o.testNumber),...U(o)})));g&&p.appendChild(d(i,"div",{fontSize:w.body,color:c.label,lineHeight:N.base,flexShrink:"0"},g));const h=d(i,"div",{overflow:"auto",flex:"1",minHeight:"0",minWidth:"0",border:`1px solid ${c.menuBorder}`,borderRadius:P.control,...n.maxHeight?{maxHeight:n.maxHeight}:{}}),u=i.createElement("table");u.className="wmap-dielist-table",u.setAttribute("aria-label",n.title??`Die list (${t.length} dies)`);const A=d(i,"thead"),D=d(i,"tr");for(const o of e){const f=i.createElement("th");f.className="wmap-dielist-th",f.scope="col",f.textContent=o.label,D.appendChild(f)}A.appendChild(D),u.appendChild(A);const R=d(i,"tbody");for(const o of C){const f=d(i,"tr");for(const b of e){const S=i.createElement("td");S.className="wmap-dielist-td",S.textContent=b.get(o),f.appendChild(S)}R.appendChild(f)}if(u.appendChild(R),h.appendChild(u),p.appendChild(h),m>0||s.length>0){const o=[];m>0&&o.push(`Showing the first ${l.toLocaleString()} of ${t.length.toLocaleString()} dies. The CSV export contains all ${t.length.toLocaleString()}.`),s.length>0&&o.push(`${s.length} further metadata field${s.length===1?"":"s"} not shown.`),p.appendChild(d(i,"div",{fontSize:w.body,color:c.label,lineHeight:N.base,flexShrink:"0"},o.join(" ")))}return a.addEventListener("click",()=>{if(a.disabled)return;const o=a.textContent,f=Y(E.map(b=>({header:b.csvLabel??b.label,get:b.csvGet??b.get})),t,"die-list.csv",n.onSaveText,{onProgress:(b,S)=>{a.textContent=S?`Exporting ${Math.floor(100*b/S)}%`:`Exporting ${b.toLocaleString()} rows`}});f&&(a.disabled=!0,a.setAttribute("aria-busy","true"),f.catch(()=>{}).finally(()=>{a.disabled=!1,a.removeAttribute("aria-busy"),a.textContent=o}))}),p}
@@ -1,11 +1,28 @@
1
- import { type SweepSpec } from '../stats/sweep.js';
2
- import { type SaveImageHandler } from './toolbar.js';
1
+ import type { Die } from '../core/dies.js';
2
+ import { type SaveImageHandler, type SaveTextHandler } from './toolbar.js';
3
3
  import type { DrilldownSource, DrilldownItem } from './chartPopulation.js';
4
+ import type { PlotStore } from './plotStore.js';
4
5
  export type { DrilldownSource, DrilldownItem };
5
6
  /** What the targets can draw on — the host's chart definitions and hooks. */
6
7
  export interface DrilldownContext {
7
- sweeps?: SweepSpec[];
8
+ /** The reader's saved plots: each is a row in the menu, opened on the selection. */
9
+ plots?: PlotStore;
8
10
  onSaveImage?: SaveImageHandler;
11
+ /** The host's CSV save hook, for the tables' Export CSV. */
12
+ onSaveText?: SaveTextHandler;
13
+ /** A die row clicked in a table: show that die on the map it belongs to. The table steps aside first. */
14
+ onLocateDie?: (die: Die, waferIndex: number | undefined) => void;
15
+ /**
16
+ * A narrower population for the same dies, offered as the menu's first row: the menu reopens on it, and its own
17
+ * `narrower` is the population it left, so the row swaps back. Set by a gallery when dies are selected on several
18
+ * wafers and the right-click is on one of them.
19
+ */
20
+ narrower?: {
21
+ label: string; /** The swap-back row's wording before the population; default "All selected". */
22
+ backLabel?: string;
23
+ source: DrilldownSource;
24
+ ctx?: DrilldownContext;
25
+ };
9
26
  }
10
27
  /**
11
28
  * Open the drilldown menu for `source` at viewport point `at`.
@@ -1 +1 @@
1
- import{isYieldEligibleDie as b}from"../core/dies.js";import{testValue as T}from"../core/dieTable.js";import{isParametricTest as y}from"../renderer/buildWaferMap.js";import{buildSweepData as C}from"../stats/sweep.js";import{renderSweepPanel as E}from"./charts/sweep.js";import{renderHistogramPanel as $}from"./charts/histogram.js";import{renderCapabilityPanel as I}from"./charts/capability.js";import{openChartExpandModal as M,populationPhrase as w}from"./charts/chartShell.js";import{buildCheckMenuEl as x,createToolbarHelpers as N,getTooltip as R,menuLayerFor as L,wireMenuA11y as V,CLR as k,FONT as F,SPACE as O}from"./toolbar.js";const P=30,v=e=>e.items.flatMap(t=>t.dies);function A(e){return e.items.map(t=>({...t,dies:t.dies.filter(a=>b(a))}))}function H(e,t,a){return(e.testDefs??[]).filter(y).map(s=>s.testNumber).filter(s=>{let o=0;for(const i of t){const n=T(i,s);if(n!==void 0&&Number.isFinite(n)&&++o>=a)return!0}return!1})}function S(e,t,a,s,o){const n=e.ownerDocument.createElement("div");n.textContent=a,n.dataset.wmapPopulation="1",Object.assign(n.style,{color:k.label,fontSize:F.body,marginBottom:O.sm});const r=e.querySelector("[data-wmap-chart-expand]")?.parentElement;r?.parentElement===e?r.after(n):e.prepend(n),M(e,t,{anchor:s,onClosed:o})}function W(e,t,a,s){const o=v(t);let i=t.notMeasuredReason??null;return!i&&!o.some(n=>b(n))&&(i="These dies are all partial or edge-excluded, which charts leave out"),i||C(o,t.testDefs,e).series.some(r=>r.points.some(d=>d.count>0))||(i="These dies have no values for this sweep\u2019s tests"),{section:"Sweep",label:e.title,unavailable:i,open:()=>{const n=E({spec:e,dies:o,population:t.population,testDefs:t.testDefs,onSaveImage:a.onSaveImage,ownerDocument:s.ownerDocument}),r=o.filter(d=>b(d)).length;M(n.card,`${e.title} \u2014 ${w(r,o.length,t.population)}`,{anchor:s,onClosed:()=>n.destroy()})}}}function B(e,t,a){const s=A(e),o=v(e).length,i=s.flatMap(l=>l.dies),n=w(i.length,o,e.population),r=(e.testDefs??[]).filter(y),d=a.ownerDocument,f=(l,c)=>e.notMeasuredReason?e.notMeasuredReason:i.length===0?"These dies are all partial or edge-excluded, which charts leave out":H(e,i,l).length>0?null:c,u={section:"Distributions",label:"Value histogram",unavailable:f(1,"These dies have no parametric test values"),open:()=>{const l=$({title:"Value histogram",items:s,testDefs:r,selectedTestNumber:e.activeTest,onSaveImage:t.onSaveImage,ownerDocument:d});S(l.card,`Value histogram \u2014 ${n}`,`Population: ${n}`,a,()=>l.destroy())}},h={section:"Distributions",label:"Process capability",unavailable:f(2,"Needs at least 2 of these dies with values for one test"),open:()=>{const l=I({title:"Process capability",items:s,testDefs:r,selectedTestNumber:e.activeTest,onSaveImage:t.onSaveImage,ownerDocument:d}),c=i.length<P?` \xB7 fewer than ${P} dies, so each Ppk is a rough estimate`:"";S(l.card,`Process capability \u2014 ${n}`,`Population: ${n}${c}`,a,()=>l.destroy())}};return[u,h]}function _(e,t,a){return[...B(e,t,a),...(t.sweeps??[]).map(s=>W(s,e,t,a))]}const g=new WeakMap;export function openDrilldownMenu(e,t,a,s){const o=t.ownerDocument,i=o.defaultView??window;g.get(o)?.();const n=_(a,s,t),r=[];let d;for(const p of n)p.section!==d&&(r.push({section:p.section}),d=p.section),r.push({label:p.label,active:!1,action:!0,enabled:p.unavailable===null,disabledHint:p.unavailable??void 0,onClick:()=>{m(),p.open()}});const{makeMenuSection:f}=N(R(o)),u=x(new DOMRect(e.x,e.y-4,0,0),r,{makeMenuSection:f},i),h=v(a).length;u.setAttribute("role","menu"),u.setAttribute("aria-label",`Open a chart of ${w(h,h,a.population)}`),u.dataset.wmapDrilldownMenu="1",L(t).appendChild(u);const l=u.getBoundingClientRect();l.bottom>i.innerHeight-4&&(u.style.top=`${Math.max(4,e.y-l.height)}px`),l.right>i.innerWidth-4&&(u.style.left=`${Math.max(4,i.innerWidth-l.width-4)}px`);let c=!1;function m(){c||(c=!0,u.remove(),o.removeEventListener("pointerdown",D,!0),i.removeEventListener("blur",m),g.get(o)===m&&g.delete(o))}function D(p){u.contains(p.target)||m()}return o.addEventListener("pointerdown",D,!0),i.addEventListener("blur",m),V(u,t,m),g.set(o,m),m}
1
+ import{isYieldEligibleDie as P}from"../core/dies.js";import{testValue as k}from"../core/dieTable.js";import{isParametricTest as C}from"../renderer/buildWaferMap.js";import{buildSweepData as x}from"../stats/sweep.js";import{plotToSweep as L}from"../stats/plotSpec.js";import{renderHistogramPanel as O}from"./charts/histogram.js";import{renderCapabilityPanel as W}from"./charts/capability.js";import{openChartExpandModal as A,populationPhrase as D}from"./charts/chartShell.js";import{buildCheckMenuEl as F,createToolbarHelpers as V,getTooltip as H,menuLayerFor as j,wireMenuA11y as q,CLR as z,FONT as B,SPACE as M,openReparentedModal as _,controlStyle as K,wireControlHover as U}from"./toolbar.js";import{requireRingCount as Y}from"../core/ringCount.js";import{defaultPlot as G,defaultSweep as J,fieldCatalogue as Q,plotTitle as X,resolvePlot as Z}from"../stats/plotData.js";import{newPlotId as y}from"../stats/plotSpec.js";import{sourceFromPoints as ee,toPlotItems as S}from"./plotItems.js";const E=30,g=e=>e.items.flatMap(t=>t.dies);function te(e){return e.items.map(t=>({...t,dies:t.dies.filter(n=>P(n))}))}function N(e,t,n){return(e.testDefs??[]).filter(C).map(l=>l.testNumber).filter(l=>{let o=0;for(const s of t){const u=k(s,l);if(u!==void 0&&Number.isFinite(u)&&++o>=n)return!0}return!1})}function $(e,t,n,l,o,s){const u=e.ownerDocument,a=u.createElement("div"),f=u.createElement("span");f.textContent=n,f.dataset.wmapPopulation="1",Object.assign(a.style,{color:z.label,fontSize:B.body,marginBottom:M.sm,display:"flex",alignItems:"center",gap:M.lg,flexWrap:"wrap"}),a.appendChild(f);let h=null;if(s){const c=u.createElement("button");c.type="button",c.textContent=s.label,c.dataset[s.hook]="1",Object.assign(c.style,K("outlined")),U(c),c.addEventListener("click",()=>s.run(()=>h?.close())),a.appendChild(c)}const i=e.querySelector("[data-wmap-chart-expand]")?.parentElement;i?.parentElement===e?i.after(a):e.prepend(a),h=A(e,t,{anchor:l,onClosed:o})}function ne(e,t){const n=g(t);return t.notMeasuredReason?t.notMeasuredReason:n.some(o=>P(o))?x(n,t.testDefs,e).series.some(o=>o.points.some(s=>s.count>0))?null:"These dies have no values for this sweep\u2019s tests":"These dies are all partial or edge-excluded, which charts leave out"}function oe(e,t,n){const l=te(e),o=g(e).length,s=l.flatMap(r=>r.dies),u=D(s.length,o,e.population),a=(e.testDefs??[]).filter(C),f=n.ownerDocument,h=(r,p)=>e.notMeasuredReason?e.notMeasuredReason:s.length===0?"These dies are all partial or edge-excluded, which charts leave out":N(e,s,r).length>0?null:p,i={section:"Distributions",label:"Value histogram",unavailable:h(1,"These dies have no parametric test values"),open:()=>{let r=e.activeTest??a[0]?.testNumber;const p=O({title:"Value histogram",items:l,testDefs:a,selectedTestNumber:e.activeTest,onTestChange:m=>{r=m},onSaveImage:t.onSaveImage,ownerDocument:f}),d=I(e,t,n),b=d&&r!==void 0?{label:"Edit as new plot",hook:"wmapEditAsPlot",run:m=>{const w=a.find(v=>v.testNumber===r);m(),d({id:y(),chart:"histogram",fields:{y:{test:r,...w?.name?{name:w.name}:{}},color:{none:!0}}},!0)()}}:void 0;$(p.card,`Value histogram \u2014 ${u}`,`Population: ${u}`,n,()=>p.destroy(),b)}},c={section:"Distributions",label:"Process capability",unavailable:h(2,"Needs at least 2 of these dies with values for one test"),open:()=>{const r=W({title:"Process capability",items:l,testDefs:a,selectedTestNumber:e.activeTest,onSaveImage:t.onSaveImage,ownerDocument:f}),p=s.length<E?` \xB7 fewer than ${E} dies, so each Ppk is a rough estimate`:"";$(r.card,`Process capability \u2014 ${u}`,`Population: ${u}${p}`,n,()=>r.destroy())}};return[i,c]}function I(e,t,n){const l=t.plots;if(!l)return null;const o=n.ownerDocument,s=S(e.items),u={testDefs:e.testDefs},a=g(e),f=D(a.length,a.length,e.population),h=D(a.filter(i=>P(i)).length,a.length,e.population);return(i,c)=>()=>{const r=i.chart==="sweep",p=r?h:f;import("./plotModal.js").then(({openPlotEditor:d})=>d({doc:o,anchor:n,store:l,plot:i,draft:c,items:s,ctx:u,population:p,rawPopulation:e.population,title:`${c?r?"New sweep":"New plot":r?"Sweep":"Plot"} \u2014 ${p}`,chart:{onSaveImage:t.onSaveImage,onSelectPoints:(b,m,w)=>{const v=ee(b,s,e.testDefs);v&&openDrilldownMenu(m,w,v,t)}}}))}}function se(e,t,n){const l=t.plots,o=I(e,t,n);if(!l||!o)return[];const s=S(e.items),u={testDefs:e.testDefs},a=g(e),f=e.notMeasuredReason??(a.length===0?"Nothing is selected":null),h=l.get().map(p=>{const d=L(p);if(d)return{section:"Plots",label:d.title,unavailable:ne(d,e),open:o(p,!1)};const b=Z(p,s,u);return{section:"Plots",label:X(b)||"Plot",unavailable:f??b.issues[0]??(b.plotted===0?"No die here has the values this plot needs":null),open:o(p,!1)}}),i=Q(s,u),c=i.some(p=>p.group==="Tests");h.push({section:"Plots",label:"New plot\u2026",unavailable:f??(c?null:"These dies have no parametric test values"),open:o(G(i,y()),!0)});const r=(e.testDefs??[]).filter(p=>C(p));return h.push({section:"Plots",label:"New sweep\u2026",unavailable:f??(r.length>0?null:"These dies have no parametric test values"),open:o(J(e.testDefs??[],y()),!0)}),h}function le(e,t,n){const l=g(e),o=e.notMeasuredReason??(l.length===0?"Nothing is selected":null),s=l.length,u=D(s,s,e.population),a=(f,h)=>()=>{import("./dataTab.js").then(({renderSelectionTables:i})=>{const c=n.ownerDocument;let r;const p=i({doc:c,ringCount:Y(e.items[0],"the drilldown tables"),items:e.items,testDefs:e.testDefs,population:e.population,view:f,onSaveText:t.onSaveText,onLocateDie:t.onLocateDie?(b,m)=>{r?.(),t.onLocateDie(b,m)}:void 0}),d=_([p.el],{title:`${h} \u2014 ${u}`,anchor:n,ownerDocument:c,boxSize:{width:"min(96vw, 1200px)",height:"min(92vh, 780px)"},onClosed:p.destroy});d?r=()=>d.close():p.destroy()})};return[{section:"Tables",label:"Dies",unavailable:o,open:a("dies","Dies")},{section:"Tables",label:"Test statistics",unavailable:o??(N(e,l,1).length>0?null:"These dies have no parametric test values"),open:a("statistics","Test statistics")},...e.items.length>1?[{section:"Tables",label:"Wafers",unavailable:o,open:a("wafers","Wafers")}]:[]]}function ae(e,t,n){return[...oe(e,t,n),...se(e,t,n),...le(e,t,n)]}const T=new WeakMap;export function openDrilldownMenu(e,t,n,l){const o=t.ownerDocument,s=o.defaultView??window;T.get(o)?.();const u=ae(n,l,t),a=[];if(l.narrower){const{label:m,source:w,ctx:v}=l.narrower,R={label:`${l.narrower.backLabel??"All selected"} \u2014 ${D(g(n).length,g(n).length,n.population)}`,source:n,ctx:l};a.push({section:"Dies"}),a.push({label:m,active:!1,action:!0,enabled:!0,onClick:()=>{d(),openDrilldownMenu(e,t,w,{...v??l,narrower:R})}})}let f;for(const m of u)m.section!==f&&(a.push({section:m.section}),f=m.section),a.push({label:m.label,active:!1,action:!0,enabled:m.unavailable===null,disabledHint:m.unavailable??void 0,onClick:()=>{d(),m.open()}});const{makeMenuSection:h}=V(H(o)),i=F(new DOMRect(e.x,e.y-4,0,0),a,{makeMenuSection:h},s),c=g(n).length;i.setAttribute("role","menu"),i.setAttribute("aria-label",`Open a chart or table of ${D(c,c,n.population)}`),i.dataset.wmapDrilldownMenu="1",j(t).appendChild(i);const r=i.getBoundingClientRect();r.bottom>s.innerHeight-4&&(i.style.top=`${Math.max(4,e.y-r.height)}px`),r.right>s.innerWidth-4&&(i.style.left=`${Math.max(4,s.innerWidth-r.width-4)}px`);let p=!1;function d(){p||(p=!0,i.remove(),o.removeEventListener("pointerdown",b,!0),s.removeEventListener("blur",d),T.get(o)===d&&T.delete(o))}function b(m){i.contains(m.target)||d()}return o.addEventListener("pointerdown",b,!0),s.addEventListener("blur",d),q(i,t,d),T.set(o,d),d}
@@ -0,0 +1,96 @@
1
+ import { type OverlayHandle } from './toolbar.js';
2
+ type GuideApi = {
3
+ buildWaferMap: unknown;
4
+ renderWaferMap: unknown;
5
+ renderWaferGallery: unknown;
6
+ analyzeWaferMap: unknown;
7
+ };
8
+ /**
9
+ * Host-supplied content inserted into wmap's own embedded user-guide window,
10
+ * so a host app's own documentation and wmap's reference live behind one
11
+ * help button instead of two separate ones.
12
+ */
13
+ export interface UserGuideExtension {
14
+ /**
15
+ * Host-provided HTML, inserted before wmap's own guide content. Static
16
+ * content only — must not contain `<script>` tags. wmap re-executes
17
+ * exactly one inline script (its own live-demo bootstrap, always the
18
+ * last element in `html`) by finding the content div's first
19
+ * `<script>` in document order; a `<script>` in this HTML would be
20
+ * found instead, silently breaking wmap's own live demos.
21
+ */
22
+ html: string;
23
+ /**
24
+ * Overrides the window's title text (default `'Wafer Map — User Guide'`).
25
+ * Use this when the host's own content should frame the whole document —
26
+ * wmap's own `<h1>` stays as-is further down the page, so the result
27
+ * reads as one combined guide with the host's section first, not a title
28
+ * rewrite.
29
+ */
30
+ title?: string;
31
+ }
32
+ /**
33
+ * Open the embedded user-guide window and populate its live demos. Closes any
34
+ * previously open guide window first (at most one may be open at a time).
35
+ * `api` must contain the four library functions the guide demos call at runtime.
36
+ * `html` is USER_GUIDE_HTML — passed in so toolbar.ts has no dependency on userGuideHtml.ts.
37
+ * `extension`, when provided, prepends host content and/or overrides the window title — see `UserGuideExtension`.
38
+ *
39
+ * Opens a real, separate window when available (draggable outside the host
40
+ * window's own bounds), falling back to the in-page floating window when
41
+ * `window.open` is blocked/unavailable — silently returns `null` in some
42
+ * embedded WebViews (Tauri, Electron, WebView2), same gap `openDetachWindow`
43
+ * exists for. Unlike gallery card detach, this doesn't go through
44
+ * `setDetachWindowOpener` — that opener's contract is scoped to per-wafer
45
+ * detach windows a host may have built specifically for that shape of
46
+ * content, and reusing it silently for the guide would broaden it without
47
+ * documentation.
48
+ */
49
+ export declare function openUserGuideWindow(api: GuideApi, html: string, extension?: UserGuideExtension, anchor?: Element): void;
50
+ /**
51
+ * Opens wmap's own guide window with no live `WaferMapController`/
52
+ * `GalleryController` required — `WaferMapController.openUserGuide()`/
53
+ * `GalleryController.openUserGuide()` are thin wrappers around this exact
54
+ * same call (see their own `openGuideWindow` closures in `renderWaferMap.ts`/
55
+ * `renderWaferGallery.ts`), built from the library's own top-level functions
56
+ * rather than anything derived from a specific render — so a host with
57
+ * nothing rendered yet (e.g. an empty-state "Help" menu, before any file is
58
+ * loaded) can reach the same guide, with the same `extension` content, the
59
+ * same way. Prefer the controller method when one exists — this is for the
60
+ * gap it can't cover.
61
+ *
62
+ * `renderWaferMap`/`renderWaferGallery` are dynamically imported (like
63
+ * `USER_GUIDE_HTML` below) since `toolbar.ts` is itself imported BY both of
64
+ * them — a static import here would cycle back.
65
+ */
66
+ export declare function openWaferMapGuide(extension?: UserGuideExtension, anchor?: Element): void;
67
+ /**
68
+ * Opens report HTML (from `renderSummaryReportHtml`/`renderLotSummaryReportHtml`)
69
+ * in an in-app modal — no host-registered `setReportOpener` required just to
70
+ * *view* a report. `setReportOpener`/`openHtmlReport` remain available as an
71
+ * "Open as full page" action inside this modal (its header's print button, plus
72
+ * a link in the toolbar strip below it), for a host that wants the report as a
73
+ * real separate page — to keep it open outside this modal, or as a fallback if
74
+ * in-app printing misbehaves on a given platform.
75
+ *
76
+ * The report HTML is a complete standalone document (its own `<style>`, its own
77
+ * `<!DOCTYPE html>`) — rendered via `<iframe srcdoc>` rather than parsed apart
78
+ * and inserted into the modal's own DOM (the way the guide's `buildGuideContent`
79
+ * does it). This keeps the report's stylesheet naturally isolated from the host
80
+ * page with no parsing, and lets `iframe.contentWindow.print()` scope printing
81
+ * to just the report — `openOverlay`'s sibling-hiding print stylesheet (built
82
+ * for content that lives directly in the host document, like the guide) isn't
83
+ * needed here and wouldn't reach into the iframe's own document anyway.
84
+ */
85
+ export declare function openReportModal(html: string, opts?: {
86
+ anchor?: Element;
87
+ ownerDocument?: Document;
88
+ /**
89
+ * A report built with `live` tells this page which finding a click on its row meant. Called with that
90
+ * finding's id and the modal's handle, so the host can close the modal and show the finding on the map.
91
+ * Only messages from this report's own frame are heeded.
92
+ */
93
+ onFinding?: (findingId: string, handle: OverlayHandle) => void;
94
+ }): OverlayHandle;
95
+ export {};
96
+ //# sourceMappingURL=guideWindow.d.ts.map
@@ -0,0 +1 @@
1
+ import{openHtmlReport as N}from"../stats/renderFindingsReport.js";import{buildWaferMap as G}from"../renderer/buildWaferMap.js";import{analyzeWaferMap as M}from"../stats/analyzeWaferMap.js";import{CLR as f,FONT as $,RADIUS as k,MOTION as O,wireControlHover as H,copyWmapThemeTokens as B,syncWmapPopupTheme as F,openModal as R,openFloatingWindow as z,wireExpandToggle as P}from"./toolbar.js";let y=null;const S=40,j=S+8;function U(e,i){const s=e.createElement("div");s.classList.add("wmap-guide-search"),s.style.cssText="display:flex;align-items:center;gap:2px;margin-left:10px;flex:1;min-width:80px;max-width:260px;";const t=e.createElement("input");t.type="text",t.placeholder="Search guide\u2026",t.setAttribute("aria-label","Search guide"),t.style.cssText=`flex:1;min-width:0;padding:4px 8px;font-size:12px;background:${f.menuBg};color:${f.text};border:1px solid ${f.menuBorder};border-radius:${k.control};outline:none;`,t.addEventListener("focus",()=>{t.style.borderColor=f.iconActive}),t.addEventListener("blur",()=>{t.style.borderColor=f.menuBorder});const n=e.createElement("span");n.setAttribute("role","status"),n.setAttribute("aria-live","polite"),n.style.cssText=`font-size:12px;color:${f.label};min-width:44px;text-align:center;flex-shrink:0;white-space:nowrap;`;function r(u,m){const d=e.createElement("button");return d.type="button",d.textContent=u,d.setAttribute("aria-label",m),d.disabled=!0,d.style.cssText=`border:none;background:none;cursor:pointer;padding:4px 6px;border-radius:${k.control};font-size:12px;color:${f.label};flex-shrink:0;opacity:0.4;`,d.addEventListener("mouseenter",()=>{d.disabled||(d.style.background=f.bgHover)}),d.addEventListener("mouseleave",()=>{d.style.background="none"}),d}const a=r("\u25B2","Previous match"),o=r("\u25BC","Next match");let l=[],c=-1;function p(){const u=l.length===0;a.disabled=u,o.disabled=u,a.style.opacity=u?"0.4":"",o.style.opacity=u?"0.4":""}function g(){for(const u of l)u.parentNode?.replaceChild(e.createTextNode(u.textContent??""),u);i.normalize(),l=[],c=-1}function h(u){if(l.length===0)return;c>=0&&(l[c].style.outline="none"),c=(u%l.length+l.length)%l.length;const m=l[c];m.style.outline=`2px solid ${f.iconActive}`,m.style.outlineOffset="1px",m.scrollIntoView({behavior:"smooth",block:"center"}),n.textContent=`${c+1}/${l.length}`}function w(u){g();const m=u.trim();if(!m){n.textContent="",p();return}const d=m.toLowerCase(),x=e.createTreeWalker(i,NodeFilter.SHOW_TEXT,{acceptNode(C){const b=C.parentElement;return!b||b.closest("script,style,.wmap-guide-toc")?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}}),T=[];let _;for(;_=x.nextNode();)T.push(_);for(const C of T){const b=C.textContent??"",A=b.toLowerCase();if(!A.includes(d))continue;const L=e.createDocumentFragment();let E=0,v=A.indexOf(d);for(;v!==-1;){v>E&&L.appendChild(e.createTextNode(b.slice(E,v)));const D=e.createElement("mark");D.textContent=b.slice(v,v+m.length),L.appendChild(D),l.push(D),E=v+m.length,v=A.indexOf(d,E)}E<b.length&&L.appendChild(e.createTextNode(b.slice(E))),C.parentNode?.replaceChild(L,C)}p(),l.length===0?n.textContent="No matches":h(0)}return t.addEventListener("input",()=>w(t.value)),t.addEventListener("keydown",u=>{u.key==="Enter"?(u.preventDefault(),h(c+(u.shiftKey?-1:1))):u.key==="Escape"&&(t.value?(t.value="",w("")):t.blur())}),a.addEventListener("click",()=>h(c-1)),o.addEventListener("click",()=>h(c+1)),s.append(t,n,a,o),s}function q(e,i,s){const t=[...i.querySelectorAll("h2[id]")];if(t.length<2)return;for(const m of i.querySelectorAll("h1[id],h2[id],h3[id],h4[id]"))m.style.scrollMarginTop=`${j}px`;const n=e.createElement("div");n.classList.add("wmap-guide-toc"),n.style.cssText=`position:sticky;top:0;z-index:2;background:${f.menuBg};border-bottom:1px solid ${f.menuBorder};font-family:system-ui,-apple-system,"Segoe UI",sans-serif;`;const r=e.createElement("div");r.style.cssText=`display:flex;align-items:center;gap:4px;height:${S}px;padding:0 12px;box-sizing:border-box;`;const a=e.createElement("span");a.innerHTML='<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>',a.style.cssText=`display:inline-flex;transition:transform ${O.base};flex-shrink:0;`;const o=e.createElement("button");o.type="button";const l="wmap-guide-toc-panel";o.setAttribute("aria-expanded","false"),o.setAttribute("aria-controls",l),o.style.cssText=`display:flex;align-items:center;gap:6px;border:none;background:none;cursor:pointer;padding:6px 8px;margin:0 -8px 0 -8px;border-radius:${k.control};font-size:13px;font-weight:600;color:${f.text};`;const c=e.createElement("span");c.textContent=`Contents (${t.length})`,o.append(a,c),o.addEventListener("mouseenter",()=>{o.style.background=f.bgHover}),o.addEventListener("mouseleave",()=>{o.style.background="none"});const p=e.createElement("button");p.type="button",p.textContent="\u2191 Top",p.style.cssText=`margin-left:auto;border:none;background:none;cursor:pointer;padding:6px 8px;border-radius:${k.control};font-size:12px;color:${f.label};`,p.addEventListener("mouseenter",()=>{p.style.background=f.bgHover}),p.addEventListener("mouseleave",()=>{p.style.background="none"}),p.addEventListener("click",()=>{u.close(),i.scrollTo({top:0,behavior:"smooth"})}),s?r.append(o,U(e,i),p):r.append(o,p);const g=e.createElement("div");g.id=l,g.hidden=!0,g.style.cssText="padding:2px 12px 12px;max-height:min(60vh,440px);overflow-y:auto;";const h=/^\s*\d+(?:\.\d+)*\.\s+/;for(const m of i.querySelectorAll("h2[id],h3[id],h4[id]")){const d=m.firstChild;d?.nodeType===Node.TEXT_NODE&&(d.textContent=(d.textContent??"").replace(h,""))}const w=e.createElement("ul");w.setAttribute("role","list"),w.style.cssText="list-style:none;margin:0;padding:0;columns:200px;column-gap:24px;";for(const m of t){const d=e.createElement("li");d.style.cssText="break-inside:avoid;";const x=e.createElement("a");x.href=`#${m.id}`,x.textContent=m.textContent??"",x.style.cssText=`display:block;padding:4px 6px;margin:0 -6px;border-radius:${k.control};font-size:12.5px;color:${f.iconActive};text-decoration:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`,x.addEventListener("mouseenter",()=>{x.style.background=f.bgHover}),x.addEventListener("mouseleave",()=>{x.style.background="none"}),x.addEventListener("click",T=>{T.preventDefault(),u.close(),m.scrollIntoView({behavior:"smooth",block:"start"})}),d.appendChild(x),w.appendChild(d)}g.appendChild(w);const u=P(o,m=>{g.hidden=!m,o.setAttribute("aria-expanded",String(m)),a.style.transform=m?"rotate(180deg)":"none"});n.append(r,g),i.prepend(n)}function W(e,i,s,t,n){const r=i,a=r.__wmapDemoApi;r.__wmapDemoApi=t;const o=e.createElement("div");return o.classList.add("wmap-guide-content"),o.innerHTML=s,q(e,o,n),{content:o,restoreApi:()=>{r.__wmapDemoApi===t&&(r.__wmapDemoApi=a)}}}function I(e,i){const s=i,t=e.querySelector("script");if(t){const n=e.ownerDocument.createElement("script");n.textContent=t.textContent,e.appendChild(n);const r=e.querySelector(".wmap-guide");return r&&s.__wmapPopulateGuideDemos?.(r),s.__wmapDestroyGuideDemos}}function V(e,i,s,t){const n=t?.ownerDocument??document,r=n.defaultView??window,{content:a,restoreApi:o}=W(n,r,i,s,!0);Object.assign(a.style,{flex:"1",overflow:"auto",minHeight:"0",minWidth:"0"});let l;const c=z({title:e,anchor:t,ownerDocument:n,printable:!0,onMaximizeChange:p=>{a.style.setProperty("--wmap-guide-reading-width",p?"1000px":"720px")},onClose:()=>{y===c&&(y=null),l?.(),o()}});return c.contentWrap.appendChild(a),l=I(a,r),c}function X(e,i,s,t){const n=e.document;n.title=i,Object.assign(n.documentElement.style,{height:"100%"}),Object.assign(n.body.style,{margin:"0",height:"100vh",overflow:"hidden",display:"flex",flexDirection:"column",fontFamily:$.family}),B(document.documentElement,n.documentElement);const r=F(document.documentElement,n.documentElement),{content:a,restoreApi:o}=W(n,e,s,t,!1);Object.assign(a.style,{flex:"1",overflow:"auto",minHeight:"0",minWidth:"0"}),n.body.appendChild(a);const l=I(a,e);let c=!1;const p=()=>{if(!c){c=!0,clearInterval(g),r();try{l?.()}catch{}o(),y===h&&(y=null)}},g=setInterval(()=>{e.closed&&p()},400);e.addEventListener("pagehide",p);const h={close:()=>{e.closed||e.close(),p()}};return h}export function openUserGuideWindow(e,i,s,t){y&&(y.close(),y=null);const n=s?.title??"Wafer Map \u2014 User Guide",r=(s?.html??"")+i,o=(t?.ownerDocument.defaultView??window).open("","_blank","width=800,height=820");y=o?X(o,n,r,e):V(n,r,e,t)}export function openWaferMapGuide(e,i){Promise.all([import("./userGuideHtml.js"),import("./renderWaferMap.js"),import("./renderWaferGallery.js")]).then(([s,t,n])=>{openUserGuideWindow({buildWaferMap:G,renderWaferMap:t.renderWaferMap,renderWaferGallery:n.renderWaferGallery,analyzeWaferMap:M},s.USER_GUIDE_HTML,e,i)})}export function openReportModal(e,i){const s=i?.ownerDocument??document,t=s.createElement("iframe");t.srcdoc=e,t.title="Summary report",Object.assign(t.style,{border:"0",width:"100%",flex:"1",minHeight:"0",background:"#fff"});const n=s.createElement("div");Object.assign(n.style,{display:"flex",justifyContent:"flex-end",padding:"6px 14px",borderBottom:`1px solid ${f.menuBorder}`,flexShrink:"0"});const r=s.createElement("button");r.type="button",H(r,"bare"),r.textContent="Open as full page \u2197",Object.assign(r.style,{border:"none",background:"none",color:f.iconActive,cursor:"pointer",fontSize:$.body,textDecoration:"underline",padding:"0"}),r.addEventListener("click",()=>N(e)),n.appendChild(r);const a=s.defaultView??window,o=c=>{if(c.source!==t.contentWindow)return;const p=c.data?.wmapFinding;typeof p=="string"&&i?.onFinding?.(p,l)};i?.onFinding&&a.addEventListener("message",o);const l=R({title:"Summary report",anchor:i?.anchor,ownerDocument:s,printable:()=>t.contentWindow?.print(),onClose:()=>a.removeEventListener("message",o)});return l.contentWrap.style.flexDirection="column",l.contentWrap.appendChild(n),l.contentWrap.appendChild(t),l}
@@ -41,6 +41,22 @@ export interface IdentityHeaderController {
41
41
  collapse(): void;
42
42
  destroy(): void;
43
43
  }
44
+ /**
45
+ * The overlay that holds a wafer's full metadata while its identity header is
46
+ * expanded: absolutely positioned over the top of the nearest `position:
47
+ * relative` ancestor, `display: none` until opened. Shared by the single-wafer
48
+ * header and the gallery's per-wafer views.
49
+ */
50
+ export declare function createMetaPanelEl(doc: Document): HTMLDivElement;
51
+ /**
52
+ * Make `toggleEl` (a label row the caller has already built) expand into the
53
+ * wafer's full metadata: appends the chevron, the hover, the ARIA state and
54
+ * the click/keyboard toggle, and returns the panel for the caller to mount in
55
+ * a `position: relative` ancestor. Returns `null`, touching nothing, when the
56
+ * wafer has no metadata to show. The fixed-label counterpart of
57
+ * `createIdentityHeader`, for views whose label never changes.
58
+ */
59
+ export declare function attachExpandableMetadata(doc: Document, toggleEl: HTMLElement, label: string, metadata: WaferMetadata | null | undefined): HTMLDivElement | null;
44
60
  export declare function collapsedLabel(meta: WaferMetadata, lotStack: IdentityHeaderLotStack | undefined): string | undefined;
45
61
  export declare function createIdentityHeader(label: string, metadata: WaferMetadata | null | undefined, opts?: IdentityHeaderOptions): IdentityHeaderController;
46
62
  //# sourceMappingURL=identityHeader.d.ts.map
@@ -1 +1 @@
1
- import{metadataEntries as C,buildCompactMetadataRows as W}from"./summaryPanel.js";import{prettyKey as A}from"../stats/facets.js";import{SHADOW as L,LEADING as k,SPACE as w,FONT as x,CLR as E,Z_ABOVE as M,wireExpandToggle as j}from"./toolbar.js";export function collapsedLabel(r,a){if(a)return a.aggrMethod?`${a.lotSize} wafers \xB7 ${a.aggrMethod}`:`${a.lotSize} wafers`;if(r.lot&&r.waferId!==void 0&&r.waferId!==""){const s=String(r.lot),t=String(r.waferId);return t.includes(s)?t:`${s} \xB7 ${t}`}for(const s of["lot","waferId","product","testProgram"]){const t=r[s];if(t!=null&&t!=="")return String(t)}}export function createIdentityHeader(r,a,s={}){let t=r,b=a??{},o=s.lotStack,i=!1,m=!1;const c=s.ownerDocument??document,e=c.createElement("div");Object.assign(e.style,{display:"flex",alignItems:"center",gap:w.xs,minWidth:"0"});const u=c.createElement("span");Object.assign(u.style,{fontWeight:"700",fontSize:x.sub,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}),e.appendChild(u);const y=c.createElement("span");Object.assign(y.style,{fontSize:x.body,lineHeight:k.none,color:E.label,flexShrink:"0",display:"none"}),e.appendChild(y);const $=4,d=c.createElement("span");Object.assign(d.style,{fontSize:x.body,color:E.label,whiteSpace:"nowrap",overflow:"hidden",flexShrink:"0",display:"none",marginLeft:w.sm}),e.appendChild(d);const l=c.createElement("div");l.dataset.wmapMetaPanel="1",Object.assign(l.style,{position:"absolute",top:"0",left:"0",right:"0",zIndex:M,background:E.menuBg,borderBottom:`1px solid ${E.menuBorder}`,boxShadow:L.menu,padding:`${w.md} ${w.lg}`,fontSize:x.body,display:"none"});function S(){return C(b).length>0||!!o}function O(){return o?[]:C(b).filter(([,n])=>!t.includes(n))}function z(){return e.clientWidth?u.scrollWidth>u.clientWidth||d.scrollWidth>d.clientWidth||e.scrollWidth>e.clientWidth:!0}function g(){u.textContent=t;const n=O();d.textContent=n.map(([p,h])=>`${A(p)}: ${h}`).join(" \xB7 "),d.style.display=n.length>0?"":"none",m=n.length>0&&n.length<=$&&!z(),d.style.display=m?"":"none";const f=S()&&!m;if(y.style.display=f?"":"none",e.style.cursor=f?"pointer":"",f||(i=!1),y.textContent=i?"\u25B4":"\u25BE",l.style.display=i?"block":"none",f?(e.setAttribute("aria-expanded",String(i)),e.setAttribute("aria-label",`Wafer info: ${t||"this wafer"}. ${i?"Click to collapse.":"Click to expand."}`)):(e.removeAttribute("aria-expanded"),e.removeAttribute("aria-label")),l.innerHTML="",i){if(o){const h=c.createElement("div");Object.assign(h.style,{fontWeight:"600",marginBottom:"3px"}),h.textContent=o.aggrMethod?`${o.lotSize} wafers stacked \xB7 ${o.aggrMethod}`:`${o.lotSize} wafers stacked`,l.appendChild(h)}const p=W(b);p&&l.appendChild(p)}}const I=j(e,n=>{!S()||m||(i=n,g())});g();const v=typeof ResizeObserver>"u"?null:new ResizeObserver(()=>g());return v?.observe(e),{wrap:e,metaPanel:l,update(n,f,p){t=n,b=f??{},o=p,g()},isEmpty(){return!t&&!S()},collapse(){i&&(i=!1,g())},destroy(){v?.disconnect(),I.destroy(),e.remove(),l.remove()}}}
1
+ import{metadataEntries as v,buildCompactMetadataRows as $}from"./summaryPanel.js";import{prettyKey as M}from"../stats/facets.js";import{SHADOW as j,LEADING as E,SPACE as C,FONT as g,CLR as x,RADIUS as L,Z_ABOVE as H,wireExpandToggle as z,wireControlHover as P}from"./toolbar.js";export function createMetaPanelEl(i){const e=i.createElement("div");return Object.assign(e.style,{position:"absolute",top:"0",left:"0",right:"0",zIndex:H,background:x.menuBg,borderBottom:`1px solid ${x.menuBorder}`,boxShadow:j.menu,padding:`${C.md} ${C.lg}`,fontSize:g.body,display:"none"}),e}export function attachExpandableMetadata(i,e,s,t){const d=t??{};if(v(d).length===0)return null;const a=i.createElement("span");Object.assign(a.style,{fontSize:g.body,lineHeight:E.none,color:x.label,flexShrink:"0"}),a.textContent="\u25BE",e.appendChild(a),Object.assign(e.style,{cursor:"pointer",borderRadius:L.control}),P(e,"bare");const r=createMetaPanelEl(i);r.dataset.wmapCardMetaPanel="1";const c=$(d);return c&&r.appendChild(c),e.setAttribute("aria-expanded","false"),e.setAttribute("aria-label",`Wafer info for ${s||"this card"}. Click to expand.`),z(e,o=>{a.textContent=o?"\u25B4":"\u25BE",e.setAttribute("aria-expanded",String(o)),r.style.display=o?"block":"none"}),r}export function collapsedLabel(i,e){if(e)return e.aggrMethod?`${e.lotSize} wafers \xB7 ${e.aggrMethod}`:`${e.lotSize} wafers`;if(i.lot&&i.waferId!==void 0&&i.waferId!==""){const s=String(i.lot),t=String(i.waferId);return t.includes(s)?t:`${s} \xB7 ${t}`}for(const s of["lot","waferId","product","testProgram"]){const t=i[s];if(t!=null&&t!=="")return String(t)}}export function createIdentityHeader(i,e,s={}){let t=i,d=e??{},a=s.lotStack,r=!1,c=!1;const o=s.ownerDocument??document,n=o.createElement("div");Object.assign(n.style,{display:"flex",alignItems:"center",gap:C.xs,minWidth:"0"});const h=o.createElement("span");Object.assign(h.style,{fontWeight:"700",fontSize:g.sub,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}),n.appendChild(h);const w=o.createElement("span");Object.assign(w.style,{fontSize:g.body,lineHeight:E.none,color:x.label,flexShrink:"0",display:"none"}),n.appendChild(w);const A=4,p=o.createElement("span");Object.assign(p.style,{fontSize:g.body,color:x.label,whiteSpace:"nowrap",overflow:"hidden",flexShrink:"0",display:"none",marginLeft:C.sm}),n.appendChild(p);const f=createMetaPanelEl(o);f.dataset.wmapMetaPanel="1";function S(){return v(d).length>0||!!a}function I(){return a?[]:v(d).filter(([,l])=>!t.includes(l))}function W(){return n.clientWidth?h.scrollWidth>h.clientWidth||p.scrollWidth>p.clientWidth||n.scrollWidth>n.clientWidth:!0}function m(){h.textContent=t;const l=I();p.textContent=l.map(([b,y])=>`${M(b)}: ${y}`).join(" \xB7 "),p.style.display=l.length>0?"":"none",c=l.length>0&&l.length<=A&&!W(),p.style.display=c?"":"none";const u=S()&&!c;if(w.style.display=u?"":"none",n.style.cursor=u?"pointer":"",u||(r=!1),w.textContent=r?"\u25B4":"\u25BE",f.style.display=r?"block":"none",u?(n.setAttribute("aria-expanded",String(r)),n.setAttribute("aria-label",`Wafer info: ${t||"this wafer"}. ${r?"Click to collapse.":"Click to expand."}`)):(n.removeAttribute("aria-expanded"),n.removeAttribute("aria-label")),f.innerHTML="",r){if(a){const y=o.createElement("div");Object.assign(y.style,{fontWeight:"600",marginBottom:"3px"}),y.textContent=a.aggrMethod?`${a.lotSize} wafers stacked \xB7 ${a.aggrMethod}`:`${a.lotSize} wafers stacked`,f.appendChild(y)}const b=$(d);b&&f.appendChild(b)}}const k=z(n,l=>{!S()||c||(r=l,m())});m();const O=typeof ResizeObserver>"u"?null:new ResizeObserver(()=>m());return O?.observe(n),{wrap:n,metaPanel:f,update(l,u,b){t=l,d=u??{},a=b,m()},isEmpty(){return!t&&!S()},collapse(){r&&(r=!1,m())},destroy(){O?.disconnect(),k.destroy(),n.remove(),f.remove()}}}
@@ -11,9 +11,9 @@ export type { WarningsOptions } from './warnings.js';
11
11
  export type { InsightsOptions, InsightsView } from './insightsTab.js';
12
12
  export { setDetachWindowOpener } from './toolbar.js';
13
13
  export type { DetachWindowOpener } from './toolbar.js';
14
- export type { UserGuideExtension } from './toolbar.js';
15
- export { openWaferMapGuide } from './toolbar.js';
14
+ export type { UserGuideExtension } from './guideWindow.js';
15
+ export { openWaferMapGuide } from './guideWindow.js';
16
16
  export { ICONS } from './icons.js';
17
17
  export type { OverlayHandle } from './toolbar.js';
18
- export { openReportModal } from './toolbar.js';
18
+ export { openReportModal } from './guideWindow.js';
19
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- export{WMAP_VERSION,WMAP_BUILD_TIME}from"./version.js";export{renderWaferMap}from"./renderWaferMap.js";export{renderWaferGallery}from"./renderWaferGallery.js";export{collectWarnings,severityOf}from"./warnings.js";export{setDetachWindowOpener}from"./toolbar.js";export{openWaferMapGuide}from"./toolbar.js";export{ICONS}from"./icons.js";export{openReportModal}from"./toolbar.js";
1
+ export{WMAP_VERSION,WMAP_BUILD_TIME}from"./version.js";export{renderWaferMap}from"./renderWaferMap.js";export{renderWaferGallery}from"./renderWaferGallery.js";export{collectWarnings,severityOf}from"./warnings.js";export{setDetachWindowOpener}from"./toolbar.js";export{openWaferMapGuide}from"./guideWindow.js";export{ICONS}from"./icons.js";export{openReportModal}from"./guideWindow.js";
@@ -1,31 +1,37 @@
1
- import type { LotStatsSummary } from '../stats/types.js';
1
+ import type { Die } from '../core/dies.js';
2
+ import type { Wafer } from '../core/wafer.js';
3
+ import type { LotStatsSummary, StatsSummary } from '../stats/types.js';
4
+ import { type FacetItem } from '../stats/facets.js';
5
+ import { type TestDef } from '../renderer/buildWaferMap.js';
2
6
  import type { WaferMapDisplayItem } from './renderWaferGallery.js';
7
+ import { type PlotStore } from './plotStore.js';
8
+ import type { PlotSpec } from '../stats/plotSpec.js';
3
9
  import type { BinColors } from '../renderer/binColors.js';
4
10
  import { type SaveImageHandler, type SaveTextHandler } from './toolbar.js';
5
- import { type SweepSpec } from '../stats/sweep.js';
6
- export type InsightsView = 'overview' | 'distributions' | 'correlation' | 'sweeps';
11
+ import type { SweepSpec } from '../stats/sweep.js';
12
+ export type InsightsView = 'overview' | 'distributions' | 'correlation' | 'sweeps' | 'data' | 'plot';
7
13
  /** Public option shape for `RenderOptions.insights`/`GalleryOptions.insights`. */
8
14
  export interface InsightsOptions {
9
15
  /**
10
- * Show an "Insights" tab in the toolbar. Selecting it replaces the canvas/
16
+ * Show an "Insights" tab in the toolbar and a Maps | Insights switch. Selecting it replaces the canvas/
11
17
  * grid with wmap's own chart suite across three sub-tabs — Overview
12
- * (yield, bins, ring/quadrant yield, test values), Distributions
18
+ * (yield, bins, ring/quadrant yield), Distributions
13
19
  * (process capability, boxplot, histogram), and Correlation (matrix +
14
- * scatter) — plus a fourth, Sweeps, when `sweeps` defines any. Default false.
20
+ * scatter) — a Data tab (the scope as tables: statistics, dies, wafers, with CSV
21
+ * export) and a Plot tab, where the reader's own plots and sweeps live. Default true with a toolbar
22
+ * (a gallery always has one); `false` opts out.
15
23
  */
16
24
  enabled?: boolean;
17
- /** Which sub-tab is shown first. Default 'overview'. `'sweeps'` with no
18
- * `sweeps` defined falls back to 'overview' — there is no such tab to open. */
25
+ /** Which sub-tab is shown first. Default 'overview'. `'sweeps'` opens the Plot tab, where sweeps are drawn. */
19
26
  defaultView?: InsightsView;
20
27
  /**
21
- * Parametric sweeps, one card each in their own Sweeps sub-tab — which appears
22
- * only when this is non-empty.
28
+ * @deprecated Sweeps are plots now: give them in `plots` as sweep plots, or read a sweeps file with
29
+ * `readPlotsFile`. Each entry here is still drawn, as a sweep plot on the Plot tab (and offered on selected dies),
30
+ * under the same `id`; a plot saved under that id takes precedence. The first use logs a notice.
23
31
  *
24
- * A sweep reads an ordered run of tests as a response curve rather than as
25
- * independent tests, and measures the PAIR: where the first two series cross,
26
- * and how far apart they are at given levels. The case it exists for is the
27
- * same quantity measured at a series of power levels, recorded as a block of
28
- * consecutive test numbers, swept up in one block and down in another.
32
+ * A sweep reads an ordered run of tests as a response curve rather than as independent tests, and measures the
33
+ * PAIR: where the first two series cross, and how far apart they are at given levels. It carries no population:
34
+ * the dies it aggregates are whatever the view is scoped to or the reader selected.
29
35
  *
30
36
  * ```ts
31
37
  * insights: { enabled: true, sweeps: [{
@@ -38,41 +44,32 @@ export interface InsightsOptions {
38
44
  * }] }
39
45
  * ```
40
46
  *
41
- * Deliberately carries no population scope of its own: a sweep definition
42
- * says which tests form the curve, and is therefore valid for any population
43
- * and portable between hosts. The dies it aggregates are whatever the
44
- * Insights view is currently scoped to.
45
- *
46
- * The same definitions drive drilldown: a user can select dies on a map and
47
- * right-click (or use the toolbar's "Chart the selection") to sweep just
48
- * those dies. A single map offers this even when `enabled` is off.
49
- *
50
- * **Provisional.** This arrived as one site's request. The mechanism — an
51
- * ordered run of tests read as a curve — is a recurring semiconductor shape
52
- * (shmoo, VDD/temperature sweeps, retention, endurance, IV), which is the case
53
- * for it being library-level; against it is that only one host has asked. It
54
- * is shipping so tsmap can put it in front of that user, and the answer
55
- * decides it: a second sweep-shaped use means it is general and stays, while
56
- * "also measure X, and split by Y" means it is a bespoke chart and belongs
57
- * behind a host-contributed-panel extension point instead. `separationAt` is
58
- * the narrowest part of the surface and the first thing to drop if it is not
59
- * used. Do not widen this shape before that question is settled.
60
- *
61
- * Range strings in `tests` (`"1010..1030"`) are NOT a widening in that sense:
62
- * they are input syntax for the same list of tests, and add nothing to what a
63
- * sweep measures. They were added because ranges are the first thing anyone
64
- * writing a sweep by hand asks for, and they share the derived-test parser so
65
- * the syntax is one syntax library-wide.
47
+ * Range strings in `tests` (`"1010..1030"`) share the derived-test parser, so the syntax is one syntax library-wide.
66
48
  */
67
49
  sweeps?: SweepSpec[];
68
50
  /**
69
- * Offers a "Remove" button on the Sweeps tab's notice about sweeps that name
70
- * none of the data's tests — typically sweeps a host kept from another test
71
- * program. Called with those sweeps' ids; the host drops them and re-renders
72
- * with the remaining `sweeps`. Without it the notice still shows, with no
73
- * button. The library never edits the host's sweep list itself.
51
+ * @deprecated With `sweeps`. Called with the id of a sweep from `sweeps` that the reader deletes on the Plot tab, so the
52
+ * host stops supplying it (otherwise the next render brings it back). Plots deleted from `plots` arrive through
53
+ * `onPlotsChange` like any other.
74
54
  */
75
55
  onRemoveSweeps?: (ids: string[]) => void;
56
+ /**
57
+ * The reader's saved plots: the recipes behind the **Plot** tab, in the order the reader keeps them. The
58
+ * library never stores anything itself: hand the list in here (from a file, `localStorage`, a server) and keep
59
+ * it current from `onPlotsChange`. A plot carries no population, so the same list works on any lot; one that
60
+ * needs a test the open lot lacks is kept and shown greyed with the reason, never dropped.
61
+ */
62
+ plots?: PlotSpec[];
63
+ /**
64
+ * Called with the whole list after the reader adds, edits, duplicates, deletes or imports a plot (a burst of
65
+ * typing is one call). Keep it, and pass it back as `plots` next time.
66
+ */
67
+ onPlotsChange?: (plots: PlotSpec[]) => void;
68
+ /**
69
+ * Choose a plots file to import, resolving to its text, or `null` when cancelled. Without it the Plot tab uses
70
+ * the browser's own file input; a desktop host passes its native dialog.
71
+ */
72
+ onPickPlotsFile?: () => Promise<string | null>;
76
73
  /**
77
74
  * Open the Insights view on mount instead of starting on the map/grid.
78
75
  * Default false — the tab is offered, the map is what you see first.
@@ -97,7 +94,7 @@ export interface InsightsTabDeps {
97
94
  */
98
95
  getBinColors: () => BinColors;
99
96
  /** Read fresh each render — used by the Overview tab's ring/quadrant regional yield cards. Default 4. */
100
- getRingCount?: () => number;
97
+ getRingCount: () => number;
101
98
  onSaveImage?: SaveImageHandler;
102
99
  /** Optional host hook for the Overview tab's test-values "Export CSV" button — see `saveTextFile` (toolbar.ts). */
103
100
  onSaveText?: SaveTextHandler;
@@ -127,6 +124,11 @@ export interface InsightsTabDeps {
127
124
  * lands on this test), and only consulted for a leaf row of the sole item.
128
125
  */
129
126
  focusTest?: (testNumber: number) => void;
127
+ /**
128
+ * A die row clicked in a table (the Data tab, or a table opened from a chart): leave Insights and show
129
+ * that die on its map. Omit where there is no map to go back to; the rows are then not clickable.
130
+ */
131
+ locateDie?: (die: Die, waferIndex: number | undefined) => void;
130
132
  /** Default sub-tab shown on first render. Default 'overview'. */
131
133
  defaultView?: InsightsView;
132
134
  /** Sweep definitions to render in the Distributions view — see `InsightsOptions.sweeps`. */
@@ -134,15 +136,13 @@ export interface InsightsTabDeps {
134
136
  /** See `InsightsOptions.onRemoveSweeps`. */
135
137
  onRemoveSweeps?: (ids: string[]) => void;
136
138
  /**
137
- * When provided, the tab bar gets a leading "‹ Map"/"‹ Gallery" tab that
138
- * exits Insights back to the host's normal view — one visible navigation
139
- * model (a tab row) instead of relying on the host toolbar's icon-swap
140
- * toggle alone, whose "way back" is discoverable only via tooltip.
139
+ * The live list of saved plots, shared with the drilldown menu so a plot edited here is the one a right-click
140
+ * opens. Created from `plots`/`onPlotsChange` when omitted.
141
141
  */
142
- backTab?: {
143
- label: string;
144
- onBack: () => void;
145
- };
142
+ plotStore?: PlotStore;
143
+ plots?: PlotSpec[];
144
+ onPlotsChange?: (plots: PlotSpec[]) => void;
145
+ onPickPlotsFile?: () => Promise<string | null>;
146
146
  /** Opens the user guide. Rendered as an icon at the end of the tab row —
147
147
  * while Insights is showing, the map toolbar is hidden (it held only a
148
148
  * back-to-gallery button, which the back tab already provides, and this
@@ -184,5 +184,24 @@ export interface InsightsTabHandle {
184
184
  /** Tears down every live panel's observers and removes `el`. */
185
185
  destroy: () => void;
186
186
  }
187
+ type Item = FacetItem & {
188
+ dies: Die[];
189
+ label: string;
190
+ waferIndex: number;
191
+ wafer: Wafer;
192
+ statsSummary?: StatsSummary;
193
+ /** This wafer's OWN pass bins — a lot can mix wafers built with different ones. */
194
+ passBins: readonly number[];
195
+ /** This wafer's OWN test defs, carried through so a scoped population can be
196
+ * reconciled (`mergeTestDefs`) over just the wafers in scope. */
197
+ testDefs?: TestDef[];
198
+ /** The wafer's real identity (`waferIdentityLabel`) — never the positional
199
+ * "Wafer N (no ID)" that `label` falls back to, which in a chart title would
200
+ * read as an ID. */
201
+ identity?: string;
202
+ };
203
+ /** One wafer (or the lot's single stand-in) as the Insights views see it. */
204
+ export type InsightsItem = Item;
187
205
  export declare function createInsightsTab(deps: InsightsTabDeps): InsightsTabHandle;
206
+ export {};
188
207
  //# sourceMappingURL=insightsTab.d.ts.map