@zkmake/three-meter 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/{mount-perf-hud-Rf98QNxM.js → mount-perf-hud-BLs1jkwM.js} +177 -16
- package/dist/mount-perf-hud-BLs1jkwM.js.map +1 -0
- package/dist/{mount-perf-hud-v6kLGJUX.d.ts → mount-perf-hud-CmINOH3T.d.ts} +3 -2
- package/dist/{performance-monitor-Br7x3cPt.d.ts → performance-monitor-CBN05_BC.d.ts} +22 -2
- package/dist/{performance-monitor-C8dhWJWR.js → performance-monitor-yxsxs57v.js} +43 -3
- package/dist/performance-monitor-yxsxs57v.js.map +1 -0
- package/dist/react.d.ts +3 -3
- package/dist/react.js +5 -3
- package/dist/react.js.map +1 -1
- package/dist/styles.css +21 -0
- package/dist/ui.d.ts +8 -3
- package/dist/ui.js +2 -2
- package/package.json +1 -1
- package/dist/mount-perf-hud-Rf98QNxM.js.map +0 -1
- package/dist/performance-monitor-C8dhWJWR.js.map +0 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ load the scene up.
|
|
|
16
16
|
<img
|
|
17
17
|
src="https://raw.githubusercontent.com/zkmake/three-meter/main/docs/hud-full.png"
|
|
18
18
|
width="320"
|
|
19
|
-
alt="The HUD in full mode, dark theme: a theme row (light, system, dark), a dim-on-leave toggle, FPS, CPU and GPU sparklines with live values, and a checkbox list of counters (FPS, calls, CPU, GPU, triangles, lines, points, render passes, geometries, textures, shaders) choosing what the compact card shows. Two discs beside it: a drag grip and the compact/full toggle."
|
|
19
|
+
alt="The HUD in full mode, dark theme: a theme row (light, system, dark), a dim-on-leave toggle, FPS, CPU and GPU sparklines with live values, and a checkbox list of counters (FPS, calls, CPU, GPU, triangles, lines, points, render passes, geometries, textures, shaders) choosing what the compact card shows, then a footer with the three-meter version, the three revision, a WebGL2 backend badge and the GPU name. Two discs beside it: a drag grip and the compact/full toggle."
|
|
20
20
|
/>
|
|
21
21
|
</p>
|
|
22
22
|
|
|
@@ -90,8 +90,8 @@ Canvas is its own React root. To run two canvases on one page, pass the same `st
|
|
|
90
90
|
| `injectStyles` | `true` | Append the stylesheet once per document. |
|
|
91
91
|
| `refreshHz` | `10` | Repaint rate. |
|
|
92
92
|
|
|
93
|
-
`PerformanceMonitor` and `PerfSampler` take `trackGPU` (default true), `gpuQueryPoolSize` (default 5)
|
|
94
|
-
|
|
93
|
+
`PerformanceMonitor` and `PerfSampler` take `trackGPU` (default true), `gpuQueryPoolSize` (default 5),
|
|
94
|
+
`historySize` (default 120) and `frameStatsSize` (default 1000).
|
|
95
95
|
|
|
96
96
|
## Theme
|
|
97
97
|
|
|
@@ -143,6 +143,39 @@ monitor.getEnvironment();
|
|
|
143
143
|
WebGL's unmasked renderer string, and is `null` where the browser hides it. Chrome's WebGPU gives
|
|
144
144
|
vendor and architecture, not the model.
|
|
145
145
|
|
|
146
|
+
## Stutter
|
|
147
|
+
|
|
148
|
+
Average FPS hides a hitch every few seconds. Three rows in the full view catch it, and any of them
|
|
149
|
+
can be ticked into the compact HUD:
|
|
150
|
+
|
|
151
|
+
- **1% low**: mean FPS across the slowest 1% of frames.
|
|
152
|
+
- **Frame p99**: 99% of frames finish within this many ms.
|
|
153
|
+
- **Hitches**: frames over twice the median frame time.
|
|
154
|
+
|
|
155
|
+
They cover the last `frameStatsSize` frames (1000 by default, about 16 s at 60 Hz). Gaps over a
|
|
156
|
+
second, like a hidden tab, are left out. Read them with `monitor.getFrameStats()`:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
monitor.getFrameStats();
|
|
160
|
+
// { frames: 1000, lowFps: 97.6, p99Ms: 10.2, hitches: 3 }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Bug reports
|
|
164
|
+
|
|
165
|
+
The **report** row's copy button puts a Markdown snapshot on the clipboard: versions, backend, GPU,
|
|
166
|
+
the current metrics, the stutter stats, the viewport and the user agent. Paste it into an issue.
|
|
167
|
+
`formatReport(monitor)` from `@zkmake/three-meter/ui` returns the same text.
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
three-meter v0.5.0 · three r186 · WebGL2 · Apple M4 Max
|
|
171
|
+
FPS 120 · 1% low 98 · p99 10.2 ms · hitches 3 / 1,000 frames
|
|
172
|
+
CPU 0.2 ms · GPU 0.7 ms
|
|
173
|
+
Calls 1 · passes 1 · triangles 24,000 · lines 0 · points 0
|
|
174
|
+
Geometries 1 · textures 1 · shaders 1
|
|
175
|
+
Viewport 1280×720 @2x
|
|
176
|
+
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) …
|
|
177
|
+
```
|
|
178
|
+
|
|
146
179
|
## Examples
|
|
147
180
|
|
|
148
181
|
[`examples/site`](examples/site) is what runs at [three-meter.pages.dev](https://three-meter.pages.dev/):
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as PerfRenderer, c as RenderCounts, d as TimingMetric, i as GpuTiming, l as ResourceCounts, n as Environment, o as PerformanceMonitorOptions, r as FrameStats, s as RenderBackend, t as PerformanceMonitor, u as Sample } from "./performance-monitor-CBN05_BC.js";
|
|
2
2
|
//#region src/core/ring-buffer.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Fixed-capacity numeric ring buffer. Backs the per-frame timing history the
|
|
@@ -32,5 +32,5 @@ type AnimationLoop<Args extends unknown[]> = (...args: Args) => void;
|
|
|
32
32
|
*/
|
|
33
33
|
declare function wrapAnimationLoop<Args extends unknown[]>(monitor: PerformanceMonitor, loop: AnimationLoop<Args>): AnimationLoop<Args>;
|
|
34
34
|
//#endregion
|
|
35
|
-
export { type Environment, type GpuTiming, type PerfRenderer, PerformanceMonitor, type PerformanceMonitorOptions, type RenderBackend, type RenderCounts, type ResourceCounts, RingBuffer, type Sample, type TimingMetric, wrapAnimationLoop };
|
|
35
|
+
export { type Environment, type FrameStats, type GpuTiming, type PerfRenderer, PerformanceMonitor, type PerformanceMonitorOptions, type RenderBackend, type RenderCounts, type ResourceCounts, RingBuffer, type Sample, type TimingMetric, wrapAnimationLoop };
|
|
36
36
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as RingBuffer, t as PerformanceMonitor } from "./performance-monitor-
|
|
1
|
+
import { n as RingBuffer, t as PerformanceMonitor } from "./performance-monitor-yxsxs57v.js";
|
|
2
2
|
//#region src/core/wrap-animation-loop.ts
|
|
3
3
|
/**
|
|
4
4
|
* Wrap a `renderer.setAnimationLoop` callback so each tick is one frame:
|
|
@@ -454,6 +454,7 @@ const dockPanel = (element, options = {}) => {
|
|
|
454
454
|
//#region src/ui/icons.ts
|
|
455
455
|
const ICON_MARKUP = {
|
|
456
456
|
blend: "<circle cx=\"9\" cy=\"9\" r=\"7\"/><circle cx=\"15\" cy=\"15\" r=\"7\"/>",
|
|
457
|
+
clipboard: "<rect width=\"8\" height=\"4\" x=\"8\" y=\"2\" rx=\"1\" ry=\"1\"/><path d=\"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2\"/>",
|
|
457
458
|
contrast: "<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 18a6 6 0 0 0 0-12v12z\"/>",
|
|
458
459
|
grip: "<circle cx=\"9\" cy=\"12\" r=\"1\"/><circle cx=\"9\" cy=\"5\" r=\"1\"/><circle cx=\"9\" cy=\"19\" r=\"1\"/><circle cx=\"15\" cy=\"12\" r=\"1\"/><circle cx=\"15\" cy=\"5\" r=\"1\"/><circle cx=\"15\" cy=\"19\" r=\"1\"/>",
|
|
459
460
|
monitor: "<rect width=\"20\" height=\"14\" x=\"2\" y=\"3\" rx=\"2\"/><line x1=\"8\" x2=\"16\" y1=\"21\" y2=\"21\"/><line x1=\"12\" x2=\"12\" y1=\"17\" y2=\"21\"/>",
|
|
@@ -476,7 +477,42 @@ const createIcon = (name, className) => {
|
|
|
476
477
|
};
|
|
477
478
|
//#endregion
|
|
478
479
|
//#region package.json
|
|
479
|
-
var version = "0.
|
|
480
|
+
var version = "0.5.0";
|
|
481
|
+
//#endregion
|
|
482
|
+
//#region src/ui/clipboard.ts
|
|
483
|
+
/**
|
|
484
|
+
* Copy text from a click handler. The async Clipboard API needs a secure
|
|
485
|
+
* context and the `clipboard-write` permission; a dev server opened over a LAN
|
|
486
|
+
* IP has neither, and some embeds deny the permission. The fallback is the old
|
|
487
|
+
* select-a-textarea `execCommand("copy")`, still honoured inside a user gesture.
|
|
488
|
+
*/
|
|
489
|
+
const copyWithSelection = (text) => {
|
|
490
|
+
const textarea = document.createElement("textarea");
|
|
491
|
+
textarea.value = text;
|
|
492
|
+
textarea.setAttribute("readonly", "");
|
|
493
|
+
textarea.style.position = "fixed";
|
|
494
|
+
textarea.style.opacity = "0";
|
|
495
|
+
textarea.style.pointerEvents = "none";
|
|
496
|
+
const previous = document.activeElement;
|
|
497
|
+
document.body.append(textarea);
|
|
498
|
+
textarea.select();
|
|
499
|
+
try {
|
|
500
|
+
return document.execCommand("copy");
|
|
501
|
+
} catch {
|
|
502
|
+
return false;
|
|
503
|
+
} finally {
|
|
504
|
+
textarea.remove();
|
|
505
|
+
previous?.focus?.();
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
const copyText = async (text) => {
|
|
509
|
+
try {
|
|
510
|
+
await navigator.clipboard.writeText(text);
|
|
511
|
+
return true;
|
|
512
|
+
} catch {
|
|
513
|
+
return copyWithSelection(text);
|
|
514
|
+
}
|
|
515
|
+
};
|
|
480
516
|
//#endregion
|
|
481
517
|
//#region src/ui/format.ts
|
|
482
518
|
/**
|
|
@@ -493,6 +529,76 @@ const compact = new Intl.NumberFormat("en-US", {
|
|
|
493
529
|
});
|
|
494
530
|
const formatCount = (value) => Math.abs(value) < COMPACT_FROM ? exact.format(value) : compact.format(value);
|
|
495
531
|
//#endregion
|
|
532
|
+
//#region src/ui/report.ts
|
|
533
|
+
/**
|
|
534
|
+
* A plain-text snapshot of the monitor for bug reports: versions, backend and
|
|
535
|
+
* GPU, the current sample, stutter stats and the page. Fenced as a `text`
|
|
536
|
+
* code block so GitHub and Slack keep the line breaks.
|
|
537
|
+
*/
|
|
538
|
+
const BACKEND_LABELS = {
|
|
539
|
+
webgl: "WebGL",
|
|
540
|
+
webgl2: "WebGL2",
|
|
541
|
+
webgpu: "WebGPU"
|
|
542
|
+
};
|
|
543
|
+
/** `WebGPU`, `WebGL2`, `WebGL2 fallback`, or `""` before the backend settles. */
|
|
544
|
+
const backendLabel = (environment) => {
|
|
545
|
+
if (!environment.backend) return "";
|
|
546
|
+
const label = BACKEND_LABELS[environment.backend];
|
|
547
|
+
return environment.fallback ? `${label} fallback` : label;
|
|
548
|
+
};
|
|
549
|
+
const ms = (value) => `${value.toFixed(1)} ms`;
|
|
550
|
+
const renderReport = ({ environment, page, sample, stats }) => {
|
|
551
|
+
const { render, resources } = sample;
|
|
552
|
+
const heading = [
|
|
553
|
+
`three-meter v${version}`,
|
|
554
|
+
environment.three ? `three r${environment.three}` : "",
|
|
555
|
+
backendLabel(environment),
|
|
556
|
+
environment.gpu ?? ""
|
|
557
|
+
].filter(Boolean);
|
|
558
|
+
const frames = stats.frames === 0 ? `FPS ${Math.round(sample.fps)}` : [
|
|
559
|
+
`FPS ${Math.round(sample.fps)}`,
|
|
560
|
+
`1% low ${Math.round(stats.lowFps)}`,
|
|
561
|
+
`p99 ${ms(stats.p99Ms)}`,
|
|
562
|
+
`hitches ${formatCount(stats.hitches)} / ${formatCount(stats.frames)} frames`
|
|
563
|
+
].join(" · ");
|
|
564
|
+
const lines = [
|
|
565
|
+
heading.join(" · "),
|
|
566
|
+
frames,
|
|
567
|
+
`CPU ${ms(sample.cpu)} · GPU ${sample.gpu.available ? ms(sample.gpu.ms) : "unavailable"}`,
|
|
568
|
+
[
|
|
569
|
+
`Calls ${formatCount(render.calls)}`,
|
|
570
|
+
`passes ${formatCount(render.passes)}`,
|
|
571
|
+
`triangles ${formatCount(render.triangles)}`,
|
|
572
|
+
`lines ${formatCount(render.lines)}`,
|
|
573
|
+
`points ${formatCount(render.points)}`
|
|
574
|
+
].join(" · "),
|
|
575
|
+
[
|
|
576
|
+
`Geometries ${formatCount(resources.geometries)}`,
|
|
577
|
+
`textures ${formatCount(resources.textures)}`,
|
|
578
|
+
`shaders ${formatCount(resources.programs)}`
|
|
579
|
+
].join(" · ")
|
|
580
|
+
];
|
|
581
|
+
if (page) lines.push(`Viewport ${page.width}×${page.height} @${page.dpr}x`, page.userAgent);
|
|
582
|
+
return [
|
|
583
|
+
"```text",
|
|
584
|
+
...lines,
|
|
585
|
+
"```"
|
|
586
|
+
].join("\n");
|
|
587
|
+
};
|
|
588
|
+
const readPage = () => typeof window === "undefined" ? null : {
|
|
589
|
+
dpr: window.devicePixelRatio || 1,
|
|
590
|
+
height: window.innerHeight,
|
|
591
|
+
userAgent: navigator.userAgent,
|
|
592
|
+
width: window.innerWidth
|
|
593
|
+
};
|
|
594
|
+
/** The report for `monitor` right now, as Markdown ready to paste into an issue. */
|
|
595
|
+
const formatReport = (monitor) => renderReport({
|
|
596
|
+
environment: monitor.getEnvironment(),
|
|
597
|
+
page: readPage(),
|
|
598
|
+
sample: monitor.getSample(),
|
|
599
|
+
stats: monitor.getFrameStats()
|
|
600
|
+
});
|
|
601
|
+
//#endregion
|
|
496
602
|
//#region src/ui/sparkline.ts
|
|
497
603
|
function drawSparkline(ctx, width, height, values, style) {
|
|
498
604
|
ctx.clearRect(0, 0, width, height);
|
|
@@ -584,16 +690,8 @@ const THEME_OPTIONS = [
|
|
|
584
690
|
}
|
|
585
691
|
];
|
|
586
692
|
const RELEASE_URL = `https://github.com/zkmake/three-meter/releases/tag/v${version}`;
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
webgl2: "WebGL2",
|
|
590
|
-
webgpu: "WebGPU"
|
|
591
|
-
};
|
|
592
|
-
const backendLabel = (environment) => {
|
|
593
|
-
if (!environment.backend) return "";
|
|
594
|
-
const label = BACKEND_LABELS[environment.backend];
|
|
595
|
-
return environment.fallback ? `${label} fallback` : label;
|
|
596
|
-
};
|
|
693
|
+
/** How long the report button says `copied` / `failed` before reading `copy` again. */
|
|
694
|
+
const COPY_FEEDBACK_MS = 1500;
|
|
597
695
|
const readTiming = (sample, metric) => {
|
|
598
696
|
switch (metric) {
|
|
599
697
|
case "fps": return sample.fps;
|
|
@@ -623,6 +721,21 @@ const NUMBERS = [
|
|
|
623
721
|
label: "GPU",
|
|
624
722
|
read: (sample) => sample.gpu.available ? sample.gpu.ms.toFixed(1) : "—"
|
|
625
723
|
},
|
|
724
|
+
{
|
|
725
|
+
key: "low",
|
|
726
|
+
label: "1% low",
|
|
727
|
+
read: (_sample, stats) => stats.frames ? Math.round(stats.lowFps).toString() : "—"
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
key: "p99",
|
|
731
|
+
label: "Frame p99",
|
|
732
|
+
read: (_sample, stats) => stats.frames ? stats.p99Ms.toFixed(1) : "—"
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
key: "hitches",
|
|
736
|
+
label: "Hitches",
|
|
737
|
+
read: (_sample, stats) => stats.frames ? formatCount(stats.hitches) : "—"
|
|
738
|
+
},
|
|
626
739
|
{
|
|
627
740
|
key: "triangles",
|
|
628
741
|
label: "Triangles",
|
|
@@ -760,7 +873,8 @@ var PerformanceView = class {
|
|
|
760
873
|
const graph = this.graphCanvases.get(config.metric);
|
|
761
874
|
drawSparkline(graph.ctx, graph.width, graph.height, available ? this.monitor.getHistory(config.metric) : [], config.style[this.theme.resolved]);
|
|
762
875
|
}
|
|
763
|
-
|
|
876
|
+
const stats = this.monitor.getFrameStats();
|
|
877
|
+
for (const config of NUMBERS) this.statValueEls.get(config.key).textContent = config.read(sample, stats);
|
|
764
878
|
this.renderFooter();
|
|
765
879
|
}
|
|
766
880
|
/** The monitor caches the environment once its backend settles; until then this re-reads. */
|
|
@@ -782,9 +896,10 @@ var PerformanceView = class {
|
|
|
782
896
|
this.footerHardwareEl.hidden = backend === "" && gpu === "";
|
|
783
897
|
}
|
|
784
898
|
renderHud(sample) {
|
|
899
|
+
const stats = this.hudNumberEls.size > 0 ? this.monitor.getFrameStats() : null;
|
|
785
900
|
for (const [key, element] of this.hudNumberEls) {
|
|
786
901
|
const config = NUMBERS.find((number) => number.key === key);
|
|
787
|
-
if (config) element.textContent = config.read(sample);
|
|
902
|
+
if (config && stats) element.textContent = config.read(sample, stats);
|
|
788
903
|
}
|
|
789
904
|
for (const [metric, graph] of this.hudGraphs) {
|
|
790
905
|
const config = TIMINGS.find((timing) => timing.metric === metric);
|
|
@@ -836,7 +951,7 @@ var PerformanceView = class {
|
|
|
836
951
|
dimLabel.className = "perf-monitor__label";
|
|
837
952
|
dimLabel.textContent = "dim on leave";
|
|
838
953
|
dimRow.append(createIcon("blend", "perf-monitor__icon"), dimLabel, this.dimCheckbox);
|
|
839
|
-
options.append(themeRow, dimRow);
|
|
954
|
+
options.append(themeRow, dimRow, this.buildReportRow());
|
|
840
955
|
const graphs = document.createElement("div");
|
|
841
956
|
graphs.className = "perf-monitor__graphs";
|
|
842
957
|
for (const config of TIMINGS) {
|
|
@@ -983,6 +1098,31 @@ var PerformanceView = class {
|
|
|
983
1098
|
applyTheme(this.element, this.theme);
|
|
984
1099
|
for (const [mode, radio] of this.themeRadios) radio.setAttribute("aria-checked", String(mode === this.theme.effective));
|
|
985
1100
|
}
|
|
1101
|
+
buildReportRow() {
|
|
1102
|
+
const row = document.createElement("div");
|
|
1103
|
+
row.className = "perf-monitor__row perf-monitor__row--static";
|
|
1104
|
+
const label = document.createElement("span");
|
|
1105
|
+
label.className = "perf-monitor__label";
|
|
1106
|
+
label.textContent = "report";
|
|
1107
|
+
const button = document.createElement("button");
|
|
1108
|
+
button.type = "button";
|
|
1109
|
+
button.className = "perf-monitor__button";
|
|
1110
|
+
button.title = "Copy versions, GPU and current metrics as Markdown for a bug report";
|
|
1111
|
+
button.textContent = "copy";
|
|
1112
|
+
let resetTimer = 0;
|
|
1113
|
+
const flash = (text) => {
|
|
1114
|
+
button.textContent = text;
|
|
1115
|
+
window.clearTimeout(resetTimer);
|
|
1116
|
+
resetTimer = window.setTimeout(() => {
|
|
1117
|
+
button.textContent = "copy";
|
|
1118
|
+
}, COPY_FEEDBACK_MS);
|
|
1119
|
+
};
|
|
1120
|
+
button.addEventListener("click", () => {
|
|
1121
|
+
copyText(formatReport(this.monitor)).then((copied) => flash(copied ? "copied" : "failed"));
|
|
1122
|
+
});
|
|
1123
|
+
row.append(createIcon("clipboard", "perf-monitor__icon"), label, button);
|
|
1124
|
+
return row;
|
|
1125
|
+
}
|
|
986
1126
|
buildCheckbox(checked, title, onChange) {
|
|
987
1127
|
const checkbox = document.createElement("input");
|
|
988
1128
|
checkbox.type = "checkbox";
|
|
@@ -1211,6 +1351,27 @@ const PERF_HUD_STYLES = `
|
|
|
1211
1351
|
color: inherit;
|
|
1212
1352
|
}
|
|
1213
1353
|
|
|
1354
|
+
.perf-monitor__button {
|
|
1355
|
+
flex: none;
|
|
1356
|
+
min-width: 7ch;
|
|
1357
|
+
padding: 0 6px;
|
|
1358
|
+
border: 1px solid var(--perf-border);
|
|
1359
|
+
border-radius: 3px;
|
|
1360
|
+
background: var(--perf-row);
|
|
1361
|
+
color: var(--perf-fg-dim);
|
|
1362
|
+
font: inherit;
|
|
1363
|
+
cursor: pointer;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
.perf-monitor__button:hover {
|
|
1367
|
+
color: var(--perf-fg);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
.perf-monitor__button:focus-visible {
|
|
1371
|
+
outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);
|
|
1372
|
+
outline-offset: 1px;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1214
1375
|
.perf-monitor__checkbox {
|
|
1215
1376
|
flex: none;
|
|
1216
1377
|
margin: 0;
|
|
@@ -1605,6 +1766,6 @@ const mountPerfHud = (monitor, options = {}) => {
|
|
|
1605
1766
|
};
|
|
1606
1767
|
};
|
|
1607
1768
|
//#endregion
|
|
1608
|
-
export {
|
|
1769
|
+
export { formatReport as a, HudSettings as c, isThemeMode as d, PerformanceView as i, HudTheme as l, PERF_HUD_STYLES as n, dockPanel as o, injectStyles as r, DEFAULT_STORAGE_KEY as s, mountPerfHud as t, THEME_MODES as u };
|
|
1609
1770
|
|
|
1610
|
-
//# sourceMappingURL=mount-perf-hud-
|
|
1771
|
+
//# sourceMappingURL=mount-perf-hud-BLs1jkwM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount-perf-hud-BLs1jkwM.js","names":[],"sources":["../src/ui/theme.ts","../src/ui/hud-settings.ts","../src/ui/dock-panel.ts","../src/ui/icons.ts","../package.json","../src/ui/clipboard.ts","../src/ui/format.ts","../src/ui/report.ts","../src/ui/sparkline.ts","../src/ui/performance-view.ts","../src/ui/styles.ts","../src/ui/mount-perf-hud.ts"],"sourcesContent":["/**\n * Dark, light, or follow the OS. One instance per HUD: the floating host and\n * the card inside it both paint `resolved` onto `data-theme`, and the\n * stylesheet picks the palette from that attribute.\n *\n * Two layers. `mode` is what the consumer asked for (the `theme` option,\n * `setTheme`). `override` is what the person using the HUD picked in the\n * panel, kept in `HudSettings`; it wins while set. `effective` is whichever\n * applies, and `system` there tracks `prefers-color-scheme` for as long as\n * the instance lives.\n */\ntype ThemeMode = \"dark\" | \"light\" | \"system\";\ntype ResolvedTheme = Exclude<ThemeMode, \"system\">;\n\nconst THEME_MODES: readonly ThemeMode[] = [\"dark\", \"light\", \"system\"];\nconst DARK_SCHEME_QUERY = \"(prefers-color-scheme: dark)\";\n\nconst isThemeMode = (value: unknown): value is ThemeMode =>\n typeof value === \"string\" && (THEME_MODES as readonly string[]).includes(value);\n\nclass HudTheme {\n private current: ThemeMode;\n private userPick: ThemeMode | null = null;\n private readonly listeners = new Set<() => void>();\n private media: MediaQueryList | null = null;\n private readonly onSchemeChange = () => this.notify();\n\n constructor(mode: ThemeMode = \"system\") {\n this.current = mode;\n this.watch();\n }\n\n /** What the consumer asked for. */\n get mode(): ThemeMode {\n return this.current;\n }\n\n /** What the person using the HUD picked in the panel, or `null` for no pick. */\n get override(): ThemeMode | null {\n return this.userPick;\n }\n\n /** The layer that applies: the user's pick if there is one, else the consumer's mode. */\n get effective(): ThemeMode {\n return this.userPick ?? this.current;\n }\n\n /**\n * `dark` or `light`, with `system` resolved against the OS preference.\n * Where `matchMedia` is missing, `system` falls back to `dark`, the HUD's\n * original palette.\n */\n get resolved(): ResolvedTheme {\n const effective = this.effective;\n\n if (effective !== \"system\") {\n return effective;\n }\n\n if (!this.media) {\n return \"dark\";\n }\n\n return this.media.matches ? \"dark\" : \"light\";\n }\n\n setMode(mode: ThemeMode) {\n if (mode === this.current) {\n return;\n }\n\n this.current = mode;\n this.watch();\n this.notify();\n }\n\n setOverride(mode: ThemeMode | null) {\n if (mode === this.userPick) {\n return;\n }\n\n this.userPick = mode;\n this.watch();\n this.notify();\n }\n\n /** Fires on `setMode`, `setOverride` and, in `system`, whenever the OS preference flips. */\n subscribe(listener: () => void) {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /** Stop tracking the OS preference and drop every subscriber. */\n dispose() {\n this.unwatch();\n this.listeners.clear();\n }\n\n private watch() {\n if (this.effective !== \"system\") {\n this.unwatch();\n\n return;\n }\n\n if (this.media || typeof matchMedia !== \"function\") {\n return;\n }\n\n this.media = matchMedia(DARK_SCHEME_QUERY);\n this.media.addEventListener(\"change\", this.onSchemeChange);\n }\n\n private unwatch() {\n this.media?.removeEventListener(\"change\", this.onSchemeChange);\n this.media = null;\n }\n\n private notify() {\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n\n/** Paint the resolved theme onto an element for the stylesheet to key off. */\nconst applyTheme = (element: HTMLElement, theme: HudTheme) => {\n element.dataset.theme = theme.resolved;\n};\n\nexport { applyTheme, HudTheme, isThemeMode, THEME_MODES };\nexport type { ResolvedTheme, ThemeMode };\n","/**\n * Which metrics the compact HUD shows, whether it shows the environment\n * footer, the dim-on-leave toggle, and the theme\n * the person using the HUD picked in the panel (`null` until they pick one,\n * so the consumer's `theme` option applies). One instance per HUD; the\n * full-mode controls and the compact card stay in sync through it. Persists\n * to `localStorage` under `storageKey` (pass `null` to keep it in memory only).\n */\nimport type { TimingMetric } from \"../core/types.ts\";\nimport { isThemeMode, type ThemeMode } from \"./theme.ts\";\n\ntype HudSelection = {\n dim: boolean;\n graphs: ReadonlySet<TimingMetric>;\n /** Version, three revision, backend and GPU footer in the compact HUD. */\n info: boolean;\n numbers: ReadonlySet<string>;\n theme: ThemeMode | null;\n};\n\ntype HudDefaults = {\n dim?: boolean;\n graphs?: readonly TimingMetric[];\n info?: boolean;\n numbers?: readonly string[];\n};\n\ntype HudSettingsOptions = {\n /** localStorage key. Default `three-meter`; `null` disables persistence. */\n storageKey?: string | null;\n /** What a first visit shows. */\n defaults?: HudDefaults;\n};\n\ntype MutableSelection = {\n dim: boolean;\n graphs: Set<TimingMetric>;\n info: boolean;\n numbers: Set<string>;\n theme: ThemeMode | null;\n};\n\nconst DEFAULT_STORAGE_KEY = \"three-meter\";\nconst DEFAULT_DIM = false;\nconst DEFAULT_INFO = false;\nconst DEFAULT_NUMBERS: readonly string[] = [\"fps\", \"calls\", \"cpu\", \"gpu\"];\nconst DEFAULT_GRAPHS: readonly TimingMetric[] = [];\nconst TIMING_METRICS = new Set<TimingMetric>([\"fps\", \"cpu\", \"gpu\"]);\n\nconst isTimingMetric = (value: string): value is TimingMetric =>\n TIMING_METRICS.has(value as TimingMetric);\n\nclass HudSettings {\n private readonly storageKey: string | null;\n private readonly defaults: Required<HudDefaults>;\n private readonly listeners = new Set<() => void>();\n private readonly state: MutableSelection;\n\n constructor(options: HudSettingsOptions = {}) {\n this.storageKey = options.storageKey === undefined ? DEFAULT_STORAGE_KEY : options.storageKey;\n this.defaults = {\n dim: options.defaults?.dim ?? DEFAULT_DIM,\n graphs: options.defaults?.graphs ?? DEFAULT_GRAPHS,\n info: options.defaults?.info ?? DEFAULT_INFO,\n numbers: options.defaults?.numbers ?? DEFAULT_NUMBERS,\n };\n this.state = this.load();\n }\n\n get selection(): HudSelection {\n return this.state;\n }\n\n get dim() {\n return this.state.dim;\n }\n\n setDim(enabled: boolean) {\n this.state.dim = enabled;\n this.persistAndNotify();\n }\n\n get info() {\n return this.state.info;\n }\n\n setInfo(enabled: boolean) {\n this.state.info = enabled;\n this.persistAndNotify();\n }\n\n /** The user's pick in the panel; `null` means none, so the consumer's option applies. */\n get theme() {\n return this.state.theme;\n }\n\n setTheme(mode: ThemeMode | null) {\n this.state.theme = mode;\n this.persistAndNotify();\n }\n\n isNumberEnabled(key: string) {\n return this.state.numbers.has(key);\n }\n\n isGraphEnabled(metric: TimingMetric) {\n return this.state.graphs.has(metric);\n }\n\n setNumberEnabled(key: string, enabled: boolean) {\n if (enabled) {\n this.state.numbers.add(key);\n } else {\n this.state.numbers.delete(key);\n }\n\n this.persistAndNotify();\n }\n\n setGraphEnabled(metric: TimingMetric, enabled: boolean) {\n if (enabled) {\n this.state.graphs.add(metric);\n } else {\n this.state.graphs.delete(metric);\n }\n\n this.persistAndNotify();\n }\n\n subscribe(listener: () => void) {\n this.listeners.add(listener);\n\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private load(): MutableSelection {\n const fallback = (): MutableSelection => ({\n dim: this.defaults.dim,\n graphs: new Set(this.defaults.graphs),\n info: this.defaults.info,\n numbers: new Set(this.defaults.numbers),\n theme: null,\n });\n\n if (this.storageKey === null) {\n return fallback();\n }\n\n try {\n const raw = localStorage.getItem(this.storageKey);\n\n if (!raw) {\n return fallback();\n }\n\n const parsed = JSON.parse(raw) as {\n dim?: boolean;\n graphs?: string[];\n info?: boolean;\n numbers?: string[];\n theme?: unknown;\n };\n\n return {\n dim: parsed.dim ?? this.defaults.dim,\n graphs: new Set((parsed.graphs ?? this.defaults.graphs).filter(isTimingMetric)),\n info: parsed.info ?? this.defaults.info,\n numbers: new Set(parsed.numbers ?? this.defaults.numbers),\n theme: isThemeMode(parsed.theme) ? parsed.theme : null,\n };\n } catch {\n // Malformed or unavailable storage → defaults.\n return fallback();\n }\n }\n\n private persistAndNotify() {\n if (this.storageKey !== null) {\n try {\n localStorage.setItem(\n this.storageKey,\n JSON.stringify({\n dim: this.state.dim,\n graphs: [...this.state.graphs],\n info: this.state.info,\n numbers: [...this.state.numbers],\n // Omitted while null: JSON.stringify drops undefined.\n theme: this.state.theme ?? undefined,\n }),\n );\n } catch {\n // In-memory selection still drives the live views.\n }\n }\n\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n\nexport { DEFAULT_STORAGE_KEY, HudSettings };\nexport type { HudDefaults, HudSelection, HudSettingsOptions };\n","/**\n * Dock a fixed-position panel to a screen edge. Dragging the handle snaps it\n * to the nearest edge on drop; the edge and the offset along it persist under\n * `<storageKey>:placement`. A stored box whose centre sits outside the current\n * viewport is re-docked to whichever edge that box is closest to.\n */\nimport { DEFAULT_STORAGE_KEY } from \"./hud-settings.ts\";\n\nconst INSET = 10;\n/** Room for the stacked control discs on the inward side of the panel. */\nconst SIDE_CLEARANCE = 26;\nconst DRAG_THRESHOLD = 4;\nconst EDGES = [\"left\", \"right\", \"top\", \"bottom\"] as const;\n\ntype ScreenEdge = (typeof EDGES)[number];\n\ntype Placement = {\n /** Top (left/right) or left (top/bottom) of the host, in CSS pixels. */\n along: number;\n edge: ScreenEdge;\n};\n\ntype Box = {\n height: number;\n left: number;\n top: number;\n width: number;\n};\n\ntype DockAlign = \"start\" | \"center\" | \"end\";\n\ntype DefaultPlacement = {\n edge: ScreenEdge;\n /** Where along that edge on a first visit. Default `center`. */\n align?: DockAlign;\n};\n\ntype DockPanelOptions = {\n /** The drag grip. Without one the panel still docks and re-docks on resize. */\n handle?: HTMLElement | null;\n /** First-visit position, before anything is stored. Default left edge, centred. */\n defaultPlacement?: DefaultPlacement;\n /** Shares the HUD's key; `null` disables persistence. Default `three-meter`. */\n storageKey?: string | null;\n};\n\ntype DockHandle = {\n /** Re-resolve against the current viewport and panel size (after a mode change, say). */\n refresh: () => Placement;\n dispose: () => void;\n};\n\nconst viewportBox = (): Box => ({\n height: window.innerHeight,\n left: 0,\n top: 0,\n width: window.innerWidth,\n});\n\nconst clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\n\nconst panelBox = (element: HTMLElement): Box => {\n const rect = element.getBoundingClientRect();\n\n return {\n height: rect.height || element.offsetHeight,\n left: rect.left,\n top: rect.top,\n width: rect.width || element.offsetWidth,\n };\n};\n\nconst clampAlong = (edge: ScreenEdge, along: number, view: Box, panel: Box) => {\n if (edge === \"left\" || edge === \"right\") {\n return clamp(along, INSET, Math.max(INSET, view.height - panel.height - INSET));\n }\n\n // Tools sit on the right (inward) unless the panel is on the right edge.\n return clamp(along, INSET, Math.max(INSET, view.width - panel.width - INSET - SIDE_CLEARANCE));\n};\n\nconst ghostBox = (placement: Placement, panel: Box, view: Box): Box => {\n switch (placement.edge) {\n case \"left\":\n return { height: panel.height, left: INSET, top: placement.along, width: panel.width };\n case \"right\":\n return {\n height: panel.height,\n left: view.width - INSET - panel.width,\n top: placement.along,\n width: panel.width,\n };\n case \"top\":\n return { height: panel.height, left: placement.along, top: INSET, width: panel.width };\n case \"bottom\":\n return {\n height: panel.height,\n left: placement.along,\n top: view.height - INSET - panel.height,\n width: panel.width,\n };\n }\n};\n\nconst centerOutside = (box: Box, view: Box) => {\n const x = box.left + box.width / 2;\n const y = box.top + box.height / 2;\n\n return x < 0 || y < 0 || x > view.width || y > view.height;\n};\n\nconst snapBoxToEdge = (box: Box, view: Box): Placement => {\n const distances: Record<ScreenEdge, number> = {\n bottom: view.height - (box.top + box.height),\n left: box.left,\n right: view.width - (box.left + box.width),\n top: box.top,\n };\n\n let edge: ScreenEdge = \"left\";\n let nearest = Number.POSITIVE_INFINITY;\n\n for (const next of EDGES) {\n if (distances[next] < nearest) {\n nearest = distances[next];\n edge = next;\n }\n }\n\n const along = edge === \"left\" || edge === \"right\" ? box.top : box.left;\n\n return { along: Math.round(clampAlong(edge, along, view, box)), edge };\n};\n\nconst initialPlacement = (preset: DefaultPlacement, view: Box, panel: Box): Placement => {\n const { edge } = preset;\n const vertical = edge === \"left\" || edge === \"right\";\n const span = vertical ? view.height : view.width;\n const size = vertical ? panel.height : panel.width;\n const along =\n preset.align === \"start\" ? 0 : preset.align === \"end\" ? span - size : (span - size) / 2;\n\n return { along: Math.round(clampAlong(edge, along, view, panel)), edge };\n};\n\nconst isEdge = (value: unknown): value is ScreenEdge =>\n typeof value === \"string\" && (EDGES as readonly string[]).includes(value);\n\nconst readPlacement = (key: string | null): Placement | null => {\n if (key === null) {\n return null;\n }\n\n try {\n const raw = localStorage.getItem(key);\n\n if (!raw) {\n return null;\n }\n\n const parsed = JSON.parse(raw) as { along?: unknown; edge?: unknown };\n\n if (\n typeof parsed.along !== \"number\" ||\n !Number.isFinite(parsed.along) ||\n !isEdge(parsed.edge)\n ) {\n return null;\n }\n\n return { along: parsed.along, edge: parsed.edge };\n } catch {\n return null;\n }\n};\n\nconst writePlacement = (key: string | null, placement: Placement) => {\n if (key === null) {\n return;\n }\n\n try {\n localStorage.setItem(key, JSON.stringify(placement));\n } catch {\n // Best-effort; the live dock still applies.\n }\n};\n\nconst resolvePlacement = (\n stored: Placement | null,\n preset: DefaultPlacement,\n panel: Box,\n view: Box,\n): Placement => {\n if (!stored) {\n return initialPlacement(preset, view, panel);\n }\n\n const ghost = ghostBox(stored, panel, view);\n\n if (centerOutside(ghost, view)) {\n return snapBoxToEdge(ghost, view);\n }\n\n return {\n along: Math.round(clampAlong(stored.edge, stored.along, view, panel)),\n edge: stored.edge,\n };\n};\n\nconst paintPlacement = (element: HTMLElement, placement: Placement) => {\n element.dataset.edge = placement.edge;\n element.style.left = \"\";\n element.style.right = \"\";\n element.style.top = \"\";\n element.style.bottom = \"\";\n element.style.transform = \"\";\n\n switch (placement.edge) {\n case \"left\":\n element.style.left = `${INSET}px`;\n element.style.top = `${placement.along}px`;\n break;\n case \"right\":\n element.style.right = `${INSET}px`;\n element.style.top = `${placement.along}px`;\n break;\n case \"top\":\n element.style.top = `${INSET}px`;\n element.style.left = `${placement.along}px`;\n break;\n case \"bottom\":\n element.style.bottom = `${INSET}px`;\n element.style.left = `${placement.along}px`;\n break;\n }\n};\n\n/**\n * Dock `element` from storage (or the left centre), re-dock on resize, and\n * snap to the nearest edge when the handle drops.\n */\nconst dockPanel = (element: HTMLElement, options: DockPanelOptions = {}): DockHandle => {\n const baseKey = options.storageKey === undefined ? DEFAULT_STORAGE_KEY : options.storageKey;\n const key = baseKey === null ? null : `${baseKey}:placement`;\n const handle = options.handle ?? null;\n const preset = options.defaultPlacement ?? { edge: \"left\" };\n\n const apply = (): Placement => {\n const stored = readPlacement(key);\n const view = viewportBox();\n\n if (element.classList.contains(\"is-dragging\")) {\n return stored ?? initialPlacement(preset, view, panelBox(element));\n }\n\n const panel = panelBox(element);\n\n if (panel.width === 0 || panel.height === 0) {\n return stored ?? initialPlacement(preset, view, panel);\n }\n\n const next = resolvePlacement(stored, preset, panel, view);\n paintPlacement(element, next);\n\n if (!stored || stored.edge !== next.edge || stored.along !== next.along) {\n writePlacement(key, next);\n }\n\n return next;\n };\n\n apply();\n requestAnimationFrame(apply);\n\n const onResize = () => apply();\n window.addEventListener(\"resize\", onResize);\n\n const resizeObserver = new ResizeObserver(() => apply());\n resizeObserver.observe(element);\n\n const disposeBase = () => {\n window.removeEventListener(\"resize\", onResize);\n resizeObserver.disconnect();\n };\n\n if (!handle) {\n return { dispose: disposeBase, refresh: apply };\n }\n\n let pointerId: number | null = null;\n let originX = 0;\n let originY = 0;\n let grabX = 0;\n let grabY = 0;\n let dragging = false;\n\n const finish = (event: PointerEvent) => {\n if (pointerId !== event.pointerId) {\n return;\n }\n\n if (dragging) {\n const next = snapBoxToEdge(panelBox(element), viewportBox());\n paintPlacement(element, next);\n writePlacement(key, next);\n }\n\n dragging = false;\n pointerId = null;\n element.classList.remove(\"is-dragging\");\n\n try {\n handle.releasePointerCapture(event.pointerId);\n } catch {\n // Capture was never set.\n }\n };\n\n const onPointerDown = (event: PointerEvent) => {\n if (event.button !== 0) {\n return;\n }\n\n const box = panelBox(element);\n pointerId = event.pointerId;\n originX = event.clientX;\n originY = event.clientY;\n grabX = event.clientX - box.left;\n grabY = event.clientY - box.top;\n dragging = false;\n handle.setPointerCapture(event.pointerId);\n };\n\n const onPointerMove = (event: PointerEvent) => {\n if (pointerId !== event.pointerId) {\n return;\n }\n\n const dx = event.clientX - originX;\n const dy = event.clientY - originY;\n\n if (!dragging && dx * dx + dy * dy < DRAG_THRESHOLD * DRAG_THRESHOLD) {\n return;\n }\n\n dragging = true;\n element.classList.add(\"is-dragging\");\n element.style.right = \"\";\n element.style.bottom = \"\";\n element.style.transform = \"\";\n element.style.left = `${event.clientX - grabX}px`;\n element.style.top = `${event.clientY - grabY}px`;\n };\n\n handle.addEventListener(\"pointerdown\", onPointerDown);\n handle.addEventListener(\"pointermove\", onPointerMove);\n handle.addEventListener(\"pointerup\", finish);\n handle.addEventListener(\"pointercancel\", finish);\n\n return {\n dispose: () => {\n disposeBase();\n handle.removeEventListener(\"pointerdown\", onPointerDown);\n handle.removeEventListener(\"pointermove\", onPointerMove);\n handle.removeEventListener(\"pointerup\", finish);\n handle.removeEventListener(\"pointercancel\", finish);\n },\n refresh: apply,\n };\n};\n\nexport { dockPanel };\nexport type { DefaultPlacement, DockAlign, DockHandle, DockPanelOptions, Placement, ScreenEdge };\n","/**\n * The Lucide marks the HUD chrome uses, inlined so the package carries no\n * icon dependency. The markup is static, so nothing user-supplied reaches it.\n */\ntype IconName =\n | \"blend\"\n | \"clipboard\"\n | \"contrast\"\n | \"grip\"\n | \"monitor\"\n | \"moon\"\n | \"sliders\"\n | \"sun\";\n\nconst ICON_MARKUP: Record<IconName, string> = {\n blend: '<circle cx=\"9\" cy=\"9\" r=\"7\"/><circle cx=\"15\" cy=\"15\" r=\"7\"/>',\n clipboard:\n '<rect width=\"8\" height=\"4\" x=\"8\" y=\"2\" rx=\"1\" ry=\"1\"/>' +\n '<path d=\"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2\"/>',\n contrast: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 18a6 6 0 0 0 0-12v12z\"/>',\n grip:\n '<circle cx=\"9\" cy=\"12\" r=\"1\"/><circle cx=\"9\" cy=\"5\" r=\"1\"/><circle cx=\"9\" cy=\"19\" r=\"1\"/>' +\n '<circle cx=\"15\" cy=\"12\" r=\"1\"/><circle cx=\"15\" cy=\"5\" r=\"1\"/><circle cx=\"15\" cy=\"19\" r=\"1\"/>',\n monitor:\n '<rect width=\"20\" height=\"14\" x=\"2\" y=\"3\" rx=\"2\"/>' +\n '<line x1=\"8\" x2=\"16\" y1=\"21\" y2=\"21\"/><line x1=\"12\" x2=\"12\" y1=\"17\" y2=\"21\"/>',\n moon: '<path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"/>',\n sun:\n '<circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2\"/><path d=\"M12 20v2\"/>' +\n '<path d=\"m4.93 4.93 1.41 1.41\"/><path d=\"m17.66 17.66 1.41 1.41\"/>' +\n '<path d=\"M2 12h2\"/><path d=\"M20 12h2\"/>' +\n '<path d=\"m6.34 17.66-1.41 1.41\"/><path d=\"m19.07 4.93-1.41 1.41\"/>',\n sliders:\n '<line x1=\"21\" x2=\"14\" y1=\"4\" y2=\"4\"/><line x1=\"10\" x2=\"3\" y1=\"4\" y2=\"4\"/>' +\n '<line x1=\"21\" x2=\"12\" y1=\"12\" y2=\"12\"/><line x1=\"8\" x2=\"3\" y1=\"12\" y2=\"12\"/>' +\n '<line x1=\"21\" x2=\"16\" y1=\"20\" y2=\"20\"/><line x1=\"12\" x2=\"3\" y1=\"20\" y2=\"20\"/>' +\n '<line x1=\"14\" x2=\"14\" y1=\"2\" y2=\"6\"/><line x1=\"8\" x2=\"8\" y1=\"10\" y2=\"14\"/>' +\n '<line x1=\"16\" x2=\"16\" y1=\"18\" y2=\"22\"/>',\n};\n\nconst createIcon = (name: IconName, className: string) => {\n const svg = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\n svg.setAttribute(\"viewBox\", \"0 0 24 24\");\n svg.setAttribute(\"fill\", \"none\");\n svg.setAttribute(\"stroke\", \"currentColor\");\n svg.setAttribute(\"stroke-width\", \"2\");\n svg.setAttribute(\"stroke-linecap\", \"round\");\n svg.setAttribute(\"stroke-linejoin\", \"round\");\n svg.setAttribute(\"aria-hidden\", \"true\");\n svg.setAttribute(\"class\", className);\n svg.innerHTML = ICON_MARKUP[name];\n\n return svg;\n};\n\nexport { createIcon };\nexport type { IconName };\n","","/**\n * Copy text from a click handler. The async Clipboard API needs a secure\n * context and the `clipboard-write` permission; a dev server opened over a LAN\n * IP has neither, and some embeds deny the permission. The fallback is the old\n * select-a-textarea `execCommand(\"copy\")`, still honoured inside a user gesture.\n */\nconst copyWithSelection = (text: string) => {\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.setAttribute(\"readonly\", \"\");\n textarea.style.position = \"fixed\";\n textarea.style.opacity = \"0\";\n textarea.style.pointerEvents = \"none\";\n\n const previous = document.activeElement as HTMLElement | null;\n document.body.append(textarea);\n textarea.select();\n\n try {\n return document.execCommand(\"copy\");\n } catch {\n return false;\n } finally {\n textarea.remove();\n previous?.focus?.();\n }\n};\n\nconst copyText = async (text: string): Promise<boolean> => {\n try {\n await navigator.clipboard.writeText(text);\n\n return true;\n } catch {\n return copyWithSelection(text);\n }\n};\n\nexport { copyText };\n","/**\n * Number formatting for the card. Counts stay exact with thousands separators\n * while they fit the compact grid's 6ch value column (`99,999`), then switch\n * to compact notation (`250K`, `1.4M`, `12.3M`, `1.2B`) so a triangle count in\n * the millions can't run into the neighbouring label.\n */\nconst COMPACT_FROM = 100_000;\n\nconst exact = new Intl.NumberFormat(\"en-US\");\nconst compact = new Intl.NumberFormat(\"en-US\", {\n maximumFractionDigits: 1,\n notation: \"compact\",\n});\n\nconst formatCount = (value: number) =>\n Math.abs(value) < COMPACT_FROM ? exact.format(value) : compact.format(value);\n\nexport { formatCount };\n","/**\n * A plain-text snapshot of the monitor for bug reports: versions, backend and\n * GPU, the current sample, stutter stats and the page. Fenced as a `text`\n * code block so GitHub and Slack keep the line breaks.\n */\nimport { version } from \"../../package.json\";\nimport type { PerformanceMonitor } from \"../core/performance-monitor.ts\";\nimport type { Environment, FrameStats, Sample } from \"../core/types.ts\";\nimport { formatCount } from \"./format.ts\";\n\ntype ReportPage = {\n dpr: number;\n height: number;\n userAgent: string;\n width: number;\n};\n\ntype ReportData = {\n environment: Environment;\n page: ReportPage | null;\n sample: Sample;\n stats: FrameStats;\n};\n\nconst BACKEND_LABELS = { webgl: \"WebGL\", webgl2: \"WebGL2\", webgpu: \"WebGPU\" } as const;\n\n/** `WebGPU`, `WebGL2`, `WebGL2 fallback`, or `\"\"` before the backend settles. */\nconst backendLabel = (environment: Environment) => {\n if (!environment.backend) {\n return \"\";\n }\n\n const label = BACKEND_LABELS[environment.backend];\n\n return environment.fallback ? `${label} fallback` : label;\n};\n\nconst ms = (value: number) => `${value.toFixed(1)} ms`;\n\nconst renderReport = ({ environment, page, sample, stats }: ReportData): string => {\n const { render, resources } = sample;\n const heading = [\n `three-meter v${version}`,\n environment.three ? `three r${environment.three}` : \"\",\n backendLabel(environment),\n environment.gpu ?? \"\",\n ].filter(Boolean);\n\n const frames =\n stats.frames === 0\n ? `FPS ${Math.round(sample.fps)}`\n : [\n `FPS ${Math.round(sample.fps)}`,\n `1% low ${Math.round(stats.lowFps)}`,\n `p99 ${ms(stats.p99Ms)}`,\n `hitches ${formatCount(stats.hitches)} / ${formatCount(stats.frames)} frames`,\n ].join(\" · \");\n\n const lines = [\n heading.join(\" · \"),\n frames,\n `CPU ${ms(sample.cpu)} · GPU ${sample.gpu.available ? ms(sample.gpu.ms) : \"unavailable\"}`,\n [\n `Calls ${formatCount(render.calls)}`,\n `passes ${formatCount(render.passes)}`,\n `triangles ${formatCount(render.triangles)}`,\n `lines ${formatCount(render.lines)}`,\n `points ${formatCount(render.points)}`,\n ].join(\" · \"),\n [\n `Geometries ${formatCount(resources.geometries)}`,\n `textures ${formatCount(resources.textures)}`,\n `shaders ${formatCount(resources.programs)}`,\n ].join(\" · \"),\n ];\n\n if (page) {\n lines.push(`Viewport ${page.width}×${page.height} @${page.dpr}x`, page.userAgent);\n }\n\n return [\"```text\", ...lines, \"```\"].join(\"\\n\");\n};\n\nconst readPage = (): ReportPage | null =>\n typeof window === \"undefined\"\n ? null\n : {\n dpr: window.devicePixelRatio || 1,\n height: window.innerHeight,\n userAgent: navigator.userAgent,\n width: window.innerWidth,\n };\n\n/** The report for `monitor` right now, as Markdown ready to paste into an issue. */\nconst formatReport = (monitor: PerformanceMonitor): string =>\n renderReport({\n environment: monitor.getEnvironment(),\n page: readPage(),\n sample: monitor.getSample(),\n stats: monitor.getFrameStats(),\n });\n\nexport { backendLabel, formatReport, renderReport };\nexport type { ReportData, ReportPage };\n","/**\n * Minimal canvas sparkline. The caller owns the canvas, its size, and any\n * device-pixel-ratio transform; this just paints a filled line series in\n * logical (CSS) pixel coordinates.\n */\ntype SparklineStyle = {\n fill: string;\n stroke: string;\n};\n\nfunction drawSparkline(\n ctx: CanvasRenderingContext2D,\n width: number,\n height: number,\n values: readonly number[],\n style: SparklineStyle,\n) {\n ctx.clearRect(0, 0, width, height);\n\n if (values.length < 2) {\n return;\n }\n\n let max = 0;\n\n for (const value of values) {\n if (value > max) {\n max = value;\n }\n }\n\n const range = max * 1.1 || 1;\n const stepX = width / (values.length - 1);\n const toY = (value: number) => height - (value / range) * height;\n\n ctx.beginPath();\n ctx.moveTo(0, toY(values[0]!));\n\n for (let index = 1; index < values.length; index += 1) {\n ctx.lineTo(index * stepX, toY(values[index]!));\n }\n\n ctx.lineWidth = 1;\n ctx.strokeStyle = style.stroke;\n ctx.stroke();\n\n ctx.lineTo(width, height);\n ctx.lineTo(0, height);\n ctx.closePath();\n ctx.fillStyle = style.fill;\n ctx.fill();\n}\n\nexport { drawSparkline };\nexport type { SparklineStyle };\n","import { version } from \"../../package.json\";\nimport type { PerformanceMonitor } from \"../core/performance-monitor.ts\";\nimport type { FrameStats, Sample, TimingMetric } from \"../core/types.ts\";\nimport { copyText } from \"./clipboard.ts\";\nimport { formatCount } from \"./format.ts\";\nimport { HudSettings } from \"./hud-settings.ts\";\nimport { createIcon } from \"./icons.ts\";\nimport { backendLabel, formatReport } from \"./report.ts\";\nimport { drawSparkline, type SparklineStyle } from \"./sparkline.ts\";\nimport { applyTheme, HudTheme, type ResolvedTheme, type ThemeMode } from \"./theme.ts\";\n\ntype PerformanceViewMode = \"full\" | \"compact\";\n\ntype PerformanceViewOptions = {\n monitor: PerformanceMonitor;\n /** `full` is the checkbox list; `compact` the card it configures. Default `full`. */\n mode?: PerformanceViewMode;\n /** Repaint rate. Default 10. */\n refreshHz?: number;\n /** Share one between views to keep them in sync; a fresh instance otherwise. */\n settings?: HudSettings;\n /**\n * Share the host's so the card and its chrome switch together. Otherwise a\n * fresh `system` instance is created and disposed with the view.\n */\n theme?: HudTheme;\n};\n\ntype TimingConfig = {\n format: (value: number) => string;\n label: string;\n metric: TimingMetric;\n /** Canvas can't read CSS custom properties, so the sparkline palette lives here per theme. */\n style: Record<ResolvedTheme, SparklineStyle>;\n unit: string;\n};\n\ntype NumberConfig = {\n key: string;\n label: string;\n read: (sample: Sample, stats: FrameStats) => string;\n};\n\nconst TIMINGS: TimingConfig[] = [\n {\n format: (value) => Math.round(value).toString(),\n label: \"FPS\",\n metric: \"fps\",\n style: {\n dark: { fill: \"rgba(74, 222, 128, 0.15)\", stroke: \"#4ade80\" },\n light: { fill: \"rgba(22, 163, 74, 0.12)\", stroke: \"#16a34a\" },\n },\n unit: \"\",\n },\n {\n format: (value) => value.toFixed(1),\n label: \"CPU\",\n metric: \"cpu\",\n style: {\n dark: { fill: \"rgba(96, 165, 250, 0.15)\", stroke: \"#60a5fa\" },\n light: { fill: \"rgba(37, 99, 235, 0.12)\", stroke: \"#2563eb\" },\n },\n unit: \"ms\",\n },\n {\n format: (value) => value.toFixed(1),\n label: \"GPU\",\n metric: \"gpu\",\n style: {\n dark: { fill: \"rgba(244, 114, 182, 0.15)\", stroke: \"#f472b6\" },\n light: { fill: \"rgba(219, 39, 119, 0.12)\", stroke: \"#db2777\" },\n },\n unit: \"ms\",\n },\n];\n\nconst THEME_OPTIONS: { icon: \"sun\" | \"monitor\" | \"moon\"; label: string; mode: ThemeMode }[] = [\n { icon: \"sun\", label: \"Light theme\", mode: \"light\" },\n { icon: \"monitor\", label: \"Match the system theme\", mode: \"system\" },\n { icon: \"moon\", label: \"Dark theme\", mode: \"dark\" },\n];\n\nconst RELEASE_URL = `https://github.com/zkmake/three-meter/releases/tag/v${version}`;\n\n/** How long the report button says `copied` / `failed` before reading `copy` again. */\nconst COPY_FEEDBACK_MS = 1500;\n\nconst readTiming = (sample: Sample, metric: TimingMetric) => {\n switch (metric) {\n case \"fps\":\n return sample.fps;\n case \"cpu\":\n return sample.cpu;\n case \"gpu\":\n return sample.gpu.ms;\n }\n};\n\nconst isTimingAvailable = (sample: Sample, metric: TimingMetric) =>\n metric !== \"gpu\" || sample.gpu.available;\n\nconst NUMBERS: NumberConfig[] = [\n { key: \"fps\", label: \"FPS\", read: (sample) => Math.round(sample.fps).toString() },\n { key: \"calls\", label: \"Calls\", read: (sample) => formatCount(sample.render.calls) },\n { key: \"cpu\", label: \"CPU\", read: (sample) => sample.cpu.toFixed(1) },\n {\n key: \"gpu\",\n label: \"GPU\",\n read: (sample) => (sample.gpu.available ? sample.gpu.ms.toFixed(1) : \"—\"),\n },\n {\n key: \"low\",\n label: \"1% low\",\n read: (_sample, stats) => (stats.frames ? Math.round(stats.lowFps).toString() : \"—\"),\n },\n {\n key: \"p99\",\n label: \"Frame p99\",\n read: (_sample, stats) => (stats.frames ? stats.p99Ms.toFixed(1) : \"—\"),\n },\n {\n key: \"hitches\",\n label: \"Hitches\",\n read: (_sample, stats) => (stats.frames ? formatCount(stats.hitches) : \"—\"),\n },\n {\n key: \"triangles\",\n label: \"Triangles\",\n read: (sample) => formatCount(sample.render.triangles),\n },\n { key: \"lines\", label: \"Lines\", read: (sample) => formatCount(sample.render.lines) },\n { key: \"points\", label: \"Points\", read: (sample) => formatCount(sample.render.points) },\n {\n key: \"passes\",\n label: \"Render passes\",\n read: (sample) => formatCount(sample.render.passes),\n },\n {\n key: \"geometries\",\n label: \"Geometries\",\n read: (sample) => formatCount(sample.resources.geometries),\n },\n {\n key: \"textures\",\n label: \"Textures\",\n read: (sample) => formatCount(sample.resources.textures),\n },\n {\n key: \"shaders\",\n label: \"Shaders\",\n read: (sample) => formatCount(sample.resources.programs),\n },\n];\n\ntype GraphCanvas = {\n canvas: HTMLCanvasElement;\n ctx: CanvasRenderingContext2D;\n height: number;\n width: number;\n};\n\ntype HudGraph = GraphCanvas & {\n valueEl: HTMLElement;\n};\n\n/**\n * Host-agnostic widget that paints {@link Sample}s. Mount {@link element}\n * anywhere; the view drives its own throttled repaint. Compact is the floating\n * HUD; full is the checkbox list that chooses what compact shows. Styles are\n * not injected here. Call `injectStyles()` or use `mountPerfHud`.\n */\nclass PerformanceView {\n readonly element: HTMLDivElement;\n readonly settings: HudSettings;\n readonly theme: HudTheme;\n\n private readonly ownsTheme: boolean;\n private readonly unsubscribeTheme: () => void;\n private readonly monitor: PerformanceMonitor;\n private readonly minIntervalMs: number;\n private mode: PerformanceViewMode;\n private readonly graphValueEls = new Map<TimingMetric, HTMLElement>();\n private readonly graphCanvases = new Map<TimingMetric, GraphCanvas>();\n private readonly graphCheckboxes = new Map<TimingMetric, HTMLInputElement>();\n private readonly statValueEls = new Map<string, HTMLElement>();\n private readonly statCheckboxes = new Map<string, HTMLInputElement>();\n private dimCheckbox!: HTMLInputElement;\n private infoCheckbox!: HTMLInputElement;\n private readonly themeRadios = new Map<ThemeMode, HTMLButtonElement>();\n private footerThreeEl!: HTMLElement;\n private footerHardwareEl!: HTMLElement;\n private footerBackendEl!: HTMLElement;\n private footerGpuEl!: HTMLElement;\n private lastEnvironmentKey = \"\";\n private hudGraphsEl!: HTMLElement;\n private hudGridEl!: HTMLElement;\n private readonly hudGraphs = new Map<TimingMetric, HudGraph>();\n private readonly hudNumberEls = new Map<string, HTMLElement>();\n private rafId: number | null = null;\n private lastPaintAt = 0;\n private readonly resizeObserver: ResizeObserver;\n private readonly unsubscribe: () => void;\n\n constructor(options: PerformanceViewOptions) {\n this.monitor = options.monitor;\n this.settings = options.settings ?? new HudSettings();\n this.ownsTheme = !options.theme;\n this.theme = options.theme ?? new HudTheme();\n // The panel's pick, restored from storage, sits on top of the consumer's mode.\n this.theme.setOverride(this.settings.theme);\n this.mode = options.mode ?? \"full\";\n this.minIntervalMs = 1000 / (options.refreshHz ?? 10);\n this.element = document.createElement(\"div\");\n this.element.className = \"perf-monitor\";\n this.applyModeClass();\n this.resizeObserver = new ResizeObserver(() => this.resizeCanvases());\n this.build();\n this.applyInfoClass();\n this.rebuildHud();\n this.onThemeChanged();\n this.unsubscribe = this.settings.subscribe(() => this.onSelectionChanged());\n this.unsubscribeTheme = this.theme.subscribe(() => this.onThemeChanged());\n }\n\n start() {\n if (this.rafId === null) {\n this.rafId = requestAnimationFrame(this.frame);\n }\n }\n\n stop() {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n }\n\n setMode(mode: PerformanceViewMode) {\n if (mode === this.mode) {\n return;\n }\n\n this.mode = mode;\n this.applyModeClass();\n this.resizeCanvases();\n }\n\n getMode() {\n return this.mode;\n }\n\n dispose() {\n this.stop();\n this.unsubscribe();\n this.unsubscribeTheme();\n\n if (this.ownsTheme) {\n this.theme.dispose();\n }\n\n this.resizeObserver.disconnect();\n this.element.remove();\n }\n\n private frame = (now: number) => {\n this.rafId = requestAnimationFrame(this.frame);\n\n if (now - this.lastPaintAt < this.minIntervalMs) {\n return;\n }\n\n this.lastPaintAt = now;\n this.render();\n };\n\n private render() {\n const sample = this.monitor.getSample();\n\n if (this.mode === \"compact\") {\n this.renderHud(sample);\n\n if (this.settings.info) {\n this.renderFooter();\n }\n\n return;\n }\n\n for (const config of TIMINGS) {\n const available = isTimingAvailable(sample, config.metric);\n const valueEl = this.graphValueEls.get(config.metric)!;\n valueEl.textContent = available\n ? this.formatTiming(config, readTiming(sample, config.metric))\n : \"unavailable\";\n\n const graph = this.graphCanvases.get(config.metric)!;\n drawSparkline(\n graph.ctx,\n graph.width,\n graph.height,\n available ? this.monitor.getHistory(config.metric) : [],\n config.style[this.theme.resolved],\n );\n }\n\n const stats = this.monitor.getFrameStats();\n\n for (const config of NUMBERS) {\n this.statValueEls.get(config.key)!.textContent = config.read(sample, stats);\n }\n\n this.renderFooter();\n }\n\n /** The monitor caches the environment once its backend settles; until then this re-reads. */\n private renderFooter() {\n const environment = this.monitor.getEnvironment();\n const three = environment.three ? `three r${environment.three}` : \"\";\n const backend = backendLabel(environment);\n const gpu = environment.gpu ?? \"\";\n const key = `${three}\\n${backend}\\n${gpu}`;\n\n if (key === this.lastEnvironmentKey) {\n return;\n }\n\n this.lastEnvironmentKey = key;\n this.footerThreeEl.textContent = three;\n this.footerBackendEl.textContent = backend;\n this.footerBackendEl.hidden = backend === \"\";\n this.footerBackendEl.title = environment.fallback\n ? \"WebGPURenderer couldn't get WebGPU and fell back to WebGL2\"\n : \"\";\n this.footerBackendEl.classList.toggle(\"is-fallback\", environment.fallback);\n this.footerGpuEl.textContent = gpu;\n this.footerGpuEl.title = gpu;\n this.footerHardwareEl.hidden = backend === \"\" && gpu === \"\";\n }\n\n private renderHud(sample: Sample) {\n const stats = this.hudNumberEls.size > 0 ? this.monitor.getFrameStats() : null;\n\n for (const [key, element] of this.hudNumberEls) {\n const config = NUMBERS.find((number) => number.key === key);\n\n if (config && stats) {\n element.textContent = config.read(sample, stats);\n }\n }\n\n for (const [metric, graph] of this.hudGraphs) {\n const config = TIMINGS.find((timing) => timing.metric === metric)!;\n const available = isTimingAvailable(sample, metric);\n\n graph.valueEl.textContent = available\n ? this.formatTiming(config, readTiming(sample, metric))\n : \"—\";\n\n drawSparkline(\n graph.ctx,\n graph.width,\n graph.height,\n available ? this.monitor.getHistory(metric) : [],\n config.style[this.theme.resolved],\n );\n }\n }\n\n private formatTiming(config: TimingConfig, value: number) {\n return `${config.format(value)}${config.unit ? ` ${config.unit}` : \"\"}`;\n }\n\n private build() {\n const hud = document.createElement(\"div\");\n hud.className = \"perf-monitor__hud\";\n this.hudGraphsEl = document.createElement(\"div\");\n this.hudGraphsEl.className = \"perf-monitor__hud-graphs\";\n this.hudGridEl = document.createElement(\"div\");\n this.hudGridEl.className = \"perf-monitor__hud-grid\";\n hud.append(this.hudGraphsEl, this.hudGridEl);\n\n // Option rows read icon, label, control; the stat rows below keep the\n // checkbox first, so the two groups scan differently on purpose.\n const options = document.createElement(\"div\");\n options.className = \"perf-monitor__section perf-monitor__options\";\n\n const themeRow = document.createElement(\"div\");\n themeRow.className = \"perf-monitor__row perf-monitor__row--static\";\n\n const themeLabel = document.createElement(\"span\");\n themeLabel.className = \"perf-monitor__label\";\n themeLabel.textContent = \"theme\";\n\n const segment = document.createElement(\"div\");\n segment.className = \"perf-monitor__segment\";\n segment.setAttribute(\"role\", \"radiogroup\");\n segment.setAttribute(\"aria-label\", \"Panel theme\");\n\n for (const option of THEME_OPTIONS) {\n const radio = document.createElement(\"button\");\n radio.type = \"button\";\n radio.className = \"perf-monitor__segment-option\";\n radio.setAttribute(\"role\", \"radio\");\n radio.setAttribute(\"aria-checked\", \"false\");\n radio.setAttribute(\"aria-label\", option.label);\n radio.title = option.label;\n radio.append(createIcon(option.icon, \"perf-monitor__icon\"));\n radio.addEventListener(\"click\", () => this.settings.setTheme(option.mode));\n this.themeRadios.set(option.mode, radio);\n segment.append(radio);\n }\n\n themeRow.append(createIcon(\"contrast\", \"perf-monitor__icon\"), themeLabel, segment);\n\n const dimRow = document.createElement(\"label\");\n dimRow.className = \"perf-monitor__row\";\n\n this.dimCheckbox = this.buildCheckbox(\n this.settings.dim,\n \"Dim the performance panel when the pointer leaves\",\n (on) => this.settings.setDim(on),\n );\n\n const dimLabel = document.createElement(\"span\");\n dimLabel.className = \"perf-monitor__label\";\n dimLabel.textContent = \"dim on leave\";\n\n dimRow.append(createIcon(\"blend\", \"perf-monitor__icon\"), dimLabel, this.dimCheckbox);\n options.append(themeRow, dimRow, this.buildReportRow());\n\n const graphs = document.createElement(\"div\");\n graphs.className = \"perf-monitor__graphs\";\n\n for (const config of TIMINGS) {\n const graph = document.createElement(\"div\");\n graph.className = \"perf-monitor__graph\";\n\n const canvas = document.createElement(\"canvas\");\n canvas.className = \"perf-monitor__canvas\";\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"perf-monitor__graph-overlay\";\n\n const head = document.createElement(\"label\");\n head.className = \"perf-monitor__graph-head\";\n\n const checkbox = this.buildCheckbox(\n this.settings.isGraphEnabled(config.metric),\n `Show ${config.label} graph in HUD`,\n (on) => this.settings.setGraphEnabled(config.metric, on),\n );\n this.graphCheckboxes.set(config.metric, checkbox);\n\n const label = document.createElement(\"span\");\n label.className = \"perf-monitor__graph-label\";\n label.textContent = config.label;\n\n const value = document.createElement(\"span\");\n value.className = \"perf-monitor__graph-value\";\n value.textContent = \"—\";\n this.graphValueEls.set(config.metric, value);\n\n head.append(checkbox, label);\n overlay.append(head, value);\n graph.append(canvas, overlay);\n graphs.append(graph);\n\n const ctx = canvas.getContext(\"2d\");\n\n if (ctx) {\n this.graphCanvases.set(config.metric, { canvas, ctx, height: 0, width: 0 });\n this.resizeObserver.observe(graph);\n }\n }\n\n const stats = document.createElement(\"div\");\n stats.className = \"perf-monitor__section\";\n\n for (const config of NUMBERS) {\n const rowEl = document.createElement(\"label\");\n rowEl.className = \"perf-monitor__row\";\n\n const checkbox = this.buildCheckbox(\n this.settings.isNumberEnabled(config.key),\n `Show ${config.label} in HUD`,\n (on) => this.settings.setNumberEnabled(config.key, on),\n );\n this.statCheckboxes.set(config.key, checkbox);\n\n const label = document.createElement(\"span\");\n label.className = \"perf-monitor__label\";\n label.textContent = config.label;\n\n const value = document.createElement(\"span\");\n value.className = \"perf-monitor__value\";\n value.textContent = \"—\";\n this.statValueEls.set(config.key, value);\n\n rowEl.append(checkbox, label, value);\n stats.append(rowEl);\n }\n\n // Checkbox first like the stat rows; the checkbox hides in compact, the rows don't.\n const footer = document.createElement(\"div\");\n footer.className = \"perf-monitor__footer\";\n\n this.infoCheckbox = this.buildCheckbox(\n this.settings.info,\n \"Show version and environment in HUD\",\n (on) => this.settings.setInfo(on),\n );\n\n const footerBody = document.createElement(\"div\");\n footerBody.className = \"perf-monitor__footer-body\";\n\n // Software on the first row, hardware on the second.\n const softwareRow = document.createElement(\"div\");\n softwareRow.className = \"perf-monitor__footer-row\";\n\n const release = document.createElement(\"a\");\n release.className = \"perf-monitor__link\";\n release.href = RELEASE_URL;\n release.target = \"_blank\";\n release.rel = \"noopener noreferrer\";\n release.title = `Release notes for v${version}`;\n release.textContent = `three-meter v${version}`;\n\n this.footerThreeEl = document.createElement(\"span\");\n softwareRow.append(release, this.footerThreeEl);\n\n this.footerHardwareEl = document.createElement(\"div\");\n this.footerHardwareEl.className = \"perf-monitor__footer-row\";\n this.footerHardwareEl.hidden = true;\n\n this.footerBackendEl = document.createElement(\"span\");\n this.footerBackendEl.className = \"perf-monitor__badge\";\n\n this.footerGpuEl = document.createElement(\"span\");\n this.footerGpuEl.className = \"perf-monitor__footer-gpu\";\n\n this.footerHardwareEl.append(this.footerBackendEl, this.footerGpuEl);\n footerBody.append(softwareRow, this.footerHardwareEl);\n footer.append(this.infoCheckbox, footerBody);\n\n this.element.append(hud, options, graphs, stats, footer);\n }\n\n private rebuildHud() {\n const { selection } = this.settings;\n\n for (const graph of this.hudGraphs.values()) {\n this.resizeObserver.unobserve(graph.canvas.parentElement ?? graph.canvas);\n }\n\n this.hudGraphs.clear();\n this.hudNumberEls.clear();\n this.hudGraphsEl.replaceChildren();\n this.hudGridEl.replaceChildren();\n\n for (const config of TIMINGS) {\n if (!selection.graphs.has(config.metric)) {\n continue;\n }\n\n const row = document.createElement(\"div\");\n row.className = \"perf-monitor__hud-graph\";\n\n const canvas = document.createElement(\"canvas\");\n canvas.className = \"perf-monitor__canvas\";\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"perf-monitor__graph-overlay\";\n\n const label = document.createElement(\"span\");\n label.className = \"perf-monitor__graph-label\";\n label.textContent = config.label;\n\n const value = document.createElement(\"span\");\n value.className = \"perf-monitor__graph-value\";\n value.textContent = \"—\";\n\n overlay.append(label, value);\n row.append(canvas, overlay);\n this.hudGraphsEl.append(row);\n\n const ctx = canvas.getContext(\"2d\");\n\n if (ctx) {\n this.hudGraphs.set(config.metric, { canvas, ctx, height: 0, valueEl: value, width: 0 });\n this.resizeObserver.observe(row);\n }\n }\n\n for (const config of NUMBERS) {\n if (!selection.numbers.has(config.key)) {\n continue;\n }\n\n const item = document.createElement(\"div\");\n item.className = \"perf-monitor__hud-item\";\n\n const label = document.createElement(\"span\");\n label.className = \"perf-monitor__hud-label\";\n label.textContent = config.label;\n\n const value = document.createElement(\"span\");\n value.className = \"perf-monitor__hud-value\";\n value.textContent = \"—\";\n\n item.append(label, value);\n this.hudGridEl.append(item);\n this.hudNumberEls.set(config.key, value);\n }\n\n this.resizeCanvases();\n }\n\n private onSelectionChanged() {\n for (const [metric, checkbox] of this.graphCheckboxes) {\n checkbox.checked = this.settings.isGraphEnabled(metric);\n }\n\n for (const [key, checkbox] of this.statCheckboxes) {\n checkbox.checked = this.settings.isNumberEnabled(key);\n }\n\n this.dimCheckbox.checked = this.settings.dim;\n this.infoCheckbox.checked = this.settings.info;\n this.applyInfoClass();\n this.theme.setOverride(this.settings.theme);\n this.rebuildHud();\n }\n\n /** Repaint the palette and mark the radio matching what applies (pick, else consumer mode). */\n private onThemeChanged() {\n applyTheme(this.element, this.theme);\n\n for (const [mode, radio] of this.themeRadios) {\n radio.setAttribute(\"aria-checked\", String(mode === this.theme.effective));\n }\n }\n\n private buildReportRow() {\n const row = document.createElement(\"div\");\n row.className = \"perf-monitor__row perf-monitor__row--static\";\n\n const label = document.createElement(\"span\");\n label.className = \"perf-monitor__label\";\n label.textContent = \"report\";\n\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.className = \"perf-monitor__button\";\n button.title = \"Copy versions, GPU and current metrics as Markdown for a bug report\";\n button.textContent = \"copy\";\n\n let resetTimer = 0;\n\n const flash = (text: string) => {\n button.textContent = text;\n window.clearTimeout(resetTimer);\n resetTimer = window.setTimeout(() => {\n button.textContent = \"copy\";\n }, COPY_FEEDBACK_MS);\n };\n\n button.addEventListener(\"click\", () => {\n void copyText(formatReport(this.monitor)).then((copied) =>\n flash(copied ? \"copied\" : \"failed\"),\n );\n });\n\n row.append(createIcon(\"clipboard\", \"perf-monitor__icon\"), label, button);\n\n return row;\n }\n\n private buildCheckbox(checked: boolean, title: string, onChange: (enabled: boolean) => void) {\n const checkbox = document.createElement(\"input\");\n checkbox.type = \"checkbox\";\n checkbox.className = \"perf-monitor__checkbox\";\n checkbox.checked = checked;\n checkbox.title = title;\n checkbox.setAttribute(\"aria-label\", title);\n checkbox.addEventListener(\"change\", () => onChange(checkbox.checked));\n\n return checkbox;\n }\n\n private applyInfoClass() {\n this.element.classList.toggle(\"perf-monitor--info\", this.settings.info);\n }\n\n private applyModeClass() {\n this.element.classList.toggle(\"perf-monitor--compact\", this.mode === \"compact\");\n this.element.classList.toggle(\"perf-monitor--full\", this.mode === \"full\");\n }\n\n private resizeCanvases() {\n const dpr = window.devicePixelRatio || 1;\n\n const sizeAll = (graphs: Iterable<GraphCanvas>) => {\n for (const graph of graphs) {\n const { canvas, ctx } = graph;\n const width = canvas.clientWidth;\n const height = canvas.clientHeight;\n\n if (width === 0 || height === 0) {\n continue;\n }\n\n canvas.width = Math.round(width * dpr);\n canvas.height = Math.round(height * dpr);\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n graph.width = width;\n graph.height = height;\n }\n };\n\n sizeAll(this.graphCanvases.values());\n sizeAll(this.hudGraphs.values());\n }\n}\n\nexport { PerformanceView };\nexport type { PerformanceViewMode, PerformanceViewOptions };\n","/**\n * The HUD stylesheet as a string, so the package works without a bundler and\n * injects itself once per document. `.perf-monitor` is the metrics card,\n * `.perf-hud` the floating host around it. Both namespaced so a host page can\n * override safely; the `--perf-*` custom properties are the theming surface.\n *\n * Both elements carry `data-theme=\"dark\" | \"light\"` (see `HudTheme`) and each\n * resolves its own tokens from it, so the card also themes when mounted on\n * its own, outside a `.perf-hud`.\n */\nconst STYLE_ATTRIBUTE = \"data-three-meter\";\n\nconst PERF_HUD_STYLES = `\n.perf-hud,\n.perf-monitor {\n --perf-bg: rgba(22, 24, 29, 0.85);\n --perf-fg: #e6e8eb;\n --perf-fg-dim: #c5c8ce;\n --perf-muted: #8b909a;\n --perf-row: rgba(255, 255, 255, 0.04);\n --perf-border: rgba(255, 255, 255, 0.12);\n --perf-accent: #60a5fa;\n --perf-shadow: 0 8px 24px rgba(0, 0, 0, 0.32);\n\n color-scheme: dark;\n}\n\n.perf-hud[data-theme=\"light\"],\n.perf-monitor[data-theme=\"light\"] {\n --perf-bg: rgba(250, 250, 252, 0.88);\n --perf-fg: #1a1c21;\n --perf-fg-dim: #4b5058;\n --perf-muted: #6b7079;\n --perf-row: rgba(0, 0, 0, 0.05);\n --perf-border: rgba(0, 0, 0, 0.12);\n --perf-accent: #2563eb;\n --perf-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);\n\n color-scheme: light;\n}\n\n.perf-monitor {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 8px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.4;\n color: var(--perf-fg);\n}\n\n.perf-monitor__graphs {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.perf-monitor__graph {\n position: relative;\n height: 40px;\n background: var(--perf-row);\n border-radius: 4px;\n overflow: hidden;\n}\n\n.perf-monitor__canvas {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.perf-monitor__graph-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 8px;\n pointer-events: none;\n}\n\n.perf-monitor__graph-head {\n display: flex;\n align-items: center;\n gap: 6px;\n cursor: pointer;\n}\n\n.perf-monitor__graph-overlay .perf-monitor__graph-head {\n pointer-events: auto;\n}\n\n.perf-monitor__graph-label {\n color: var(--perf-muted);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n font-size: 10px;\n}\n\n.perf-monitor__graph-value {\n min-width: 6ch;\n font-weight: 600;\n font-variant-numeric: tabular-nums lining-nums;\n text-align: right;\n}\n\n.perf-monitor__section {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.perf-monitor__row {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 2px 6px;\n border-radius: 3px;\n cursor: pointer;\n}\n\n.perf-monitor__row:nth-child(even) {\n background: var(--perf-row);\n}\n\n.perf-monitor__label {\n flex: 1;\n color: var(--perf-muted);\n}\n\n.perf-monitor__icon {\n flex: none;\n width: 12px;\n height: 12px;\n color: var(--perf-muted);\n}\n\n.perf-monitor__options {\n padding-bottom: 8px;\n border-bottom: 1px solid var(--perf-border);\n}\n\n.perf-monitor__row--static {\n cursor: default;\n}\n\n/* Light / system / dark, as a small segmented radio group. */\n.perf-monitor__segment {\n display: inline-flex;\n flex: none;\n gap: 1px;\n padding: 1px;\n border-radius: 4px;\n background: var(--perf-row);\n}\n\n.perf-monitor__segment-option {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 16px;\n padding: 0;\n border: 0;\n border-radius: 3px;\n background: transparent;\n color: var(--perf-muted);\n cursor: pointer;\n}\n\n.perf-monitor__segment-option:hover {\n color: var(--perf-fg);\n}\n\n.perf-monitor__segment-option[aria-checked=\"true\"] {\n color: var(--perf-fg);\n background: var(--perf-border);\n}\n\n.perf-monitor__segment-option:focus-visible {\n outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);\n outline-offset: 1px;\n}\n\n.perf-monitor__segment-option .perf-monitor__icon {\n width: 11px;\n height: 11px;\n color: inherit;\n}\n\n.perf-monitor__button {\n flex: none;\n min-width: 7ch;\n padding: 0 6px;\n border: 1px solid var(--perf-border);\n border-radius: 3px;\n background: var(--perf-row);\n color: var(--perf-fg-dim);\n font: inherit;\n cursor: pointer;\n}\n\n.perf-monitor__button:hover {\n color: var(--perf-fg);\n}\n\n.perf-monitor__button:focus-visible {\n outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);\n outline-offset: 1px;\n}\n\n.perf-monitor__checkbox {\n flex: none;\n margin: 0;\n width: 12px;\n height: 12px;\n accent-color: var(--perf-accent);\n cursor: pointer;\n}\n\n.perf-monitor__value {\n min-width: 7ch;\n font-variant-numeric: tabular-nums lining-nums;\n text-align: right;\n}\n\n.perf-monitor__hud {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 2px;\n}\n\n.perf-monitor__hud-graphs {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.perf-monitor__hud-graphs:empty {\n display: none;\n}\n\n.perf-monitor__hud-graph {\n position: relative;\n height: 28px;\n background: var(--perf-row);\n border-radius: 4px;\n overflow: hidden;\n}\n\n.perf-monitor__hud-grid {\n display: grid;\n grid-template-columns: auto 6ch auto 6ch;\n align-items: baseline;\n gap: 2px 8px;\n}\n\n.perf-monitor__hud-grid:empty {\n display: none;\n}\n\n.perf-monitor__hud-item {\n display: contents;\n}\n\n.perf-monitor__hud-label {\n color: var(--perf-muted);\n font-size: 10px;\n}\n\n.perf-monitor__hud-value {\n font-weight: 600;\n font-variant-numeric: tabular-nums lining-nums;\n text-align: right;\n}\n\n.perf-monitor__footer {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n padding: 8px 6px 0;\n border-top: 1px solid var(--perf-border);\n color: var(--perf-muted);\n font-size: 10px;\n}\n\n.perf-monitor__footer-body {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n}\n\n.perf-monitor__footer-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-width: 0;\n}\n\n.perf-monitor__footer-row[hidden],\n.perf-monitor__badge[hidden] {\n display: none;\n}\n\n.perf-monitor__footer-gpu {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--perf-fg-dim);\n}\n\n.perf-monitor__badge {\n flex: none;\n padding: 0 5px;\n border: 1px solid var(--perf-border);\n border-radius: 3px;\n color: var(--perf-fg-dim);\n letter-spacing: 0.04em;\n}\n\n.perf-monitor__badge.is-fallback {\n border-color: #f59e0b;\n color: #f59e0b;\n}\n\n.perf-monitor[data-theme=\"light\"] .perf-monitor__badge.is-fallback {\n border-color: #d97706;\n color: #b45309;\n}\n\n.perf-monitor__link {\n color: inherit;\n text-decoration: none;\n}\n\n.perf-monitor__link:hover,\n.perf-monitor__link:focus-visible {\n color: var(--perf-accent);\n text-decoration: underline;\n}\n\n.perf-monitor--full .perf-monitor__hud {\n display: none;\n}\n\n.perf-monitor--compact .perf-monitor__graphs,\n.perf-monitor--compact .perf-monitor__section,\n.perf-monitor--compact:not(.perf-monitor--info) .perf-monitor__footer,\n.perf-monitor--compact .perf-monitor__footer > .perf-monitor__checkbox {\n display: none;\n}\n\n.perf-monitor--compact .perf-monitor__footer {\n padding: 6px 2px 2px;\n}\n\n.perf-monitor--compact {\n padding: 4px 8px;\n}\n\n.perf-hud {\n position: fixed;\n z-index: 9999;\n /* Safari resolves width:100% canvases against the viewport on a fixed\n host with auto width. Shrink-wrap so expand stays a card, not the window. */\n width: max-content;\n max-width: min(20rem, calc(100vw - 48px));\n overflow: visible;\n background: transparent;\n pointer-events: none;\n user-select: none;\n touch-action: none;\n}\n\n.perf-hud--full > .perf-monitor {\n width: 16rem;\n max-width: 100%;\n}\n\n.perf-hud > .perf-monitor {\n position: relative;\n z-index: 1;\n max-height: 70vh;\n overflow: auto;\n background: var(--perf-bg);\n border-radius: 6px;\n box-shadow: var(--perf-shadow);\n backdrop-filter: blur(2px);\n pointer-events: auto;\n transition: opacity 0.25s ease;\n}\n\n.perf-hud--dim:not(.is-awake):not(.is-dragging) > .perf-monitor {\n opacity: 0.32;\n}\n\n.perf-hud__hotspot {\n position: absolute;\n z-index: 0;\n inset: -20px;\n pointer-events: auto;\n}\n\n.perf-hud:not([data-edge=\"right\"]) .perf-hud__hotspot {\n right: -46px;\n}\n\n.perf-hud[data-edge=\"right\"] .perf-hud__hotspot {\n left: -46px;\n}\n\n.perf-hud__tools {\n position: absolute;\n top: 0;\n z-index: 2;\n display: flex;\n flex-direction: column;\n gap: 4px;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.15s ease;\n}\n\n.perf-hud.is-awake .perf-hud__tools,\n.perf-hud.is-dragging .perf-hud__tools {\n opacity: 1;\n pointer-events: auto;\n}\n\n/* Inward side: right by default, left when docked to the right edge. */\n.perf-hud:not([data-edge=\"right\"]) .perf-hud__tools {\n left: calc(100% + 6px);\n}\n\n.perf-hud[data-edge=\"right\"] .perf-hud__tools {\n right: calc(100% + 6px);\n}\n\n.perf-hud__disc {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n padding: 0;\n border: 0;\n border-radius: 999px;\n background: var(--perf-bg);\n box-shadow: var(--perf-shadow);\n color: var(--perf-fg-dim);\n cursor: pointer;\n backdrop-filter: blur(2px);\n}\n\n.perf-hud__icon {\n width: 12px;\n height: 12px;\n}\n\n.perf-hud__drag {\n cursor: grab;\n}\n\n.perf-hud.is-dragging .perf-hud__drag {\n cursor: grabbing;\n}\n\n.perf-hud__disc:hover,\n.perf-hud__disc:focus-visible,\n.perf-hud__disc[aria-pressed=\"true\"] {\n color: var(--perf-fg);\n outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);\n outline-offset: 1px;\n}\n`;\n\n/**\n * Append the stylesheet to `target`'s head, once. Call before mounting a view\n * by hand; {@link mountPerfHud} does it for you unless told not to.\n */\nconst injectStyles = (target: Document = document) => {\n if (target.head.querySelector(`style[${STYLE_ATTRIBUTE}]`)) {\n return;\n }\n\n const style = target.createElement(\"style\");\n style.setAttribute(STYLE_ATTRIBUTE, \"\");\n style.textContent = PERF_HUD_STYLES;\n target.head.append(style);\n};\n\nexport { injectStyles, PERF_HUD_STYLES };\n","/**\n * The floating HUD: a fixed host docked to a screen edge, the metrics card\n * inside it, and two discs on the inward side (drag grip, compact/full toggle)\n * that stay hidden until the pointer is near or someone taps. Dim-on-leave\n * comes from the settings; the palette from `theme`. This is what the React\n * `PerfHud` wraps; a vanilla three app calls it directly.\n */\nimport type { PerformanceMonitor } from \"../core/performance-monitor.ts\";\nimport { type DefaultPlacement, dockPanel } from \"./dock-panel.ts\";\nimport { DEFAULT_STORAGE_KEY, HudSettings } from \"./hud-settings.ts\";\nimport { createIcon } from \"./icons.ts\";\nimport { PerformanceView, type PerformanceViewMode } from \"./performance-view.ts\";\nimport { injectStyles } from \"./styles.ts\";\nimport { applyTheme, HudTheme, type ThemeMode } from \"./theme.ts\";\n\nconst TOUCH_LINGER_MS = 2500;\n\ntype MountPerfHudOptions = {\n /** Start compact (the card) or full (the checkbox list). Default `compact`. */\n mode?: PerformanceViewMode;\n /**\n * `dark`, `light`, or `system` to follow the OS preference. Default\n * `system`. Change it later with the handle's `setTheme`. A pick made in\n * the panel's own theme row (kept in `settings`) sits on top of this.\n */\n theme?: ThemeMode;\n /** localStorage key for the selection and the dock. `null` disables persistence. */\n storageKey?: string | null;\n /** Bring your own; otherwise one is created from `storageKey`. */\n settings?: HudSettings;\n /** Where the card sits on a first visit. Default left edge, centred. */\n defaultPlacement?: DefaultPlacement;\n /** Where the host element goes. Default `document.body`. */\n parent?: HTMLElement;\n /** Append the stylesheet to the document once. Default true. */\n injectStyles?: boolean;\n /** Repaint rate. Default 10. */\n refreshHz?: number;\n /** Accessible name of the panel. Default `Performance`. */\n label?: string;\n};\n\ntype PerfHudHandle = {\n element: HTMLDivElement;\n view: PerformanceView;\n settings: HudSettings;\n /**\n * `mode` is the consumer layer, `override` the panel's pick, `effective`\n * whichever applies, `resolved` the `dark` / `light` on screen. Subscribe\n * for changes, including the OS preference moving under `system`.\n */\n theme: HudTheme;\n getMode: () => PerformanceViewMode;\n setMode: (mode: PerformanceViewMode) => void;\n /** The consumer layer. The panel's pick, if any, is `settings.theme`. */\n getTheme: () => ThemeMode;\n /** Sets the consumer layer; a pick made in the panel still wins until `settings.setTheme(null)`. */\n setTheme: (mode: ThemeMode) => void;\n dispose: () => void;\n};\n\nconst disc = (label: string, className: string, icon: \"grip\" | \"sliders\") => {\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.className = `perf-hud__disc ${className}`.trim();\n button.setAttribute(\"aria-label\", label);\n button.append(createIcon(icon, \"perf-hud__icon\"));\n\n return button;\n};\n\nconst mountPerfHud = (\n monitor: PerformanceMonitor,\n options: MountPerfHudOptions = {},\n): PerfHudHandle => {\n const storageKey = options.storageKey === undefined ? DEFAULT_STORAGE_KEY : options.storageKey;\n const settings = options.settings ?? new HudSettings({ storageKey });\n const theme = new HudTheme(options.theme ?? \"system\");\n const parent = options.parent ?? document.body;\n\n if (options.injectStyles ?? true) {\n injectStyles(parent.ownerDocument);\n }\n\n const host = document.createElement(\"div\");\n host.className = \"perf-hud\";\n host.dataset.edge = \"left\";\n host.setAttribute(\"aria-label\", options.label ?? \"Performance\");\n\n const hotspot = document.createElement(\"div\");\n hotspot.className = \"perf-hud__hotspot\";\n hotspot.setAttribute(\"aria-hidden\", \"true\");\n\n const tools = document.createElement(\"div\");\n tools.className = \"perf-hud__tools\";\n const grip = disc(\"Drag performance panel\", \"perf-hud__drag\", \"grip\");\n const toggle = disc(\"Toggle full performance metrics\", \"\", \"sliders\");\n tools.append(grip, toggle);\n\n const view = new PerformanceView({\n mode: options.mode ?? \"compact\",\n monitor,\n refreshHz: options.refreshHz,\n settings,\n theme,\n });\n\n host.append(hotspot, tools, view.element);\n parent.append(host);\n\n const applyMode = () => {\n host.classList.toggle(\"perf-hud--full\", view.getMode() === \"full\");\n };\n\n const applyDim = () => {\n host.classList.toggle(\"perf-hud--dim\", settings.dim);\n };\n\n applyMode();\n applyDim();\n // After the view: constructing it restores the panel's stored theme pick.\n applyTheme(host, theme);\n const unsubscribe = settings.subscribe(applyDim);\n const unsubscribeTheme = theme.subscribe(() => applyTheme(host, theme));\n const dock = dockPanel(host, {\n defaultPlacement: options.defaultPlacement,\n handle: grip,\n storageKey,\n });\n\n const setMode = (mode: PerformanceViewMode) => {\n view.setMode(mode);\n applyMode();\n requestAnimationFrame(() => dock.refresh());\n };\n\n toggle.addEventListener(\"click\", () => {\n setMode(view.getMode() === \"compact\" ? \"full\" : \"compact\");\n });\n\n // Wake on approach, sleep on leave; a touch lingers so the discs can be tapped.\n let hideTimer = 0;\n\n const sleep = () => {\n if (host.classList.contains(\"is-dragging\")) {\n return;\n }\n\n host.classList.remove(\"is-awake\");\n };\n\n const wake = (linger: boolean) => {\n host.classList.add(\"is-awake\");\n window.clearTimeout(hideTimer);\n\n if (linger) {\n hideTimer = window.setTimeout(sleep, TOUCH_LINGER_MS);\n }\n };\n\n const onEnter = () => {\n wake(false);\n };\n\n const onLeave = (event: PointerEvent) => {\n if (host.contains(event.relatedTarget as Node | null)) {\n return;\n }\n\n if (event.pointerType === \"touch\" || event.pointerType === \"pen\") {\n wake(true);\n\n return;\n }\n\n sleep();\n };\n\n const onDown = (event: PointerEvent) => {\n wake(event.pointerType !== \"mouse\");\n };\n\n host.addEventListener(\"pointerenter\", onEnter);\n host.addEventListener(\"pointerleave\", onLeave);\n host.addEventListener(\"pointerdown\", onDown);\n\n view.start();\n\n return {\n dispose: () => {\n window.clearTimeout(hideTimer);\n host.removeEventListener(\"pointerenter\", onEnter);\n host.removeEventListener(\"pointerleave\", onLeave);\n host.removeEventListener(\"pointerdown\", onDown);\n unsubscribe();\n unsubscribeTheme();\n dock.dispose();\n view.dispose();\n theme.dispose();\n host.remove();\n },\n element: host,\n getMode: () => view.getMode(),\n setMode,\n getTheme: () => theme.mode,\n setTheme: (mode: ThemeMode) => theme.setMode(mode),\n settings,\n theme,\n view,\n };\n};\n\nexport { mountPerfHud };\nexport type { MountPerfHudOptions, PerfHudHandle };\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as TimingMetric, t as PerformanceMonitor } from "./performance-monitor-CBN05_BC.js";
|
|
2
2
|
//#region src/ui/dock-panel.d.ts
|
|
3
3
|
declare const EDGES: readonly ["left", "right", "top", "bottom"];
|
|
4
4
|
type ScreenEdge = (typeof EDGES)[number];
|
|
@@ -192,6 +192,7 @@ declare class PerformanceView {
|
|
|
192
192
|
private onSelectionChanged;
|
|
193
193
|
/** Repaint the palette and mark the radio matching what applies (pick, else consumer mode). */
|
|
194
194
|
private onThemeChanged;
|
|
195
|
+
private buildReportRow;
|
|
195
196
|
private buildCheckbox;
|
|
196
197
|
private applyInfoClass;
|
|
197
198
|
private applyModeClass;
|
|
@@ -244,4 +245,4 @@ type PerfHudHandle = {
|
|
|
244
245
|
declare const mountPerfHud: (monitor: PerformanceMonitor, options?: MountPerfHudOptions) => PerfHudHandle;
|
|
245
246
|
//#endregion
|
|
246
247
|
export { dockPanel as C, ScreenEdge as S, DefaultPlacement as _, PerformanceViewMode as a, DockPanelOptions as b, HudDefaults as c, HudSettingsOptions as d, HudTheme as f, isThemeMode as g, ThemeMode as h, PerformanceView as i, HudSelection as l, THEME_MODES as m, PerfHudHandle as n, PerformanceViewOptions as o, ResolvedTheme as p, mountPerfHud as r, DEFAULT_STORAGE_KEY as s, MountPerfHudOptions as t, HudSettings as u, DockAlign as v, Placement as x, DockHandle as y };
|
|
247
|
-
//# sourceMappingURL=mount-perf-hud-
|
|
248
|
+
//# sourceMappingURL=mount-perf-hud-CmINOH3T.d.ts.map
|