@wafertools/wafermap 0.21.1

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 (138) hide show
  1. package/CHANGELOG.md +751 -0
  2. package/LICENSE +21 -0
  3. package/README.md +79 -0
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.js +1 -0
  6. package/dist/packages/canvas-adapter/canvasTheme.d.ts +28 -0
  7. package/dist/packages/canvas-adapter/canvasTheme.js +1 -0
  8. package/dist/packages/canvas-adapter/charts/barPanel.d.ts +52 -0
  9. package/dist/packages/canvas-adapter/charts/barPanel.js +1 -0
  10. package/dist/packages/canvas-adapter/charts/binCluster.d.ts +17 -0
  11. package/dist/packages/canvas-adapter/charts/binCluster.js +1 -0
  12. package/dist/packages/canvas-adapter/charts/boxplot.d.ts +45 -0
  13. package/dist/packages/canvas-adapter/charts/boxplot.js +1 -0
  14. package/dist/packages/canvas-adapter/charts/capability.d.ts +41 -0
  15. package/dist/packages/canvas-adapter/charts/capability.js +1 -0
  16. package/dist/packages/canvas-adapter/charts/chartShell.d.ts +156 -0
  17. package/dist/packages/canvas-adapter/charts/chartShell.js +1 -0
  18. package/dist/packages/canvas-adapter/charts/correlation.d.ts +35 -0
  19. package/dist/packages/canvas-adapter/charts/correlation.js +1 -0
  20. package/dist/packages/canvas-adapter/charts/histogram.d.ts +30 -0
  21. package/dist/packages/canvas-adapter/charts/histogram.js +1 -0
  22. package/dist/packages/canvas-adapter/charts/index.d.ts +16 -0
  23. package/dist/packages/canvas-adapter/charts/index.js +1 -0
  24. package/dist/packages/canvas-adapter/charts/palette.d.ts +26 -0
  25. package/dist/packages/canvas-adapter/charts/palette.js +1 -0
  26. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +18 -0
  27. package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -0
  28. package/dist/packages/canvas-adapter/charts/scatter.d.ts +37 -0
  29. package/dist/packages/canvas-adapter/charts/scatter.js +1 -0
  30. package/dist/packages/canvas-adapter/icons.d.ts +2 -0
  31. package/dist/packages/canvas-adapter/icons.js +42 -0
  32. package/dist/packages/canvas-adapter/index.d.ts +12 -0
  33. package/dist/packages/canvas-adapter/index.js +1 -0
  34. package/dist/packages/canvas-adapter/insightsTab.d.ts +76 -0
  35. package/dist/packages/canvas-adapter/insightsTab.js +1 -0
  36. package/dist/packages/canvas-adapter/metadataBadge.d.ts +26 -0
  37. package/dist/packages/canvas-adapter/metadataBadge.js +1 -0
  38. package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +181 -0
  39. package/dist/packages/canvas-adapter/renderWaferGallery.js +20 -0
  40. package/dist/packages/canvas-adapter/renderWaferMap.d.ts +316 -0
  41. package/dist/packages/canvas-adapter/renderWaferMap.js +1 -0
  42. package/dist/packages/canvas-adapter/summaryPanel.d.ts +262 -0
  43. package/dist/packages/canvas-adapter/summaryPanel.js +3 -0
  44. package/dist/packages/canvas-adapter/toCanvas.d.ts +106 -0
  45. package/dist/packages/canvas-adapter/toCanvas.js +1 -0
  46. package/dist/packages/canvas-adapter/toolbar.d.ts +496 -0
  47. package/dist/packages/canvas-adapter/toolbar.js +9 -0
  48. package/dist/packages/canvas-adapter/userGuideHtml.d.ts +2 -0
  49. package/dist/packages/canvas-adapter/userGuideHtml.js +968 -0
  50. package/dist/packages/canvas-adapter/version.d.ts +3 -0
  51. package/dist/packages/canvas-adapter/version.js +1 -0
  52. package/dist/packages/core/aggregates.d.ts +54 -0
  53. package/dist/packages/core/aggregates.js +1 -0
  54. package/dist/packages/core/classify.d.ts +24 -0
  55. package/dist/packages/core/classify.js +1 -0
  56. package/dist/packages/core/dies.d.ts +92 -0
  57. package/dist/packages/core/dies.js +1 -0
  58. package/dist/packages/core/index.d.ts +13 -0
  59. package/dist/packages/core/index.js +1 -0
  60. package/dist/packages/core/inference/grid.d.ts +35 -0
  61. package/dist/packages/core/inference/grid.js +1 -0
  62. package/dist/packages/core/inference/index.d.ts +7 -0
  63. package/dist/packages/core/inference/index.js +1 -0
  64. package/dist/packages/core/inference/pitch.d.ts +26 -0
  65. package/dist/packages/core/inference/pitch.js +1 -0
  66. package/dist/packages/core/inference/wafer.d.ts +30 -0
  67. package/dist/packages/core/inference/wafer.js +1 -0
  68. package/dist/packages/core/metadata.d.ts +31 -0
  69. package/dist/packages/core/metadata.js +1 -0
  70. package/dist/packages/core/probe.d.ts +16 -0
  71. package/dist/packages/core/probe.js +1 -0
  72. package/dist/packages/core/reticle.d.ts +68 -0
  73. package/dist/packages/core/reticle.js +1 -0
  74. package/dist/packages/core/transforms.d.ts +142 -0
  75. package/dist/packages/core/transforms.js +1 -0
  76. package/dist/packages/core/utils.d.ts +60 -0
  77. package/dist/packages/core/utils.js +1 -0
  78. package/dist/packages/core/wafer.d.ts +45 -0
  79. package/dist/packages/core/wafer.js +1 -0
  80. package/dist/packages/renderer/buildView.d.ts +420 -0
  81. package/dist/packages/renderer/buildView.js +1 -0
  82. package/dist/packages/renderer/buildWaferMap.d.ts +543 -0
  83. package/dist/packages/renderer/buildWaferMap.js +1 -0
  84. package/dist/packages/renderer/colorMap.d.ts +46 -0
  85. package/dist/packages/renderer/colorMap.js +1 -0
  86. package/dist/packages/renderer/colorSchemes.d.ts +40 -0
  87. package/dist/packages/renderer/colorSchemes.js +1 -0
  88. package/dist/packages/renderer/fmt.d.ts +30 -0
  89. package/dist/packages/renderer/fmt.js +1 -0
  90. package/dist/packages/renderer/index.d.ts +7 -0
  91. package/dist/packages/renderer/index.js +1 -0
  92. package/dist/packages/stats/analyzeWaferLot.d.ts +5 -0
  93. package/dist/packages/stats/analyzeWaferLot.js +1 -0
  94. package/dist/packages/stats/analyzeWaferMap.d.ts +13 -0
  95. package/dist/packages/stats/analyzeWaferMap.js +1 -0
  96. package/dist/packages/stats/binPareto.d.ts +49 -0
  97. package/dist/packages/stats/binPareto.js +1 -0
  98. package/dist/packages/stats/boxplot.d.ts +39 -0
  99. package/dist/packages/stats/boxplot.js +1 -0
  100. package/dist/packages/stats/capability.d.ts +55 -0
  101. package/dist/packages/stats/capability.js +1 -0
  102. package/dist/packages/stats/clusterDetection.d.ts +18 -0
  103. package/dist/packages/stats/clusterDetection.js +1 -0
  104. package/dist/packages/stats/correlation.d.ts +55 -0
  105. package/dist/packages/stats/correlation.js +1 -0
  106. package/dist/packages/stats/facets.d.ts +74 -0
  107. package/dist/packages/stats/facets.js +1 -0
  108. package/dist/packages/stats/filterFindings.d.ts +9 -0
  109. package/dist/packages/stats/filterFindings.js +1 -0
  110. package/dist/packages/stats/findingsNarrative.d.ts +3 -0
  111. package/dist/packages/stats/findingsNarrative.js +1 -0
  112. package/dist/packages/stats/histogram.d.ts +42 -0
  113. package/dist/packages/stats/histogram.js +1 -0
  114. package/dist/packages/stats/index.d.ts +27 -0
  115. package/dist/packages/stats/index.js +1 -0
  116. package/dist/packages/stats/math.d.ts +10 -0
  117. package/dist/packages/stats/math.js +1 -0
  118. package/dist/packages/stats/patternClassification.d.ts +107 -0
  119. package/dist/packages/stats/patternClassification.js +1 -0
  120. package/dist/packages/stats/regions.d.ts +57 -0
  121. package/dist/packages/stats/regions.js +1 -0
  122. package/dist/packages/stats/renderFindingsReport.d.ts +7 -0
  123. package/dist/packages/stats/renderFindingsReport.js +41 -0
  124. package/dist/packages/stats/renderSummaryReport.d.ts +60 -0
  125. package/dist/packages/stats/renderSummaryReport.js +68 -0
  126. package/dist/packages/stats/reportHtml.d.ts +42 -0
  127. package/dist/packages/stats/reportHtml.js +301 -0
  128. package/dist/packages/stats/scatter.d.ts +24 -0
  129. package/dist/packages/stats/scatter.js +1 -0
  130. package/dist/packages/stats/types.d.ts +269 -0
  131. package/dist/packages/stats/types.js +1 -0
  132. package/dist/packages/stats/yield.d.ts +56 -0
  133. package/dist/packages/stats/yield.js +1 -0
  134. package/dist/packages/worker/index.d.ts +44 -0
  135. package/dist/packages/worker/index.js +1 -0
  136. package/dist/packages/worker/wafermap.worker.d.ts +44 -0
  137. package/dist/packages/worker/wafermap.worker.js +1 -0
  138. package/package.json +122 -0
@@ -0,0 +1,496 @@
1
+ import type { PlotMode } from '../renderer/buildView.js';
2
+ import type { Die } from '../core/dies.js';
3
+ import type { TestDef, MetadataFieldDef } from '../renderer/buildWaferMap.js';
4
+ import type { StatsSeverity } from '../stats/types.js';
5
+ export declare function logWmapVersionOnce(): void;
6
+ export declare const WMAP_TOKEN_NAMES: readonly ["z", "icon", "icon-hover", "icon-active", "bg-hover", "bg-active", "separator", "surface", "border", "menu-hover", "menu-active", "panel-bg", "text-muted", "text", "text-strong", "warn-bg", "warn-border", "warn-text", "info-bg", "info-text", "selected", "finding-indicator", "canvas-bg"];
7
+ export type WmapTokenName = (typeof WMAP_TOKEN_NAMES)[number];
8
+ export declare const CLR: {
9
+ icon: string;
10
+ iconHover: string;
11
+ iconActive: string;
12
+ bgHover: string;
13
+ bgActive: string;
14
+ separator: string;
15
+ menuBg: string;
16
+ menuBorder: string;
17
+ menuHover: string;
18
+ menuActive: string;
19
+ panelBg: string;
20
+ label: string;
21
+ value: string;
22
+ text: string;
23
+ warnBg: string;
24
+ warnBorder: string;
25
+ warnText: string;
26
+ infoBg: string;
27
+ infoText: string;
28
+ findingHighlight: string;
29
+ findingIndicator: string;
30
+ };
31
+ /**
32
+ * Findings-severity → colour, shared by summaryPanel.ts (severity dots/borders
33
+ * on the findings list) and renderWaferGallery.ts (its own copy drifted from
34
+ * this one only by name, never by value — hoisted here so the two can't
35
+ * silently diverge again).
36
+ */
37
+ export declare function sevColor(s: StatsSeverity): string;
38
+ export declare const ROTATIONS: Array<0 | 90 | 180 | 270>;
39
+ export declare const INLINE_TEST_LIMIT = 6;
40
+ /** Above this many rows, a scrollable list (test cascade submenu, chart test picker) gets a filter box. */
41
+ export declare const MENU_SEARCH_THRESHOLD = 8;
42
+ export declare const DEFAULT_OVERLAY_Z = 6000;
43
+ /** `z-index` value string for a base-level overlay (menus, modal backdrop). */
44
+ export declare const Z_BASE = "var(--wmap-z, 6000)";
45
+ /** One above base — tooltips, submenus, the modal box above its backdrop. */
46
+ export declare const Z_ABOVE = "calc(var(--wmap-z, 6000) + 1)";
47
+ /** Two above base — controls that must sit above a maximized modal box. */
48
+ export declare const Z_ABOVE2 = "calc(var(--wmap-z, 6000) + 2)";
49
+ /**
50
+ * Apply a host-supplied `zIndex` to wmap's overlays by writing `--wmap-z` onto
51
+ * `document.documentElement`. Returns a disposer that restores the previous
52
+ * value (called on controller `destroy`). No-op when `zIndex` is undefined, so
53
+ * the safe high default in `Z_BASE`/`Z_ABOVE` applies.
54
+ *
55
+ * `--wmap-z` is set on the document root (not the render container) because
56
+ * several overlays append to `document.body`, outside the container's subtree,
57
+ * and must still inherit the value.
58
+ */
59
+ export declare function applyOverlayZ(zIndex: number | undefined): () => void;
60
+ /** Copy every `--wmap-*` custom property resolved on `src` onto `dest`, so an
61
+ * element in a different document (e.g. a detached popup's `documentElement`)
62
+ * renders with the same theme as the host page instead of silently reverting
63
+ * to light-mode defaults. */
64
+ export declare function copyWmapThemeTokens(src: Element, dest: HTMLElement): void;
65
+ /**
66
+ * Keeps a popup's copied `--wmap-*` tokens (`copyWmapThemeTokens`, above) in
67
+ * sync with *later* host theme changes. A one-time copy at open time is a
68
+ * snapshot, not a live link (CSS custom properties can't inherit across
69
+ * documents) — if the host flips theme while the popup is still open (e.g. a
70
+ * dark-mode toggle re-pointing its `--wmap-*` values), nothing would
71
+ * otherwise re-copy them. Watches attribute changes on `themeSource` and
72
+ * `<html>` — the two most common places a host actually changes theme (a
73
+ * toggled class or inline style) — plus the OS light/dark preference. This
74
+ * can't observe a change on some other ancestor further up that isn't
75
+ * `themeSource` or `<html>` itself, but that already covers the realistic
76
+ * cases; watching the whole ancestor chain generically is not something
77
+ * anything else in this codebase does either. `onResync`, if given, fires
78
+ * alongside each re-copy — e.g. to force a live map inside the popup to
79
+ * redraw so its canvas re-resolves the same palette (CSS custom properties
80
+ * update DOM-driven chrome automatically, but a canvas only re-resolves
81
+ * `--wmap-*` at its next draw). Returns a disposer to call when the popup closes.
82
+ */
83
+ export declare function syncWmapPopupTheme(themeSource: Element, popupDocumentElement: HTMLElement, onResync?: () => void): () => void;
84
+ export type DetachWindowOpener = (label: string) => Window | null;
85
+ /** Register a custom opener for gallery card detach windows — e.g. one backed
86
+ * by a Tauri `WebviewWindow`, for hosts where `window.open` is blocked. The
87
+ * opener receives the card's label and must return a `Window`-like handle
88
+ * (with a usable `.document`) to build into, or `null` to decline (treated
89
+ * exactly like a blocked popup — the detach silently no-ops). */
90
+ export declare function setDetachWindowOpener(opener: DetachWindowOpener | null): void;
91
+ /** Open a blank window for a detached gallery card. Uses the registered
92
+ * `setDetachWindowOpener` opener when present, else falls back to a plain
93
+ * `window.open` popup.
94
+ *
95
+ * Known cosmetic quirk: since this never navigates away from `about:blank`,
96
+ * Chrome's window-title algorithm shows "{title} - Chrome" in the OS window/
97
+ * tab title instead of just the title we set (an un-navigated about:blank
98
+ * window is treated as titleless regardless of `document.title`). Navigating
99
+ * to a real URL would fix it but means either re-running the host page's own
100
+ * script inside the popup (real risk of double side effects) or requiring a
101
+ * dedicated blank same-origin asset — not worth it for a cosmetic issue. */
102
+ export declare function openDetachWindow(label: string): Window | null;
103
+ export declare const MODE_LABELS: Record<PlotMode, string>;
104
+ export declare const BIN_LEGEND_MODES: Set<PlotMode>;
105
+ export declare const STACKED_MODES: Set<PlotMode>;
106
+ /**
107
+ * Host hook for persisting a rendered PNG. When supplied, the toolbar calls this
108
+ * instead of triggering a browser `<a download>`, letting embedded hosts (Tauri,
109
+ * Electron, WebView2) route the blob through a native save dialog or upload.
110
+ * Receives the PNG `blob` and a `suggestedName` (already includes `.png`).
111
+ */
112
+ export type SaveImageHandler = (blob: Blob, suggestedName: string) => void | Promise<void>;
113
+ /**
114
+ * Persist a rendered PNG blob. Routes through the host `onSaveImage` hook when
115
+ * provided; otherwise falls back to a browser `<a download>` click. Single
116
+ * source of truth for the save action across `renderWaferMap` and
117
+ * `renderWaferGallery` — neither should reimplement the `<a download>` dance.
118
+ *
119
+ * @param blob the PNG blob (e.g. from `canvas.toBlob`)
120
+ * @param filename filename without extension (e.g. `'wafermap'`)
121
+ * @param onSaveImage optional host hook; when present, bypasses `<a download>`
122
+ */
123
+ export declare function saveImageBlob(blob: Blob, filename: string, onSaveImage?: SaveImageHandler): void;
124
+ /**
125
+ * Host hook for saving a text file (CSV, etc). Mirrors `SaveImageHandler` —
126
+ * when provided, `saveTextFile` calls this instead of triggering a browser
127
+ * `<a download>`, letting embedded hosts (Tauri, Electron, WebView2) route
128
+ * the text through a native save dialog. Receives the raw `text`, a
129
+ * `suggestedName` (already includes its extension), and the `mimeType`.
130
+ */
131
+ export type SaveTextHandler = (text: string, suggestedName: string, mimeType: string) => void | Promise<void>;
132
+ /**
133
+ * Persist a text file (e.g. a CSV export). Routes through the host
134
+ * `onSaveText` hook when provided; otherwise falls back to a browser
135
+ * `<a download>` click — the same `<a download>` dance `saveImageBlob` uses
136
+ * for PNG saves, which is silently a no-op in Tauri/Electron/WebView2 (see
137
+ * `saveImageBlob`'s own doc comment), hence the hook.
138
+ *
139
+ * @param text the file contents
140
+ * @param filename filename including extension (e.g. `'test-values.csv'`)
141
+ * @param mimeType e.g. `'text/csv'`
142
+ * @param onSaveText optional host hook; when present, bypasses `<a download>`
143
+ */
144
+ export declare function saveTextFile(text: string, filename: string, mimeType: string, onSaveText?: SaveTextHandler): void;
145
+ /**
146
+ * Run `fn` on the next animation frame, falling back to a macrotask in
147
+ * environments without `requestAnimationFrame` (e.g. JSDOM under tests).
148
+ * Used to defer focus moves until the element is laid out, and to schedule
149
+ * re-renders after a resize.
150
+ *
151
+ * Accepts an explicit `ownerWindow` — a bare `requestAnimationFrame` reference
152
+ * resolves to whichever window this MODULE was first evaluated in (the host
153
+ * page's), not necessarily the window whose layout the caller actually cares
154
+ * about. For a gallery card detached into its own popup window, scheduling
155
+ * against the opener's rAF loop means the callback only fires when the OPENER
156
+ * repaints (e.g. on mouse movement there) — not when the popup itself does,
157
+ * which reads as "the map doesn't resize until I move focus back to the main
158
+ * window." Passing the popup's own `window` fixes this.
159
+ */
160
+ export declare function nextFrame(fn: () => void, ownerWindow?: Window): void;
161
+ export declare function overlayRootFor(anchor: Element): Element;
162
+ /** The shared tooltip element for `doc` (default: the host page's own
163
+ * document), lazily created and appended to `doc.body`. */
164
+ export declare function getTooltip(doc?: Document): HTMLDivElement;
165
+ /** Hide `doc`'s shared tooltip. Deliberately does NOT re-home it to `doc.body`
166
+ * — it stays wherever `reparentTooltip()` last placed it (e.g. inside an open
167
+ * floating window's box). A non-modal floating window can stay open for many
168
+ * ordinary hover/unhover cycles; snapping the tooltip back to `<body>` on
169
+ * every hide (as this used to do) meant the very first unhover after opening
170
+ * a floating window silently evicted the tooltip from the window's stacking
171
+ * context, and it was never reparented back in — every subsequent hover in
172
+ * that window then rendered the tooltip at `<body>`'s z-index, BEHIND the
173
+ * window. Callers that actually close an overlay (see `openOverlay`'s
174
+ * `close()`) are responsible for reparenting the tooltip back to `<body>`
175
+ * themselves once the box that owned it is really going away. */
176
+ export declare function hideTooltip(doc?: Document): void;
177
+ /**
178
+ * Move `parent`'s document's shared tooltip into `parent` (e.g. a maximized
179
+ * modal box that creates its own stacking/overflow context). Pass nothing to
180
+ * re-home the host page's tooltip to `document.body`.
181
+ */
182
+ export declare function reparentTooltip(parent?: HTMLElement): void;
183
+ /**
184
+ * Position the shared tooltip near (clientX, clientY), re-homing it under
185
+ * `anchor`'s nearest overlay root first (see overlayRootFor) if it isn't there
186
+ * already. Re-resolving on every call is deliberate and cheap (a short
187
+ * ancestor walk) — anchor's overlay root can change over the tooltip's
188
+ * lifetime (e.g. a host opens its own <dialog> around an already-rendered
189
+ * map), and the `parentElement !==` guard means the DOM is only actually
190
+ * touched when the root has changed, not on every mousemove.
191
+ */
192
+ export declare function positionTooltip(tooltip: HTMLDivElement, anchor: Element, clientX: number, clientY: number): void;
193
+ export interface ToolbarHelpers {
194
+ makeBtn(iconKey: string, label: string, onClick: () => void): HTMLButtonElement;
195
+ setActive(btn: HTMLButtonElement, active: boolean): void;
196
+ makeSep(): HTMLDivElement;
197
+ makeMenuRow(label: string, active: boolean, indent: boolean, onClick: (e: MouseEvent) => void): HTMLDivElement;
198
+ makeMenuSection(label: string): HTMLDivElement;
199
+ makeDropdown<T extends string>(iconKey: string, title: string, getItems: () => Array<{
200
+ value: T;
201
+ label: string;
202
+ }>, getCurrent: () => T, onPick: (v: T) => void): HTMLButtonElement;
203
+ /**
204
+ * Create a button that opens a persistent checkbox-style dropdown menu.
205
+ * `getRows` is called fresh on each open/replace so active state is always current.
206
+ * `onSync` is called after each row click to update the button's active highlight.
207
+ */
208
+ makeCheckMenuBtn(iconKey: string, label: string, getRows: () => CheckMenuRow[], onSync: (btn: HTMLButtonElement) => void): HTMLButtonElement;
209
+ closeOpenMenu(e: MouseEvent): void;
210
+ /** Read/write the shared open-menu slot — used by custom menus that can't go through makeDropdown. */
211
+ getOpenMenu(): HTMLDivElement | null;
212
+ setOpenMenu(menu: HTMLDivElement | null): void;
213
+ }
214
+ export type ModeEntry = {
215
+ plotMode: PlotMode;
216
+ activeTest?: number;
217
+ activeMetadataKey?: string;
218
+ label: string;
219
+ logScale?: boolean;
220
+ };
221
+ /**
222
+ * Which plot modes the data actually supports — the single derivation shared by
223
+ * `renderWaferMap` and `renderWaferGallery`.
224
+ *
225
+ * This is a correctness surface, not a convenience: the library's rule is that UI
226
+ * controls must derive validity from the data the library built, never from a
227
+ * caller-supplied flag. Two copies of that derivation can silently disagree about
228
+ * which modes are offered (and adding a new plot mode means remembering both),
229
+ * so it lives here with the rest of the shared toolbar primitives.
230
+ *
231
+ * `includeStacked` is the one genuine difference between the two callers, and is
232
+ * a deliberate parameter rather than an inferred value: stacked modes are valid
233
+ * for `renderWaferMap` only when the result was built from a `lotStack`
234
+ * (`view.isLotStack`), whereas `renderWaferGallery` aggregates across its own
235
+ * items and can always offer them.
236
+ */
237
+ export declare function buildDataModeEntries(dies: Die[], testDefs: TestDef[] | undefined, options: {
238
+ includeStacked: boolean;
239
+ }): {
240
+ testEntries: ModeEntry[];
241
+ binEntries: ModeEntry[];
242
+ stackedEntries: ModeEntry[];
243
+ };
244
+ /**
245
+ * True when at least one die actually carries a value for this metadata key.
246
+ * Presence in `metadataFields` is an opt-in declaration; this is the "and there
247
+ * is data for it" half of the same check, shared so the single map and the
248
+ * gallery can't disagree on which fields to offer.
249
+ */
250
+ export declare function metadataKeyHasData(dies: Die[], key: string): boolean;
251
+ /** A configured metadata field → its mode-menu entry, with the shared label fallback. */
252
+ export declare function metadataModeEntry(field: MetadataFieldDef): ModeEntry;
253
+ /**
254
+ * A text input for filtering a list of rows by their label text, meant to sit
255
+ * at the top of a scrollable menu/list once it gets long enough that scanning
256
+ * beats scrolling. Shared by the plot-mode test-value cascade submenu here and
257
+ * the chart test picker (`chartShell.ts`'s `makeSearchableTestSelect`) so both
258
+ * get the same filter-as-you-type box instead of building it twice.
259
+ *
260
+ * `onFilter` receives the lowercased query on every keystroke; the caller
261
+ * owns showing/hiding its own rows. Autofocuses on mount. Click/keydown are
262
+ * stopped from bubbling so typing (including Space/arrow keys) lands in the
263
+ * input rather than triggering the host menu's own keyboard navigation.
264
+ */
265
+ export declare function makeMenuSearchBox(onFilter: (query: string) => void, placeholder?: string): HTMLInputElement;
266
+ /**
267
+ * Build the plot-mode dropdown menu element.
268
+ * Shared between renderWaferMap and renderWaferGallery.
269
+ * The caller provides data-derived entry arrays and pick/active callbacks.
270
+ */
271
+ export declare function buildModeMenuEl(anchorRect: DOMRect, testEntries: ModeEntry[], binEntries: ModeEntry[], stackedEntries: ModeEntry[], isCurrentEntry: (e: ModeEntry) => boolean, pickEntry: (entry: ModeEntry, menu: HTMLElement) => void, helpers: Pick<ToolbarHelpers, 'makeMenuRow' | 'makeMenuSection'>, currentMode: PlotMode, ownerWindow?: Window, metadataEntries?: ModeEntry[]): HTMLDivElement;
272
+ /**
273
+ * Mark a button as a menu trigger and reflect its open/closed state. Call with
274
+ * `expanded` on open/close so assistive tech announces the popup state. Public
275
+ * so the adapters can apply it to mode-menu triggers they mount themselves.
276
+ */
277
+ export declare function markMenuTrigger(btn: HTMLElement, expanded: boolean): void;
278
+ /**
279
+ * Public re-export of the internal roving-focus keyboard wiring, for menus the
280
+ * adapters mount directly (the plot-mode menu). See `wireMenuKeyboard`.
281
+ */
282
+ export declare function wireMenuA11y(menu: HTMLElement, trigger: HTMLElement | null, close: () => void): void;
283
+ export interface ExpandToggleHandle {
284
+ isOpen(): boolean;
285
+ /** Force-close without toggling — used for e.g. a data refresh that should collapse an open panel. */
286
+ close(): void;
287
+ destroy(): void;
288
+ }
289
+ /**
290
+ * Shared "click/Enter/Space toggles an expand/collapse state, Escape or an
291
+ * outside click closes it" wiring — the small interaction pattern behind both
292
+ * the metadata badge's own expand (metadataBadge.ts) and the gallery card
293
+ * header's metadata reveal (renderWaferGallery.ts). `trigger` gets
294
+ * `role="button"`/`tabindex` and the event listeners; `onChange(open)` is
295
+ * called whenever the state actually changes (never redundantly) — the
296
+ * caller owns all the resulting DOM (aria-expanded text, panel visibility,
297
+ * content), this only owns the open/closed boolean and its triggers.
298
+ */
299
+ export declare function wireExpandToggle(trigger: HTMLElement, onChange: (open: boolean) => void): ExpandToggleHandle;
300
+ export type CheckMenuRow = {
301
+ section: string;
302
+ } | {
303
+ label: string;
304
+ active: boolean;
305
+ enabled?: boolean;
306
+ onClick: (e: MouseEvent) => void;
307
+ };
308
+ /**
309
+ * Build a checkbox-style dropdown menu (for overlays/orientation groups).
310
+ * Each row with an `onClick` is a toggleable item; rows with `section` are headers.
311
+ * Stays open after each click so the user can toggle multiple items.
312
+ */
313
+ export declare function buildCheckMenuEl(anchorRect: DOMRect, rows: CheckMenuRow[], helpers: Pick<ToolbarHelpers, 'makeMenuRow' | 'makeMenuSection'>, ownerWindow?: Window): HTMLDivElement;
314
+ export declare function createToolbarHelpers(tooltip: HTMLDivElement): ToolbarHelpers;
315
+ export type OverlayMode = 'modal' | 'window';
316
+ export interface OverlayOptions {
317
+ /** Optional title shown in the header left. */
318
+ title?: string;
319
+ mode: OverlayMode;
320
+ /**
321
+ * Called when the maximized state changes — use to reparent tooltips etc. Uses
322
+ * a CSS maximize (the box grows to fill its backdrop/the viewport), not the
323
+ * real Fullscreen API, for macOS WKWebView compatibility.
324
+ */
325
+ onMaximizeChange?: (isMaximized: boolean, box: HTMLElement) => void;
326
+ /** Called when the overlay is closed. */
327
+ onClose: () => void;
328
+ /**
329
+ * The document to build this overlay into — defaults to the bare global
330
+ * `document`. Pass the triggering element's `ownerDocument` when the
331
+ * caller's own container might live in a different document (e.g.
332
+ * `renderWaferMap`'s own expand modal, whose canvas could be inside a
333
+ * gallery card detached into its own popup window) — otherwise the overlay
334
+ * silently builds into the WRONG document, visibly emptying the popup and
335
+ * popping the box up on the host page instead.
336
+ */
337
+ ownerDocument?: Document;
338
+ /**
339
+ * An element from the triggering render's own live DOM subtree — used to
340
+ * resolve the overlay's root via `overlayRootFor` instead of always
341
+ * `doc.body`. Needed so this overlay itself (not just the tooltip/menus)
342
+ * lands inside a host's own native `<dialog>` (shown via `.showModal()`,
343
+ * which promotes it into the browser's top layer — see `overlayRootFor`'s
344
+ * own doc comment) when the map is embedded inside one; without it, the
345
+ * modal/window box would build straight onto `doc.body`, which sits BEHIND
346
+ * that top-layer dialog regardless of z-index. Any still-attached element
347
+ * from the render works equally well — the exact choice doesn't matter,
348
+ * only that it lives in the correct subtree at call time. Omit when no
349
+ * such element exists (e.g. a click handler with no natural anchor); the
350
+ * overlay then falls back to `doc.body`, matching prior behaviour exactly.
351
+ */
352
+ anchor?: Element;
353
+ }
354
+ export interface OverlayHandle {
355
+ /** null in 'window' mode — there is no backdrop to block the page. */
356
+ backdrop: HTMLDivElement | null;
357
+ box: HTMLDivElement;
358
+ /** Flex container inside the box for the canvas/content area. */
359
+ contentWrap: HTMLDivElement;
360
+ /** Close the overlay — removes DOM, restores scroll, removes all listeners, fires onClose. */
361
+ close: () => void;
362
+ /** Raise this window above other open windows. No-op in 'modal' mode. */
363
+ bringToFront: () => void;
364
+ }
365
+ /** Create and open a resizable, maximizable, exclusive expand modal — dims and
366
+ * blocks the rest of the page. See `openFloatingWindow` for the non-modal form. */
367
+ export declare function openModal(opts: Omit<OverlayOptions, 'mode'>): OverlayHandle;
368
+ /** Create and open a resizable, maximizable, non-modal floating window — the
369
+ * rest of the page stays fully interactive, and multiple windows may be open at
370
+ * once. See `openModal` for the exclusive/blocking form. */
371
+ export declare function openFloatingWindow(opts: Omit<OverlayOptions, 'mode'>): OverlayHandle;
372
+ export interface ReparentModalOptions {
373
+ ownerDocument?: Document;
374
+ title?: string;
375
+ /** Called after the modal has closed and every element has been restored to its original position (or appended back, if its original spot is no longer available — see the stale-reference note above). Runs once, even if nothing was actually moved (the re-entrancy guard tripped). */
376
+ onClosed?: () => void;
377
+ }
378
+ /**
379
+ * Reparent one or more elements (moved together, in the given order) into a
380
+ * new modal's `contentWrap`, and restore each to its original position when
381
+ * the modal closes. Returns `null` — opening nothing — if any element is
382
+ * already inside a still-open modal from an earlier call; the caller's own
383
+ * trigger should normally be hidden/disabled for that duration anyway (see
384
+ * both call sites), this is defense in depth, not the primary guard.
385
+ */
386
+ export declare function openReparentedModal(elements: HTMLElement[], opts?: ReparentModalOptions): OverlayHandle | null;
387
+ /**
388
+ * `sync()` hides the button entirely in `'metadata'` mode — that mode never
389
+ * consults `colorFns`/the active colour scheme at all (always the dedicated
390
+ * ordered palette + `MetadataFieldDef.values[].color` overrides, see
391
+ * `pushDieRectangles`), so every scheme choice would be a real control with
392
+ * zero visible effect, not just a restricted one — worse than the existing
393
+ * hardBin/softBin restriction (which still changes the die colours, just from
394
+ * a smaller scheme set).
395
+ */
396
+ export declare function makePaletteBtn(helpers: ToolbarHelpers, getPlotMode: () => PlotMode, getColorScheme: () => string, hasCustomColors: () => boolean, setColorScheme: (v: string) => void): {
397
+ btn: HTMLButtonElement;
398
+ sync: () => void;
399
+ };
400
+ /** Requested pass/fail display, defaulting to 'off' when unset. */
401
+ export declare function requestedPassFailDisplay(opts: {
402
+ passFailDisplay?: 'off' | 'spec' | 'test';
403
+ }): 'off' | 'spec' | 'test';
404
+ /**
405
+ * The two mutually exclusive pass/fail display entries for the overlays menu,
406
+ * shared by renderWaferMap and renderWaferGallery. Each entry appears only when
407
+ * valid for the active test (library-derived): "Spec pass/fail" needs limits,
408
+ * "Test pass/fail" needs at least one recorded verdict. A functional active
409
+ * test returns no entries at all — its value mode IS test pass/fail, there is
410
+ * no alternative display to choose.
411
+ */
412
+ export declare function passFailMenuRows(state: {
413
+ functionalActive: boolean;
414
+ hasLimits: boolean;
415
+ hasRecorded: boolean;
416
+ display: 'off' | 'spec' | 'test';
417
+ }, setDisplay: (d: 'off' | 'spec' | 'test') => void): CheckMenuRow[];
418
+ export declare function makeLogScaleBtn(helpers: ToolbarHelpers, getOpts: () => {
419
+ plotMode?: PlotMode;
420
+ logScale?: boolean;
421
+ passFailDisplay?: 'off' | 'spec' | 'test';
422
+ functionalActive?: boolean;
423
+ }, setOpts: (patch: {
424
+ logScale: boolean;
425
+ }) => void): {
426
+ btn: HTMLButtonElement;
427
+ sync: () => void;
428
+ };
429
+ export type LegendPosition = 'default' | 'compact' | 'left' | 'top' | 'bottom' | 'floating';
430
+ export declare function makeLegendStyleBtn(helpers: ToolbarHelpers, getOpts: () => {
431
+ plotMode?: PlotMode;
432
+ legendPosition?: LegendPosition;
433
+ }, setLegendPosition: (v: LegendPosition) => void): {
434
+ btn: HTMLButtonElement;
435
+ sync: () => void;
436
+ };
437
+ export declare function makeOverlaysBtn(helpers: ToolbarHelpers, getRows: () => CheckMenuRow[], isAnyOn: () => boolean): HTMLButtonElement;
438
+ export declare function makeOrientationBtn(helpers: ToolbarHelpers, getOpts: () => {
439
+ rotation?: number;
440
+ flipX?: boolean;
441
+ flipY?: boolean;
442
+ }, setOpts: (patch: {
443
+ rotation?: 0 | 90 | 180 | 270;
444
+ flipX?: boolean;
445
+ flipY?: boolean;
446
+ }) => void): HTMLButtonElement;
447
+ type GuideApi = {
448
+ buildWaferMap: unknown;
449
+ renderWaferMap: unknown;
450
+ renderWaferGallery: unknown;
451
+ analyzeWaferMap: unknown;
452
+ };
453
+ /**
454
+ * Host-supplied content inserted into wmap's own embedded user-guide window,
455
+ * so a host app's own documentation and wmap's reference live behind one
456
+ * help button instead of two separate ones.
457
+ */
458
+ export interface UserGuideExtension {
459
+ /**
460
+ * Host-provided HTML, inserted before wmap's own guide content. Static
461
+ * content only — must not contain `<script>` tags. wmap re-executes
462
+ * exactly one inline script (its own live-demo bootstrap, always the
463
+ * last element in `html`) by finding the content div's first
464
+ * `<script>` in document order; a `<script>` in this HTML would be
465
+ * found instead, silently breaking wmap's own live demos.
466
+ */
467
+ html: string;
468
+ /**
469
+ * Overrides the window's title text (default `'Wafer Map — User Guide'`).
470
+ * Use this when the host's own content should frame the whole document —
471
+ * wmap's own `<h1>` stays as-is further down the page, so the result
472
+ * reads as one combined guide with the host's section first, not a title
473
+ * rewrite.
474
+ */
475
+ title?: string;
476
+ }
477
+ /**
478
+ * Open the embedded user-guide window and populate its live demos. Closes any
479
+ * previously open guide window first (at most one may be open at a time).
480
+ * `api` must contain the four library functions the guide demos call at runtime.
481
+ * `html` is USER_GUIDE_HTML — passed in so toolbar.ts has no dependency on userGuideHtml.ts.
482
+ * `extension`, when provided, prepends host content and/or overrides the window title — see `UserGuideExtension`.
483
+ *
484
+ * Opens a real, separate window when available (draggable outside the host
485
+ * window's own bounds), falling back to the in-page floating window when
486
+ * `window.open` is blocked/unavailable — silently returns `null` in some
487
+ * embedded WebViews (Tauri, Electron, WebView2), same gap `openDetachWindow`
488
+ * exists for. Unlike gallery card detach, this doesn't go through
489
+ * `setDetachWindowOpener` — that opener's contract is scoped to per-wafer
490
+ * detach windows a host may have built specifically for that shape of
491
+ * content, and reusing it silently for the guide would broaden it without
492
+ * documentation.
493
+ */
494
+ export declare function openUserGuideWindow(api: GuideApi, html: string, extension?: UserGuideExtension, anchor?: Element): void;
495
+ export {};
496
+ //# sourceMappingURL=toolbar.d.ts.map
@@ -0,0 +1,9 @@
1
+ import{getUniqueTestNumbers as ue}from"../renderer/buildView.js";import{dieHasTestData as pe}from"../renderer/buildWaferMap.js";import{prettyKey as me}from"../core/utils.js";import{listColorSchemes as oe}from"../renderer/colorSchemes.js";import{ICONS as _}from"./icons.js";import{WMAP_VERSION as fe,WMAP_BUILD_TIME as be}from"./version.js";let ie=!1;export function logWmapVersionOnce(){ie||(ie=!0,console.log(`[wmap] v${fe} (built ${be})`))}export const WMAP_TOKEN_NAMES=["z","icon","icon-hover","icon-active","bg-hover","bg-active","separator","surface","border","menu-hover","menu-active","panel-bg","text-muted","text","text-strong","warn-bg","warn-border","warn-text","info-bg","info-text","selected","finding-indicator","canvas-bg"];const y=(e,t)=>`var(--wmap-${e}, ${t})`;export const CLR={icon:y("icon","#506784"),iconHover:y("icon-hover","#2a3f5f"),iconActive:y("icon-active","#1a65ca"),bgHover:y("bg-hover","#edf0f8"),bgActive:y("bg-active","#dce8f8"),separator:y("separator","rgba(0,0,0,0.12)"),menuBg:y("surface","#fff"),menuBorder:y("border","rgba(0,0,0,0.12)"),menuHover:y("menu-hover","#f0f4fc"),menuActive:y("menu-active","#dce8f8"),panelBg:y("panel-bg","#fafbfc"),label:y("text-muted","#647687"),value:y("text-strong","#1f2f43"),text:y("text","#333"),warnBg:y("warn-bg","#fffbe6"),warnBorder:y("warn-border","#f0c040"),warnText:y("warn-text","#7a5800"),infoBg:y("info-bg","#dce8f8"),infoText:y("info-text","#334155"),findingHighlight:y("selected","#e07a20"),findingIndicator:y("finding-indicator","#b7551a")};export function sevColor(e){return e==="unusual"?"#a84112":e==="notable"?"#8a6500":CLR.icon}export const ROTATIONS=[0,90,180,270],INLINE_TEST_LIMIT=6,MENU_SEARCH_THRESHOLD=8,DEFAULT_OVERLAY_Z=6e3,Z_BASE=`var(--wmap-z, ${DEFAULT_OVERLAY_Z})`,Z_ABOVE=`calc(var(--wmap-z, ${DEFAULT_OVERLAY_Z}) + 1)`,Z_ABOVE2=`calc(var(--wmap-z, ${DEFAULT_OVERLAY_Z}) + 2)`;export function applyOverlayZ(e){if(e==null)return()=>{};const t=document.documentElement,n=t.style.getPropertyValue("--wmap-z");return t.style.setProperty("--wmap-z",String(e)),()=>{n?t.style.setProperty("--wmap-z",n):t.style.removeProperty("--wmap-z")}}export function copyWmapThemeTokens(e,t){const n=getComputedStyle(e);for(const a of WMAP_TOKEN_NAMES){const i=n.getPropertyValue(`--wmap-${a}`).trim();i&&t.style.setProperty(`--wmap-${a}`,i)}}export function syncWmapPopupTheme(e,t,n){const a=()=>{copyWmapThemeTokens(e,t),n?.()},i=new window.MutationObserver(a);i.observe(e,{attributes:!0,attributeFilter:["style","class"]}),i.observe(document.documentElement,{attributes:!0,attributeFilter:["style","class"]});const s=window.matchMedia("(prefers-color-scheme: dark)");return s.addEventListener("change",a),()=>{i.disconnect(),s.removeEventListener("change",a)}}let ee=null;export function setDetachWindowOpener(e){ee=e}export function openDetachWindow(e){return ee?ee(e):window.open("","_blank","width=560,height=600")}export const MODE_LABELS={value:"Test Value",hardBin:"Hard Bin",softBin:"Soft Bin",stackedValues:"Stacked Test Values",stackedBins:"Stacked Hard Bins",stackedSoftBins:"Stacked Soft Bins",metadata:"Metadata"},BIN_LEGEND_MODES=new Set(["hardBin","softBin","metadata"]),STACKED_MODES=new Set(["stackedValues","stackedBins","stackedSoftBins"]);export function saveImageBlob(e,t,n){const a=`${t}.png`;if(n){n(e,a);return}const i=URL.createObjectURL(e),s=document.createElement("a");s.href=i,s.download=a,s.click(),URL.revokeObjectURL(i)}export function saveTextFile(e,t,n,a){if(a){a(e,t,n);return}const i=new Blob([e],{type:n}),s=URL.createObjectURL(i),l=document.createElement("a");l.href=s,l.download=t,l.click(),URL.revokeObjectURL(s)}const he='[role="menuitem"],[role="menuitemradio"],[role="menuitemcheckbox"]';export function nextFrame(e,t=window){const n=t.requestAnimationFrame;typeof n=="function"?n.call(t,e):setTimeout(e,0)}function Oe(e,t,n){e.setAttribute("role","menu"),e.setAttribute("aria-label",t);for(const a of Array.from(e.children)){const i=n(a);i&&(a.setAttribute("role",i),a.tabIndex=-1)}}function G(e,t,n){const a=()=>Array.from(e.querySelectorAll(he));e.addEventListener("keydown",s=>{const l=a();if(l.length===0)return;const o=document.activeElement,d=o?l.indexOf(o):-1;switch(s.key){case"ArrowDown":s.preventDefault(),l[d<0||d===l.length-1?0:d+1].focus();break;case"ArrowUp":s.preventDefault(),l[d<=0?l.length-1:d-1].focus();break;case"Home":s.preventDefault(),l[0].focus();break;case"End":s.preventDefault(),l[l.length-1].focus();break;case"Enter":case" ":d>=0&&(s.preventDefault(),l[d].click());break;case"Escape":case"Tab":s.preventDefault(),n(),t?.focus();break}});const i=a()[0];i&&nextFrame(()=>i.focus())}export function overlayRootFor(e){let t=e;for(;t;){if(t.classList.contains("wmap-overlay-box")||t.tagName==="DIALOG"&&t.matches(":modal"))return t;t=t.parentElement}return e.ownerDocument.body}const te=new WeakMap;export function getTooltip(e=document){const t=te.get(e);if(t&&t.isConnected)return t;const n=t??e.createElement("div");return t||(Object.assign(n.style,{position:"fixed",pointerEvents:"none",background:"rgba(30, 32, 40, 0.93)",color:"#f0f0f2",border:"1px solid rgba(255,255,255,0.10)",padding:"7px 11px",borderRadius:"5px",fontSize:"13px",lineHeight:"1.55",maxWidth:"280px",maxHeight:"min(60vh, 480px)",overflow:"hidden",whiteSpace:"pre-wrap",zIndex:Z_ABOVE,display:"none",fontFamily:"system-ui, sans-serif",boxShadow:"0 3px 10px rgba(0,0,0,0.45)"}),te.set(e,n)),e.body.appendChild(n),n}export function hideTooltip(e=document){const t=te.get(e);t&&(t.style.display="none")}export function reparentTooltip(e){const t=e?.ownerDocument??document,n=getTooltip(t);(e??t.body).appendChild(n)}export function positionTooltip(e,t,n,a){const i=overlayRootFor(t);e.parentElement!==i&&i.appendChild(e);const s=e.ownerDocument.defaultView??window;e.style.left="0",e.style.top="0";const l=e.offsetWidth,o=e.offsetHeight,d=8;let h=n+14,c=a-8;h+l+d>s.innerWidth&&(h=n-l-6),c+o+d>s.innerHeight&&(c=s.innerHeight-o-d),c<d&&(c=d),e.style.left=`${h}px`,e.style.top=`${c}px`}export function buildDataModeEntries(e,t,n){const a=e.some(pe),i=e.some(c=>c.testValues!==void 0&&Object.keys(c.testValues).length>0),s=e.some(c=>c.hbin!=null),l=e.some(c=>c.sbin!=null),o=a?t?.length?t.map(c=>({plotMode:"value",activeTest:c.testNumber,label:c.unit?`${c.name} (${c.unit})`:c.name,logScale:c.logScale})):ue(e).map(c=>({plotMode:"value",activeTest:c,label:`Test ${c}`})):[],d=[...s?[{plotMode:"hardBin",label:MODE_LABELS.hardBin}]:[],...l?[{plotMode:"softBin",label:MODE_LABELS.softBin}]:[]],h=n.includeStacked?[...i?[{plotMode:"stackedValues",label:MODE_LABELS.stackedValues}]:[],...s?[{plotMode:"stackedBins",label:MODE_LABELS.stackedBins}]:[],...l?[{plotMode:"stackedSoftBins",label:MODE_LABELS.stackedSoftBins}]:[]]:[];return{testEntries:o,binEntries:d,stackedEntries:h}}export function metadataKeyHasData(e,t){return e.some(n=>n.metadata?.[t]!==void 0&&n.metadata?.[t]!==null)}export function metadataModeEntry(e){return{plotMode:"metadata",activeMetadataKey:e.key,label:e.label??me(e.key)}}export function makeMenuSearchBox(e,t="Filter\u2026"){const n=document.createElement("input");return n.type="text",n.placeholder=t,n.setAttribute("aria-label",t),Object.assign(n.style,{display:"block",width:"calc(100% - 20px)",margin:"2px 10px 6px",padding:"4px 8px",fontSize:"12px",boxSizing:"border-box",background:CLR.menuBg,color:CLR.text,border:`1px solid ${CLR.menuBorder}`,borderRadius:"4px",outline:"none"}),n.addEventListener("click",a=>a.stopPropagation()),n.addEventListener("mousedown",a=>a.stopPropagation()),n.addEventListener("keydown",a=>a.stopPropagation()),n.addEventListener("input",()=>e(n.value.trim().toLowerCase())),n.addEventListener("focus",()=>{n.style.borderColor=CLR.iconActive}),n.addEventListener("blur",()=>{n.style.borderColor=CLR.menuBorder}),queueMicrotask(()=>n.focus()),n}export function buildModeMenuEl(e,t,n,a,i,s,l,o,d=window,h=[]){const{makeMenuRow:c,makeMenuSection:p}=l,r=document.createElement("div"),v=180,x=e.left+v<=(d.innerWidth??1/0)?e.left:Math.max(4,e.right-v);if(Object.assign(r.style,{position:"fixed",top:`${e.bottom+4}px`,left:`${x}px`,background:CLR.menuBg,border:`1px solid ${CLR.menuBorder}`,borderRadius:"4px",boxShadow:"0 4px 12px rgba(0,0,0,0.15)",zIndex:Z_BASE,minWidth:`${v}px`,padding:"4px 0",pointerEvents:"auto"}),t.length)if(r.appendChild(p("Test Value")),t.length<=INLINE_TEST_LIMIT)for(const g of t)r.appendChild(c(g.label,i(g),!0,f=>{f.stopPropagation(),s(g,r)}));else{const g=o==="value",f=c(MODE_LABELS.value+" \u25B6",g,!1,()=>{});f.style.display="flex",f.style.justifyContent="space-between",f.style.alignItems="center";let b=null,M=null;const A=()=>{M!==null&&(clearTimeout(M),M=null)},E=()=>{A(),b?.remove(),b=null},U=()=>{A(),M=setTimeout(E,300)},F=()=>{if(b)return;const L=f.getBoundingClientRect(),w=160,C=320,I=L.right+2+w<=(d.innerWidth??1/0)?L.right+2:Math.max(4,L.left-2-w),T=Math.min(L.top-4,Math.max(4,(d.innerHeight??1/0)-C-4));if(b=document.createElement("div"),Object.assign(b.style,{position:"fixed",top:`${T}px`,left:`${I}px`,background:CLR.menuBg,border:`1px solid ${CLR.menuBorder}`,borderRadius:"4px",boxShadow:"0 4px 12px rgba(0,0,0,0.15)",zIndex:Z_ABOVE,minWidth:`${w}px`,maxHeight:`${C}px`,overflowY:"auto",padding:"4px 0",pointerEvents:"auto"}),t.length>MENU_SEARCH_THRESHOLD){const S=makeMenuSearchBox(j=>{for(const O of k)O.row.style.display=O.label.includes(j)?"":"none"},"Filter tests\u2026");Object.assign(S.style,{position:"sticky",top:"0",zIndex:"1",background:CLR.menuBg}),b.appendChild(S)}const k=[];for(const S of t){const j=c(S.label,i(S),!1,O=>{O.stopPropagation(),E(),s(S,r)});k.push({row:j,label:S.label.toLowerCase()}),b.appendChild(j)}b.addEventListener("mouseenter",A),b.addEventListener("mouseleave",U),(r.parentElement??d.document.body).appendChild(b),d.document.addEventListener("click",E,{once:!0})};f.addEventListener("mouseenter",()=>{A(),F()}),f.addEventListener("mouseleave",U),r.appendChild(f)}if(n.length){r.appendChild(p("Bins"));for(const g of n)r.appendChild(c(g.label,i(g),!1,f=>{f.stopPropagation(),s(g,r)}))}if(h.length){r.appendChild(p("Metadata"));for(const g of h)r.appendChild(c(g.label,i(g),!1,f=>{f.stopPropagation(),s(g,r)}))}if(a.length){r.appendChild(p("Lot Aggregation"));for(const g of a)r.appendChild(c(g.label,i(g),!1,f=>{f.stopPropagation(),s(g,r)}))}return r.setAttribute("role","menu"),r.setAttribute("aria-label","Plot mode"),r}export function markMenuTrigger(e,t){e.setAttribute("aria-haspopup","menu"),e.setAttribute("aria-expanded",t?"true":"false")}export function wireMenuA11y(e,t,n){G(e,t,n)}export function wireExpandToggle(e,t){const n=e.ownerDocument;let a=!1;function i(h){a!==h&&(a=h,t(a))}function s(){i(!1)}function l(){i(!a),a&&n.addEventListener("click",s,{once:!0})}const o=h=>{h.stopPropagation(),l()},d=h=>{h.key==="Enter"||h.key===" "?(h.preventDefault(),l()):h.key==="Escape"&&s()};return e.setAttribute("role","button"),e.tabIndex<0&&(e.tabIndex=0),e.addEventListener("click",o),e.addEventListener("keydown",d),{isOpen:()=>a,close:s,destroy(){e.removeEventListener("click",o),e.removeEventListener("keydown",d),n.removeEventListener("click",s)}}}export function buildCheckMenuEl(e,t,n,a=window){const{makeMenuRow:i,makeMenuSection:s}=n,l=document.createElement("div"),o=168,h=e.left+o<=(a.innerWidth??1/0)?e.left:Math.max(4,e.right-o);Object.assign(l.style,{position:"fixed",top:`${e.bottom+4}px`,left:`${h}px`,background:CLR.menuBg,border:`1px solid ${CLR.menuBorder}`,borderRadius:"4px",boxShadow:"0 4px 12px rgba(0,0,0,0.15)",zIndex:Z_BASE,minWidth:`${o}px`,padding:"4px 0",pointerEvents:"auto"});for(const c of t)if("section"in c)l.appendChild(s(c.section));else{if(!(c.enabled!==!1))continue;const r=document.createElement("div");r.setAttribute("role","menuitemcheckbox"),r.setAttribute("aria-checked",c.active?"true":"false"),r.tabIndex=-1,Object.assign(r.style,{display:"flex",alignItems:"center",gap:"6px",padding:"6px 14px",fontSize:"12px",cursor:"pointer",color:c.active?CLR.iconActive:CLR.text,fontWeight:c.active?"700":"400",background:c.active?CLR.menuActive:"transparent",whiteSpace:"nowrap",outline:"none"});const v=document.createElement("span");Object.assign(v.style,{width:"12px",flexShrink:"0",color:CLR.iconActive,visibility:c.active?"visible":"hidden"}),v.textContent="\u2713",v.setAttribute("aria-hidden","true");const m=document.createElement("span");m.textContent=c.label,r.appendChild(v),r.appendChild(m),r.addEventListener("mouseenter",()=>{c.active||(r.style.background=CLR.menuHover)}),r.addEventListener("mouseleave",()=>{r.style.background=c.active?CLR.menuActive:"transparent"}),r.addEventListener("focus",()=>{c.active||(r.style.background=CLR.menuHover)}),r.addEventListener("blur",()=>{r.style.background=c.active?CLR.menuActive:"transparent"}),r.addEventListener("click",x=>{x.stopPropagation(),c.onClick(x)}),l.appendChild(r)}return l}export function createToolbarHelpers(e){let t=null;const n=p=>overlayRootFor(p);function a(p,r,v){const m=document.createElement("button");return m.innerHTML=_[p],m.type="button",m.ariaLabel=r,Object.assign(m.style,{display:"flex",alignItems:"center",justifyContent:"center",width:"28px",height:"28px",padding:"0",border:"none",borderRadius:"3px",background:"transparent",color:CLR.icon,cursor:"pointer",pointerEvents:"auto",transition:"background 0.12s, color 0.12s",flexShrink:"0"}),m.addEventListener("mouseenter",x=>{m.dataset.active||(m.style.background=CLR.bgHover,m.style.color=CLR.iconHover),e.innerHTML=m.ariaLabel??r,e.style.display="block",positionTooltip(e,m,x.clientX,x.clientY)}),m.addEventListener("mousemove",x=>{e.style.display==="block"&&positionTooltip(e,m,x.clientX,x.clientY)}),m.addEventListener("mouseleave",()=>{m.dataset.active||(m.style.background="transparent",m.style.color=CLR.icon),e.style.display="none"}),m.addEventListener("click",()=>{e.style.display="none",v()}),m}function i(p,r){r?(p.dataset.active="1",p.style.background=CLR.bgActive,p.style.color=CLR.iconActive):(delete p.dataset.active,p.style.background="transparent",p.style.color=CLR.icon)}function s(){const p=document.createElement("div");return Object.assign(p.style,{width:"1px",height:"18px",background:CLR.separator,margin:"0 2px",flexShrink:"0"}),p}function l(p,r,v,m){const x=document.createElement("div");return x.textContent=p,x.setAttribute("role","menuitemradio"),x.setAttribute("aria-checked",r?"true":"false"),x.tabIndex=-1,Object.assign(x.style,{padding:`6px 14px 6px ${v?"26px":"14px"}`,fontSize:"12px",cursor:"pointer",color:r?CLR.iconActive:CLR.text,fontWeight:r?"700":"400",background:r?CLR.menuActive:"transparent",whiteSpace:"nowrap",outline:"none"}),x.addEventListener("mouseenter",()=>{r||(x.style.background=CLR.menuHover)}),x.addEventListener("mouseleave",()=>{x.style.background=r?CLR.menuActive:"transparent"}),x.addEventListener("focus",()=>{r||(x.style.background=CLR.menuHover)}),x.addEventListener("blur",()=>{x.style.background=r?CLR.menuActive:"transparent"}),x.addEventListener("click",m),x}function o(p){const r=document.createElement("div");return r.textContent=p,r.setAttribute("role","presentation"),Object.assign(r.style,{padding:"5px 14px 2px",fontSize:"10px",fontWeight:"600",letterSpacing:"0.05em",color:CLR.label,textTransform:"uppercase",pointerEvents:"none",userSelect:"none"}),r}function d(p,r,v,m,x){const g=()=>{t&&(t.remove(),t=null),f.setAttribute("aria-expanded","false")},f=a(p,r,()=>{if(t){g();return}const b=document.createElement("div"),M=f.getBoundingClientRect(),A=f.ownerDocument.defaultView??window,E=148,F=M.left+E<=(A.innerWidth??1/0)?M.left:Math.max(4,M.right-E);Object.assign(b.style,{position:"fixed",top:`${M.bottom+4}px`,left:`${F}px`,background:CLR.menuBg,border:`1px solid ${CLR.menuBorder}`,borderRadius:"4px",boxShadow:"0 4px 12px rgba(0,0,0,0.15)",zIndex:Z_BASE,minWidth:`${E}px`,padding:"4px 0",pointerEvents:"auto"});for(const L of v()){const w=document.createElement("div");w.textContent=L.label,w.dataset.wmapDropdownValue=L.value;const C=L.value===m();w.setAttribute("role","menuitemradio"),w.setAttribute("aria-checked",C?"true":"false"),w.tabIndex=-1,Object.assign(w.style,{padding:"6px 14px",fontSize:"12px",cursor:"pointer",color:C?CLR.iconActive:CLR.text,fontWeight:C?"700":"400",background:C?CLR.menuActive:"transparent",whiteSpace:"nowrap",outline:"none"});const D=()=>{L.value!==m()&&(w.style.background=CLR.menuHover)},I=()=>{w.style.background=L.value===m()?CLR.menuActive:"transparent"};w.addEventListener("mouseenter",D),w.addEventListener("mouseleave",I),w.addEventListener("focus",D),w.addEventListener("blur",I),w.addEventListener("click",T=>{T.stopPropagation(),x(L.value),g()}),b.appendChild(w)}b.setAttribute("role","menu"),b.setAttribute("aria-label",r),n(f).appendChild(b),t=b,f.setAttribute("aria-expanded","true"),G(b,f,g)});return f.setAttribute("aria-haspopup","menu"),f.setAttribute("aria-expanded","false"),f}function h(p,r,v,m){function x(){return buildCheckMenuEl(f.getBoundingClientRect(),v().map(b=>"section"in b?b:{...b,onClick:M=>{b.onClick(M),m(f);const A=t;if(!A)return;const E=x();E.setAttribute("role","menu"),E.setAttribute("aria-label",r),A.replaceWith(E),t=E,G(E,f,g)}}),{makeMenuRow:l,makeMenuSection:o},f.ownerDocument.defaultView??window)}const g=()=>{t&&(t.remove(),t=null),f.setAttribute("aria-expanded","false")},f=a(p,r,()=>{if(t){g();return}const b=x();b.setAttribute("role","menu"),b.setAttribute("aria-label",r),n(f).appendChild(b),t=b,f.setAttribute("aria-expanded","true"),G(b,f,g)});return f.setAttribute("aria-haspopup","menu"),f.setAttribute("aria-expanded","false"),m(f),f}function c(p){if(t&&!t.contains(p.target)&&(t.remove(),t=null,p.target?.getAttribute?.("aria-haspopup")!=="menu"))for(const v of document.querySelectorAll('[aria-haspopup="menu"][aria-expanded="true"]'))v.setAttribute("aria-expanded","false")}return{makeBtn:a,setActive:i,makeSep:s,makeMenuRow:l,makeMenuSection:o,makeDropdown:d,makeCheckMenuBtn:h,closeOpenMenu:c,getOpenMenu:()=>t,setOpenMenu:p=>{t=p}}}const xe=7e3;let ve=0;function ae(){return xe+ ++ve}const ge=32,ye=6;let we=0;function ke(){return we++%ye*ge}function Ee(e,t){if(t===e.body)return()=>{};let n=t;for(;n.parentElement&&n.parentElement!==e.body;)n=n.parentElement;return n===e.body?()=>{}:(n.classList.add("wmap-overlay-ancestor"),()=>n.classList.remove("wmap-overlay-ancestor"))}function re(e){const t=e.mode==="modal",n=e.ownerDocument??document,a=n.defaultView??window,i=n.body.style.overflow;t&&(n.body.style.overflow="hidden");let s=null;t&&(s=n.createElement("div"),s.id="wmap-modal-backdrop",Object.assign(s.style,{position:"fixed",inset:"0",background:"rgba(0,0,0,0.6)",display:"flex",alignItems:"center",justifyContent:"center",zIndex:Z_ABOVE,backdropFilter:"blur(3px)"}));const l=n.activeElement,o=n.createElement("div");if(o.className=t?"wmap-modal-box wmap-overlay-box":"wmap-window-box wmap-overlay-box",o.setAttribute("role","dialog"),t&&o.setAttribute("aria-modal","true"),o.setAttribute("aria-label",e.title??"Expanded wafer map"),o.tabIndex=-1,Object.assign(o.style,{position:"relative",background:CLR.menuBg,borderRadius:"12px",overflow:"hidden",display:"flex",flexDirection:"column",width:"min(90vw, 700px)",height:"min(90vh, 700px)",boxShadow:"0 20px 60px rgba(0,0,0,0.4)",minWidth:"320px",minHeight:"240px",maxWidth:"100vw",maxHeight:"100vh",zIndex:t?Z_ABOVE2:String(ae())}),!t){const u=ke();Object.assign(o.style,{position:"fixed",top:`${Math.round(a.innerHeight*.08)+u}px`,left:`${Math.round(a.innerWidth*.08)+u}px`})}const d=n.createElement("div");Object.assign(d.style,{display:"flex",alignItems:"center",gap:"6px",padding:"10px 14px",borderBottom:`1px solid ${CLR.menuBorder}`,flexShrink:"0"});const h={border:`1px solid ${CLR.menuBorder}`,borderRadius:"4px",background:CLR.panelBg,cursor:"pointer",color:CLR.label,lineHeight:"1",padding:"0",display:"flex",alignItems:"center",justifyContent:"center",width:"28px",height:"28px"};if(e.title){const u=n.createElement("span");u.textContent=e.title,u.title=e.title,u.dataset.wmapWindowTitle="1",Object.assign(u.style,{fontWeight:"700",fontSize:"14px",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",minWidth:"0"}),d.appendChild(u)}const c=n.createElement("div");c.style.flex="1",d.appendChild(c);let p=null;t||(p=n.createElement("button"),p.type="button",p.innerHTML=_.windowMinimize,p.title="Minimize",p.setAttribute("aria-label","Minimize"),Object.assign(p.style,h),p.addEventListener("click",()=>U(!b)),d.appendChild(p));const r=n.createElement("button");r.type="button",r.innerHTML=_.maximize,r.title="Maximize (F)",r.setAttribute("aria-label","Maximize"),Object.assign(r.style,h),r.addEventListener("click",()=>f(!m));const v=n.createElement("button");v.type="button",v.innerHTML=_.close,v.title="Close (Esc)",v.setAttribute("aria-label","Close"),Object.assign(v.style,h),v.addEventListener("click",D),d.appendChild(r),d.appendChild(v);let m=!1,x="",g="";function f(u){m=u,r.innerHTML=m?_.minimize:_.maximize,r.title=m?"Restore (F)":"Maximize (F)",r.setAttribute("aria-label",m?"Restore":"Maximize"),k.style.display=m?"none":"block",m?(o.style.borderRadius="0",o.style.width="100vw",o.style.height="100vh",t||(x=o.style.top,g=o.style.left,o.style.top="0",o.style.left="0")):(o.style.borderRadius="12px",o.style.width="min(90vw, 700px)",o.style.height="min(90vh, 700px)",t||(o.style.top=x,o.style.left=g)),e.onMaximizeChange?.(m,o)}let b=!1,M="",A="";const E=220;function U(u){b=u,p.innerHTML=b?_.windowRestore:_.windowMinimize,p.title=b?"Restore":"Minimize",p.setAttribute("aria-label",b?"Restore":"Minimize"),k.style.display=b?"none":"block",b?(M=o.style.height,A=o.style.width,o.style.minHeight="0",o.style.height="auto",o.style.minWidth="0",o.style.width=`${E}px`,r.disabled=!0,r.style.opacity="0.4",r.style.cursor="default",T.style.display="none"):(o.style.minHeight="240px",o.style.height=M||"min(90vh, 700px)",o.style.minWidth="320px",o.style.width=A||"min(90vw, 700px)",r.disabled=!1,r.style.opacity="1",r.style.cursor="pointer",T.style.display="flex")}function F(){const u=n.body.style.userSelect;return n.body.style.userSelect="none",()=>{n.body.style.userSelect=u}}t||(d.style.cursor="move",d.addEventListener("pointerdown",u=>{if(m||u.target.closest('button, [role="button"]'))return;u.preventDefault();const B=F(),W=u.clientX,z=u.clientY,H=o.offsetTop,R=o.offsetLeft,Z=a.innerHeight-d.offsetHeight,Q=a.innerWidth-40,X=40-o.offsetWidth;d.setPointerCapture(u.pointerId);const N=V=>{const J=Math.min(Math.max(H+(V.clientY-z),0),Z),de=Math.min(Math.max(R+(V.clientX-W),X),Q);o.style.top=`${J}px`,o.style.left=`${de}px`},P=V=>{B(),d.releasePointerCapture(V.pointerId),d.removeEventListener("pointermove",N),d.removeEventListener("pointerup",P)};d.addEventListener("pointermove",N),d.addEventListener("pointerup",P)}));const L='button,[href],input,select,textarea,[tabindex]:not([tabindex="-1"]),[role="menuitem"],[role="menuitemradio"],[role="menuitemcheckbox"]';function w(u){const B=Array.from(o.querySelectorAll(L)).filter(R=>R.offsetParent!==null||R===n.activeElement);if(B.length===0){u.preventDefault(),o.focus();return}const W=B[0],z=B[B.length-1],H=n.activeElement;u.shiftKey&&(H===W||!o.contains(H))?(u.preventDefault(),z.focus()):!u.shiftKey&&H===z&&(u.preventDefault(),W.focus())}const C=u=>{if(u.key==="Escape"){D();return}if(t&&(u.key==="e"||u.key==="E")){D();return}if(t&&u.key==="Tab"){w(u);return}(u.key==="f"||u.key==="F")&&!b&&f(!m)};function D(){t?n.removeEventListener("keydown",C):o.removeEventListener("keydown",C),q.remove(),ce(),hideTooltip(n),reparentTooltip(n.body),s?s.remove():o.remove(),t&&(n.body.style.overflow=i),l?.focus?.(),e.onClose()}function I(){t||(o.style.zIndex=String(ae()))}const T=n.createElement("div");Object.assign(T.style,{flex:"1",minHeight:"0",minWidth:"0",display:"flex",overflow:"hidden"});const k=n.createElement("div");k.tabIndex=0,k.setAttribute("role","separator"),k.setAttribute("aria-label","Resize window (arrow keys, or drag)"),Object.assign(k.style,{position:"absolute",right:"0",bottom:"0",width:"16px",height:"16px",cursor:"nwse-resize",touchAction:"none"}),k.innerHTML='<svg viewBox="0 0 16 16" width="16" height="16" style="display:block"><path d="M15 15 L15 9 M15 15 L9 15 M15 15 L15 3 M15 15 L3 15" stroke="currentColor" stroke-width="1" opacity="0.35" fill="none"/></svg>',k.style.color=CLR.label;const S=320,j=240,O=20;function Y(u,B){const W=a.innerWidth-o.offsetLeft,z=a.innerHeight-o.offsetTop,H=Math.min(Math.max(o.offsetWidth+u,S),W),R=Math.min(Math.max(o.offsetHeight+B,j),z);o.style.width=`${H}px`,o.style.height=`${R}px`}k.addEventListener("keydown",u=>{if(!(m||b))switch(u.key){case"ArrowRight":u.preventDefault(),Y(O,0);break;case"ArrowLeft":u.preventDefault(),Y(-O,0);break;case"ArrowDown":u.preventDefault(),Y(0,O);break;case"ArrowUp":u.preventDefault(),Y(0,-O);break}}),k.addEventListener("pointerdown",u=>{if(m||b)return;u.preventDefault();const B=F(),W=u.clientX,z=u.clientY,H=o.offsetWidth,R=o.offsetHeight,Z=a.innerWidth-o.offsetLeft,Q=a.innerHeight-o.offsetTop;k.setPointerCapture(u.pointerId);const X=P=>{const V=Math.min(Math.max(H+(P.clientX-W),S),Z),J=Math.min(Math.max(R+(P.clientY-z),j),Q);o.style.width=`${V}px`,o.style.height=`${J}px`},N=P=>{B(),k.releasePointerCapture(P.pointerId),k.removeEventListener("pointermove",X),k.removeEventListener("pointerup",N)};k.addEventListener("pointermove",X),k.addEventListener("pointerup",N)}),o.appendChild(d),o.appendChild(T),o.appendChild(k);const K=e.anchor?overlayRootFor(e.anchor):n.body;s?(s.appendChild(o),K.appendChild(s)):K.appendChild(o);const ce=Ee(n,K),q=n.createElement("style");return q.textContent=`@media print{
2
+ body{overflow:visible!important;height:auto!important}
3
+ body>*:not(.wmap-overlay-box):not(#wmap-modal-backdrop):not(.wmap-overlay-ancestor){display:none!important}
4
+ #wmap-modal-backdrop{position:static!important;background:none!important;backdrop-filter:none!important;display:block!important}
5
+ .wmap-overlay-box{position:static!important;width:auto!important;height:auto!important;max-width:none!important;max-height:none!important;min-width:0!important;min-height:0!important;box-shadow:none!important;border-radius:0!important;overflow:visible!important;display:block!important}
6
+ .wmap-overlay-box>div:first-child{display:none!important}
7
+ .wmap-overlay-box>div:nth-child(2){display:block!important;overflow:visible!important;height:auto!important;flex:none!important;min-height:0!important;min-width:0!important}
8
+ .wmap-overlay-box>div:nth-child(3){display:none!important}
9
+ }`,n.head.appendChild(q),reparentTooltip(o),t?(s.addEventListener("click",u=>{u.target===s&&D()}),n.addEventListener("keydown",C)):(o.addEventListener("keydown",C),o.addEventListener("mousedown",I),I()),nextFrame(()=>(v.isConnected?v:o).focus(),a),{backdrop:s,box:o,contentWrap:T,close:D,bringToFront:I}}export function openModal(e){return re({...e,mode:"modal"})}export function openFloatingWindow(e){return re({...e,mode:"window"})}const ne=new WeakSet;export function openReparentedModal(e,t={}){if(e.length===0||e.some(i=>ne.has(i)))return null;const n=e.map(i=>({el:i,parent:i.parentElement,next:i.nextSibling})),a=openModal({ownerDocument:t.ownerDocument,title:t.title,anchor:e[0],onClose:()=>{for(const i of n){if(ne.delete(i.el),!i.parent)continue;const s=i.next&&i.next.parentNode===i.parent?i.next:null;i.parent.insertBefore(i.el,s)}t.onClosed?.()}});for(const i of n)ne.add(i.el),a.contentWrap.appendChild(i.el);return a}export function makePaletteBtn(e,t,n,a,i){const s=e.makeDropdown("palette","Colour scheme",()=>{const d=t()==="hardBin"||t()==="softBin"?oe().filter(h=>h.name==="default"||h.name==="accessible"):oe();return[...a()?[{value:"custom",label:"Custom"}]:[],...d.map(h=>({value:h.name,label:h.label}))]},()=>n(),o=>i(o));function l(){s.style.display=t()==="metadata"?"none":""}return{btn:s,sync:l}}export function requestedPassFailDisplay(e){return e.passFailDisplay??"off"}export function passFailMenuRows(e,t){if(e.functionalActive)return[];const n=[];return e.hasLimits&&n.push({label:"Spec pass/fail",active:e.display==="spec",onClick:()=>t(e.display==="spec"?"off":"spec")}),e.hasRecorded&&n.push({label:"Test pass/fail",active:e.display==="test",onClick:()=>t(e.display==="test"?"off":"test")}),n}export function makeLogScaleBtn(e,t,n){const a=e.makeBtn("logScale","Toggle log scale",()=>{n({logScale:!t().logScale})});function i(){const s=t(),l=s.plotMode,o=l==="value"||l==="stackedValues";a.style.display=o&&requestedPassFailDisplay(s)==="off"&&!s.functionalActive?"":"none",e.setActive(a,!!s.logScale)}return{btn:a,sync:i}}export function makeLegendStyleBtn(e,t,n){const a=e.makeDropdown("legend","Legend style",()=>[{value:"default",label:"Default (right)"},{value:"compact",label:"Compact (right)"},{value:"left",label:"Left"},{value:"top",label:"Top"},{value:"bottom",label:"Bottom"},{value:"floating",label:"Floating"}],()=>t().legendPosition??"default",s=>n(s));function i(){const s=t().plotMode;a.style.display=s==="hardBin"||s==="softBin"||s==="metadata"?"":"none"}return{btn:a,sync:i}}export function makeOverlaysBtn(e,t,n){return e.makeCheckMenuBtn("overlays","Overlays",()=>t(),a=>e.setActive(a,n()))}export function makeOrientationBtn(e,t,n){return e.makeCheckMenuBtn("orient","Orientation",()=>[{section:"Rotate"},{label:"Rotate 90\xB0 clockwise",active:!1,onClick:()=>{const a=t().rotation??0;n({rotation:ROTATIONS[(ROTATIONS.indexOf(a)+1)%4]})}},{section:"Flip"},{label:"Flip horizontal",active:!!t().flipX,onClick:()=>n({flipX:!t().flipX})},{label:"Flip vertical",active:!!t().flipY,onClick:()=>n({flipY:!t().flipY})}],a=>{const{rotation:i,flipX:s,flipY:l}=t();e.setActive(a,!!(i||s||l))})}let $=null;function se(e,t,n,a){const i=t,s=i.__wmapDemoApi;i.__wmapDemoApi=a;const l=e.createElement("div");return l.classList.add("wmap-guide-content"),l.innerHTML=n,{content:l,restoreApi:()=>{i.__wmapDemoApi===a&&(i.__wmapDemoApi=s)}}}function le(e,t){const n=t,a=e.querySelector("script");if(a){const i=e.ownerDocument.createElement("script");i.textContent=a.textContent,e.appendChild(i);const s=e.querySelector(".wmap-guide");return s&&n.__wmapPopulateGuideDemos?.(s),n.__wmapDestroyGuideDemos}}function Me(e,t,n,a){const{content:i,restoreApi:s}=se(document,window,t,n);Object.assign(i.style,{flex:"1",overflow:"auto",minHeight:"0"});let l;const o=openFloatingWindow({title:e,anchor:a,onMaximizeChange:d=>{i.querySelector(".wmap-guide")?.classList.toggle("wmap-guide--max",d)},onClose:()=>{$===o&&($=null),l?.(),s()}});return o.contentWrap.appendChild(i),l=le(i,window),o}function Le(e,t,n,a){const i=e.document;i.title=t,Object.assign(i.documentElement.style,{height:"100%"}),Object.assign(i.body.style,{margin:"0",height:"100vh",overflow:"hidden",display:"flex",flexDirection:"column",fontFamily:'system-ui, -apple-system, "Segoe UI", sans-serif'}),copyWmapThemeTokens(document.documentElement,i.documentElement);const s=syncWmapPopupTheme(document.documentElement,i.documentElement),{content:l,restoreApi:o}=se(i,e,n,a);Object.assign(l.style,{flex:"1",overflow:"auto",minHeight:"0"}),i.body.appendChild(l);const d=le(l,e);let h=!1;const c=()=>{if(!h){h=!0,clearInterval(p),s();try{d?.()}catch{}o(),$===r&&($=null)}},p=setInterval(()=>{e.closed&&c()},400);e.addEventListener("pagehide",c);const r={close:()=>{e.closed||e.close(),c()}};return r}export function openUserGuideWindow(e,t,n,a){$&&($.close(),$=null);const i=n?.title??"Wafer Map \u2014 User Guide",s=(n?.html??"")+t,l=window.open("","_blank","width=800,height=820");$=l?Le(l,i,s,e):Me(i,s,e,a)}