@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.
- package/CHANGELOG.md +751 -0
- package/LICENSE +21 -0
- package/README.md +79 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -0
- package/dist/packages/canvas-adapter/canvasTheme.d.ts +28 -0
- package/dist/packages/canvas-adapter/canvasTheme.js +1 -0
- package/dist/packages/canvas-adapter/charts/barPanel.d.ts +52 -0
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -0
- package/dist/packages/canvas-adapter/charts/binCluster.d.ts +17 -0
- package/dist/packages/canvas-adapter/charts/binCluster.js +1 -0
- package/dist/packages/canvas-adapter/charts/boxplot.d.ts +45 -0
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -0
- package/dist/packages/canvas-adapter/charts/capability.d.ts +41 -0
- package/dist/packages/canvas-adapter/charts/capability.js +1 -0
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +156 -0
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -0
- package/dist/packages/canvas-adapter/charts/correlation.d.ts +35 -0
- package/dist/packages/canvas-adapter/charts/correlation.js +1 -0
- package/dist/packages/canvas-adapter/charts/histogram.d.ts +30 -0
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -0
- package/dist/packages/canvas-adapter/charts/index.d.ts +16 -0
- package/dist/packages/canvas-adapter/charts/index.js +1 -0
- package/dist/packages/canvas-adapter/charts/palette.d.ts +26 -0
- package/dist/packages/canvas-adapter/charts/palette.js +1 -0
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.d.ts +18 -0
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -0
- package/dist/packages/canvas-adapter/charts/scatter.d.ts +37 -0
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -0
- package/dist/packages/canvas-adapter/icons.d.ts +2 -0
- package/dist/packages/canvas-adapter/icons.js +42 -0
- package/dist/packages/canvas-adapter/index.d.ts +12 -0
- package/dist/packages/canvas-adapter/index.js +1 -0
- package/dist/packages/canvas-adapter/insightsTab.d.ts +76 -0
- package/dist/packages/canvas-adapter/insightsTab.js +1 -0
- package/dist/packages/canvas-adapter/metadataBadge.d.ts +26 -0
- package/dist/packages/canvas-adapter/metadataBadge.js +1 -0
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +181 -0
- package/dist/packages/canvas-adapter/renderWaferGallery.js +20 -0
- package/dist/packages/canvas-adapter/renderWaferMap.d.ts +316 -0
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -0
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +262 -0
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -0
- package/dist/packages/canvas-adapter/toCanvas.d.ts +106 -0
- package/dist/packages/canvas-adapter/toCanvas.js +1 -0
- package/dist/packages/canvas-adapter/toolbar.d.ts +496 -0
- package/dist/packages/canvas-adapter/toolbar.js +9 -0
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +2 -0
- package/dist/packages/canvas-adapter/userGuideHtml.js +968 -0
- package/dist/packages/canvas-adapter/version.d.ts +3 -0
- package/dist/packages/canvas-adapter/version.js +1 -0
- package/dist/packages/core/aggregates.d.ts +54 -0
- package/dist/packages/core/aggregates.js +1 -0
- package/dist/packages/core/classify.d.ts +24 -0
- package/dist/packages/core/classify.js +1 -0
- package/dist/packages/core/dies.d.ts +92 -0
- package/dist/packages/core/dies.js +1 -0
- package/dist/packages/core/index.d.ts +13 -0
- package/dist/packages/core/index.js +1 -0
- package/dist/packages/core/inference/grid.d.ts +35 -0
- package/dist/packages/core/inference/grid.js +1 -0
- package/dist/packages/core/inference/index.d.ts +7 -0
- package/dist/packages/core/inference/index.js +1 -0
- package/dist/packages/core/inference/pitch.d.ts +26 -0
- package/dist/packages/core/inference/pitch.js +1 -0
- package/dist/packages/core/inference/wafer.d.ts +30 -0
- package/dist/packages/core/inference/wafer.js +1 -0
- package/dist/packages/core/metadata.d.ts +31 -0
- package/dist/packages/core/metadata.js +1 -0
- package/dist/packages/core/probe.d.ts +16 -0
- package/dist/packages/core/probe.js +1 -0
- package/dist/packages/core/reticle.d.ts +68 -0
- package/dist/packages/core/reticle.js +1 -0
- package/dist/packages/core/transforms.d.ts +142 -0
- package/dist/packages/core/transforms.js +1 -0
- package/dist/packages/core/utils.d.ts +60 -0
- package/dist/packages/core/utils.js +1 -0
- package/dist/packages/core/wafer.d.ts +45 -0
- package/dist/packages/core/wafer.js +1 -0
- package/dist/packages/renderer/buildView.d.ts +420 -0
- package/dist/packages/renderer/buildView.js +1 -0
- package/dist/packages/renderer/buildWaferMap.d.ts +543 -0
- package/dist/packages/renderer/buildWaferMap.js +1 -0
- package/dist/packages/renderer/colorMap.d.ts +46 -0
- package/dist/packages/renderer/colorMap.js +1 -0
- package/dist/packages/renderer/colorSchemes.d.ts +40 -0
- package/dist/packages/renderer/colorSchemes.js +1 -0
- package/dist/packages/renderer/fmt.d.ts +30 -0
- package/dist/packages/renderer/fmt.js +1 -0
- package/dist/packages/renderer/index.d.ts +7 -0
- package/dist/packages/renderer/index.js +1 -0
- package/dist/packages/stats/analyzeWaferLot.d.ts +5 -0
- package/dist/packages/stats/analyzeWaferLot.js +1 -0
- package/dist/packages/stats/analyzeWaferMap.d.ts +13 -0
- package/dist/packages/stats/analyzeWaferMap.js +1 -0
- package/dist/packages/stats/binPareto.d.ts +49 -0
- package/dist/packages/stats/binPareto.js +1 -0
- package/dist/packages/stats/boxplot.d.ts +39 -0
- package/dist/packages/stats/boxplot.js +1 -0
- package/dist/packages/stats/capability.d.ts +55 -0
- package/dist/packages/stats/capability.js +1 -0
- package/dist/packages/stats/clusterDetection.d.ts +18 -0
- package/dist/packages/stats/clusterDetection.js +1 -0
- package/dist/packages/stats/correlation.d.ts +55 -0
- package/dist/packages/stats/correlation.js +1 -0
- package/dist/packages/stats/facets.d.ts +74 -0
- package/dist/packages/stats/facets.js +1 -0
- package/dist/packages/stats/filterFindings.d.ts +9 -0
- package/dist/packages/stats/filterFindings.js +1 -0
- package/dist/packages/stats/findingsNarrative.d.ts +3 -0
- package/dist/packages/stats/findingsNarrative.js +1 -0
- package/dist/packages/stats/histogram.d.ts +42 -0
- package/dist/packages/stats/histogram.js +1 -0
- package/dist/packages/stats/index.d.ts +27 -0
- package/dist/packages/stats/index.js +1 -0
- package/dist/packages/stats/math.d.ts +10 -0
- package/dist/packages/stats/math.js +1 -0
- package/dist/packages/stats/patternClassification.d.ts +107 -0
- package/dist/packages/stats/patternClassification.js +1 -0
- package/dist/packages/stats/regions.d.ts +57 -0
- package/dist/packages/stats/regions.js +1 -0
- package/dist/packages/stats/renderFindingsReport.d.ts +7 -0
- package/dist/packages/stats/renderFindingsReport.js +41 -0
- package/dist/packages/stats/renderSummaryReport.d.ts +60 -0
- package/dist/packages/stats/renderSummaryReport.js +68 -0
- package/dist/packages/stats/reportHtml.d.ts +42 -0
- package/dist/packages/stats/reportHtml.js +301 -0
- package/dist/packages/stats/scatter.d.ts +24 -0
- package/dist/packages/stats/scatter.js +1 -0
- package/dist/packages/stats/types.d.ts +269 -0
- package/dist/packages/stats/types.js +1 -0
- package/dist/packages/stats/yield.d.ts +56 -0
- package/dist/packages/stats/yield.js +1 -0
- package/dist/packages/worker/index.d.ts +44 -0
- package/dist/packages/worker/index.js +1 -0
- package/dist/packages/worker/wafermap.worker.d.ts +44 -0
- package/dist/packages/worker/wafermap.worker.js +1 -0
- 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)}
|