@zkmake/three-meter 0.9.2 → 0.10.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.
@@ -1,17 +1,24 @@
1
1
  //#region src/ui/budgets.ts
2
2
  const DEFAULT_TARGET_FPS = 60;
3
- const FLOORS = /* @__PURE__ */ new Set(["fps", "low"]);
4
- const resolveBudgets = (budgets) => {
5
- const targetFps = budgets && budgets.targetFps || DEFAULT_TARGET_FPS;
3
+ const FLOORS = /* @__PURE__ */ new Set([
4
+ "fps",
5
+ "headroom",
6
+ "low"
7
+ ]);
8
+ /** `detectedHz` is the monitor's refresh reading; an explicit `targetFps` wins over it. */
9
+ const resolveBudgets = (budgets, detectedHz = null) => {
10
+ const targetFps = budgets && budgets.targetFps || detectedHz || DEFAULT_TARGET_FPS;
6
11
  const frameMs = 1e3 / targetFps;
7
12
  if (budgets === false) return {
8
13
  frameMs,
9
- limits: {}
14
+ limits: {},
15
+ targetFps
10
16
  };
11
17
  const merged = {
12
18
  cpu: frameMs,
13
19
  fps: targetFps * .95,
14
20
  gpu: frameMs,
21
+ headroom: 0,
15
22
  low: targetFps / 2,
16
23
  p99: frameMs * 1.5,
17
24
  ...budgets
@@ -20,7 +27,8 @@ const resolveBudgets = (budgets) => {
20
27
  for (const [key, limit] of Object.entries(merged)) if (key !== "targetFps" && typeof limit === "number" && Number.isFinite(limit)) limits[key] = limit;
21
28
  return {
22
29
  frameMs,
23
- limits
30
+ limits,
31
+ targetFps
24
32
  };
25
33
  };
26
34
  const isFloor = (key) => FLOORS.has(key);
@@ -486,6 +494,9 @@ const dockPanel = (element, options = {}) => {
486
494
  //#endregion
487
495
  //#region src/ui/icons.ts
488
496
  const ICON_MARKUP = {
497
+ eye: "<path d=\"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>",
498
+ eyeOff: "<path d=\"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49\"/><path d=\"M14.084 14.158a3 3 0 0 1-4.242-4.242\"/><path d=\"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143\"/><path d=\"m2 2 20 20\"/>",
499
+ hourglass: "<path d=\"M5 22h14\"/><path d=\"M5 2h14\"/><path d=\"M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22\"/><path d=\"M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2\"/>",
489
500
  flame: "<path d=\"M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z\"/>",
490
501
  activity: "<path d=\"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2\"/>",
491
502
  box: "<path d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\"/><path d=\"m3.3 7 8.7 5 8.7-5\"/><path d=\"M12 22V12\"/>",
@@ -528,7 +539,7 @@ const createIcon = (name, className) => {
528
539
  //#endregion
529
540
  //#region package.json
530
541
  var name = "@zkmake/three-meter";
531
- var version = "0.9.2";
542
+ var version = "0.10.0";
532
543
  //#endregion
533
544
  //#region src/ui/clipboard.ts
534
545
  /**
@@ -580,6 +591,45 @@ const compact = new Intl.NumberFormat("en-US", {
580
591
  });
581
592
  const formatCount = (value) => Math.abs(value) < COMPACT_FROM ? exact.format(value) : compact.format(value);
582
593
  //#endregion
594
+ //#region src/ui/hider.ts
595
+ var Hider = class {
596
+ groups = /* @__PURE__ */ new Map();
597
+ /** Each hidden object's `visible` from before the first group hid it. */
598
+ original = /* @__PURE__ */ new Map();
599
+ hide(key, objects) {
600
+ if (this.groups.has(key)) return;
601
+ const hideable = objects.filter((object) => typeof object === "object" && object !== null);
602
+ this.groups.set(key, hideable);
603
+ for (const object of hideable) {
604
+ if (!this.original.has(object)) this.original.set(object, object.visible !== false);
605
+ object.visible = false;
606
+ }
607
+ }
608
+ show(key) {
609
+ const objects = this.groups.get(key);
610
+ if (!objects) return;
611
+ this.groups.delete(key);
612
+ for (const object of objects) if (!this.heldElsewhere(object)) {
613
+ object.visible = this.original.get(object) ?? true;
614
+ this.original.delete(object);
615
+ }
616
+ }
617
+ isHidden(key) {
618
+ return this.groups.has(key);
619
+ }
620
+ /** Hidden group keys, in the order they were hidden. */
621
+ keys() {
622
+ return [...this.groups.keys()];
623
+ }
624
+ restoreAll() {
625
+ for (const key of this.keys()) this.show(key);
626
+ }
627
+ heldElsewhere(object) {
628
+ for (const objects of this.groups.values()) if (objects.includes(object)) return true;
629
+ return false;
630
+ }
631
+ };
632
+ //#endregion
583
633
  //#region src/ui/metric-help.ts
584
634
  /**
585
635
  * One plain-language line per metric: what it is, and what a bad reading
@@ -591,6 +641,8 @@ const METRIC_HELP = {
591
641
  cpu: "JavaScript time per frame: your update code plus three.js preparing the render. It has to fit the frame budget (16.7 ms at 60 FPS).",
592
642
  fps: "Frames drawn per second. Higher is smoother; most displays top out at 60 or 120.",
593
643
  geometries: "Geometries held in GPU memory. Should level off; a steady climb is a leak, so dispose() what you remove.",
644
+ headroom: "Spare time left in each frame: the frame budget minus whichever of CPU and GPU took longer. Below zero, frames miss the display's refresh.",
645
+ refresh: "The rate the loop runs at when it keeps up: the display's refresh rate, or the app's own cap. Budgets use it unless you set targetFps.",
594
646
  gpu: "Time the graphics card spent drawing the frame. High means shaders, overdraw or resolution are the bottleneck.",
595
647
  hitches: "Frames that took over twice as long as usual, in the last 1,000 frames. Each one is a visible stutter.",
596
648
  lines: "Line segments drawn this frame.",
@@ -641,10 +693,15 @@ const renderReport = ({ cost, environment, page, sample, stats }) => {
641
693
  `p99 ${ms(stats.p99Ms)}`,
642
694
  `hitches ${formatCount(stats.hitches)} / ${formatCount(stats.frames)} frames`
643
695
  ].join(" · ");
696
+ const timing = [`CPU ${ms(sample.cpu)}`, `GPU ${sample.gpu.available ? ms(sample.gpu.ms) : "unavailable"}`];
697
+ if (stats.refreshHz) {
698
+ const busiest = Math.max(sample.cpu, sample.gpu.available ? sample.gpu.ms : 0);
699
+ timing.push(`refresh ${stats.refreshHz} Hz`, `headroom ${ms(1e3 / stats.refreshHz - busiest)}`);
700
+ }
644
701
  const lines = [
645
702
  heading.join(" · "),
646
703
  frames,
647
- `CPU ${ms(sample.cpu)} · GPU ${sample.gpu.available ? ms(sample.gpu.ms) : "unavailable"}`,
704
+ timing.join(" · "),
648
705
  [
649
706
  `Calls ${formatCount(render.calls)}`,
650
707
  `passes ${formatCount(render.passes)}`,
@@ -799,7 +856,15 @@ const COST_GROUPS = [{
799
856
  group: "materials",
800
857
  label: "materials"
801
858
  }];
802
- const COSTS_HELP = "The scene's biggest draw costs in the main render pass, estimated from the scene graph. Hidden and off-screen objects are left out; shadow and post-processing passes aren't counted. Click a row to log its objects to the console.";
859
+ const COSTS_HELP = "The scene's biggest draw costs in the main render pass, estimated from the scene graph. Hidden and off-screen objects are left out; shadow and post-processing passes aren't counted. Click a row to log its objects to the console; use the eye to hide them and measure the time they cost.";
860
+ /** Frames averaged on each side of hiding a row. */
861
+ const MEASURE_FRAMES = 30;
862
+ /** How long after hiding before the saving is read, so the average holds only frames without the row. */
863
+ const MEASURE_SETTLE_MS = 700;
864
+ const meanOfLast = (values, count) => {
865
+ const tail = values.slice(-count);
866
+ return tail.length === 0 ? 0 : tail.reduce((sum, value) => sum + value, 0) / tail.length;
867
+ };
803
868
  /** `×2,000` for instances or copies; nothing for a single object. */
804
869
  const copiesOf = (entry) => entry.instances > 1 ? `×${formatCount(entry.instances)}` : "";
805
870
  const costTitle = (entry) => {
@@ -848,6 +913,22 @@ const NUMBERS = [
848
913
  unit: "ms",
849
914
  value: (sample) => sample.gpu.available ? sample.gpu.ms : null
850
915
  },
916
+ {
917
+ format: tenths,
918
+ icon: "hourglass",
919
+ key: "headroom",
920
+ label: "Headroom",
921
+ unit: "ms",
922
+ value: (sample, _stats, frameMs) => sample.fps ? frameMs - Math.max(sample.cpu, sample.gpu.available ? sample.gpu.ms : 0) : null
923
+ },
924
+ {
925
+ format: (value) => `${Math.round(value)} Hz`,
926
+ icon: "monitor",
927
+ key: "refresh",
928
+ label: "Refresh",
929
+ unit: "Hz",
930
+ value: (_sample, stats) => stats.refreshHz
931
+ },
851
932
  {
852
933
  format: whole,
853
934
  icon: "trendingDown",
@@ -971,6 +1052,10 @@ var PerformanceView = class {
971
1052
  hudGraphs = /* @__PURE__ */ new Map();
972
1053
  hudNumberEls = /* @__PURE__ */ new Map();
973
1054
  budgets;
1055
+ budgetsInput;
1056
+ detectedHz = null;
1057
+ hider = new Hider();
1058
+ hiddenRows = /* @__PURE__ */ new Map();
974
1059
  rafId = null;
975
1060
  lastPaintAt = 0;
976
1061
  resizeObserver;
@@ -991,6 +1076,7 @@ var PerformanceView = class {
991
1076
  this.theme.setOverride(this.settings.theme);
992
1077
  this.mode = options.mode ?? "full";
993
1078
  this.minIntervalMs = 1e3 / (options.refreshHz ?? 10);
1079
+ this.budgetsInput = options.budgets;
994
1080
  this.budgets = resolveBudgets(options.budgets);
995
1081
  this.element = document.createElement("div");
996
1082
  this.element.className = "perf-monitor";
@@ -1023,11 +1109,13 @@ var PerformanceView = class {
1023
1109
  }
1024
1110
  /** Replace the budgets; `false` turns them all off. Applies on the next paint. */
1025
1111
  setBudgets(budgets) {
1026
- this.budgets = resolveBudgets(budgets);
1112
+ this.budgetsInput = budgets;
1113
+ this.budgets = resolveBudgets(budgets, this.detectedHz);
1027
1114
  this.applyBudgetTitles();
1028
1115
  }
1029
1116
  dispose() {
1030
1117
  this.stop();
1118
+ this.showAllHidden();
1031
1119
  this.unsubscribe();
1032
1120
  this.unsubscribeTheme();
1033
1121
  if (this.ownsTheme) this.theme.dispose();
@@ -1042,13 +1130,14 @@ var PerformanceView = class {
1042
1130
  };
1043
1131
  render() {
1044
1132
  const sample = this.monitor.getSample();
1133
+ const stats = this.monitor.getFrameStats();
1134
+ this.followRefresh(stats.refreshHz);
1045
1135
  if (this.mode === "compact") {
1046
- this.renderHud(sample);
1136
+ this.renderHud(sample, stats);
1047
1137
  if (this.settings.info) this.renderFooter();
1048
1138
  return;
1049
1139
  }
1050
1140
  for (const config of TIMINGS) this.paintGraph(config, sample, this.graphValueEls.get(config.metric), this.graphCanvases.get(config.metric), "unavailable");
1051
- const stats = this.monitor.getFrameStats();
1052
1141
  for (const config of NUMBERS) this.paintNumber(config, this.statValueEls.get(config.key), sample, stats);
1053
1142
  if (this.costsOpen && performance.now() - this.lastCostsAt >= COSTS_INTERVAL_MS) this.renderCosts();
1054
1143
  this.renderFooter();
@@ -1056,44 +1145,102 @@ var PerformanceView = class {
1056
1145
  renderCosts() {
1057
1146
  this.lastCostsAt = performance.now();
1058
1147
  const cost = this.monitor.getSceneCost();
1148
+ const hidden = [...this.hiddenRows.values()].map((row) => this.hiddenCostRow(row));
1059
1149
  if (!cost) {
1060
- this.costsListEl.replaceChildren(this.costsNote("No scene rendered yet."));
1150
+ this.costsListEl.replaceChildren(...hidden, this.costsNote("No scene rendered yet."));
1061
1151
  return;
1062
1152
  }
1063
- const entries = [...this.costGroup === "meshes" ? cost.meshes : cost.materials];
1153
+ const entries = (this.costGroup === "meshes" ? cost.meshes : cost.materials).filter((entry) => !this.hiddenRows.has(entry.key));
1064
1154
  if (this.costSort === "triangles") entries.sort((a, b) => b.triangles - a.triangles || b.calls - a.calls);
1065
- if (entries.length === 0) {
1155
+ if (entries.length === 0 && hidden.length === 0) {
1066
1156
  this.costsListEl.replaceChildren(this.costsNote("Nothing drawn."));
1067
1157
  return;
1068
1158
  }
1069
1159
  const rows = entries.slice(0, COSTS_ROWS).map((entry) => {
1070
- const row = document.createElement("button");
1071
- row.type = "button";
1072
- row.className = "perf-monitor__cost";
1073
- row.title = costTitle(entry);
1074
- const name = document.createElement("span");
1075
- name.className = "perf-monitor__cost-name";
1076
- name.textContent = entry.label;
1077
- const copies = copiesOf(entry);
1078
- if (copies) {
1079
- const suffix = document.createElement("span");
1080
- suffix.className = "perf-monitor__cost-copies";
1081
- suffix.textContent = ` ${copies}`;
1082
- name.append(suffix);
1083
- }
1160
+ const row = this.costRowShell(entry);
1084
1161
  const calls = document.createElement("span");
1085
1162
  calls.className = "perf-monitor__cost-value";
1086
1163
  calls.textContent = formatCount(entry.calls);
1087
1164
  const triangles = document.createElement("span");
1088
1165
  triangles.className = "perf-monitor__cost-value";
1089
1166
  triangles.textContent = formatCount(entry.triangles);
1090
- row.append(name, calls, triangles);
1091
- row.addEventListener("click", () => {
1092
- console.log(`three-meter: ${entry.label}`, entry.objects);
1093
- });
1167
+ row.append(calls, triangles, this.eyeButton(entry, false));
1094
1168
  return row;
1095
1169
  });
1096
- this.costsListEl.replaceChildren(...rows);
1170
+ this.costsListEl.replaceChildren(...hidden, ...rows);
1171
+ }
1172
+ /** Name button (logs the objects) in a row; the caller appends the value cells and the eye. */
1173
+ costRowShell(entry) {
1174
+ const row = document.createElement("div");
1175
+ row.className = "perf-monitor__cost";
1176
+ const name = document.createElement("button");
1177
+ name.type = "button";
1178
+ name.className = "perf-monitor__cost-name";
1179
+ name.title = costTitle(entry);
1180
+ name.textContent = entry.label;
1181
+ const copies = copiesOf(entry);
1182
+ if (copies) {
1183
+ const suffix = document.createElement("span");
1184
+ suffix.className = "perf-monitor__cost-copies";
1185
+ suffix.textContent = ` ${copies}`;
1186
+ name.append(suffix);
1187
+ }
1188
+ name.addEventListener("click", () => {
1189
+ console.log(`three-meter: ${entry.label}`, entry.objects);
1190
+ });
1191
+ row.append(name);
1192
+ return row;
1193
+ }
1194
+ /** A hidden row: what hiding it saved, measured from the timing history. */
1195
+ hiddenCostRow(hidden) {
1196
+ const row = this.costRowShell(hidden.entry);
1197
+ row.classList.add("is-hidden");
1198
+ const saved = document.createElement("span");
1199
+ saved.className = "perf-monitor__cost-saved";
1200
+ const label = hidden.metric.toUpperCase();
1201
+ if (performance.now() - hidden.hiddenAt < MEASURE_SETTLE_MS) saved.textContent = "measuring…";
1202
+ else {
1203
+ const now = meanOfLast(this.monitor.getHistory(hidden.metric), MEASURE_FRAMES);
1204
+ const delta = hidden.baseline - now;
1205
+ saved.textContent = `${delta >= 0 ? "−" : "+"}${Math.abs(delta).toFixed(1)} ms ${label}`;
1206
+ saved.title = `${label} time went from ${hidden.baseline.toFixed(1)} ms to ${now.toFixed(1)} ms with this hidden, averaged over ${MEASURE_FRAMES} frames each side.`;
1207
+ }
1208
+ row.append(saved, this.eyeButton(hidden.entry, true));
1209
+ return row;
1210
+ }
1211
+ eyeButton(entry, hidden) {
1212
+ const eye = document.createElement("button");
1213
+ eye.type = "button";
1214
+ eye.className = "perf-monitor__cost-eye";
1215
+ eye.setAttribute("aria-pressed", String(hidden));
1216
+ const label = hidden ? `Show ${entry.label} again` : `Hide ${entry.label} to measure its cost`;
1217
+ eye.title = label;
1218
+ eye.setAttribute("aria-label", label);
1219
+ eye.append(createIcon(hidden ? "eyeOff" : "eye", "perf-monitor__icon"));
1220
+ eye.addEventListener("click", () => hidden ? this.showRow(entry.key) : this.hideRow(entry));
1221
+ return eye;
1222
+ }
1223
+ hideRow(entry) {
1224
+ const metric = this.monitor.getSample().gpu.available ? "gpu" : "cpu";
1225
+ this.hiddenRows.set(entry.key, {
1226
+ baseline: meanOfLast(this.monitor.getHistory(metric), MEASURE_FRAMES),
1227
+ entry,
1228
+ hiddenAt: performance.now(),
1229
+ metric
1230
+ });
1231
+ const all = this.monitor.getObjectsForKey(entry.key);
1232
+ this.hider.hide(entry.key, all.length > 0 ? all : entry.objects);
1233
+ this.renderCosts();
1234
+ }
1235
+ showRow(key) {
1236
+ this.hider.show(key);
1237
+ this.hiddenRows.delete(key);
1238
+ this.renderCosts();
1239
+ }
1240
+ /** Everything hidden goes back: on closing top costs and on dispose. */
1241
+ showAllHidden() {
1242
+ this.hider.restoreAll();
1243
+ this.hiddenRows.clear();
1097
1244
  }
1098
1245
  costsNote(text) {
1099
1246
  const note = document.createElement("div");
@@ -1119,11 +1266,17 @@ var PerformanceView = class {
1119
1266
  this.footerGpuEl.title = gpu;
1120
1267
  this.footerHardwareEl.hidden = backend === "" && gpu === "";
1121
1268
  }
1122
- renderHud(sample) {
1123
- const stats = this.hudNumberEls.size > 0 ? this.monitor.getFrameStats() : null;
1269
+ /** Budgets follow the detected refresh rate unless `targetFps` was given. */
1270
+ followRefresh(hz) {
1271
+ if (hz === this.detectedHz) return;
1272
+ this.detectedHz = hz;
1273
+ this.budgets = resolveBudgets(this.budgetsInput, hz);
1274
+ this.applyBudgetTitles();
1275
+ }
1276
+ renderHud(sample, stats) {
1124
1277
  for (const [key, element] of this.hudNumberEls) {
1125
1278
  const config = NUMBER_BY_KEY.get(key);
1126
- if (config && stats) this.paintNumber(config, element, sample, stats);
1279
+ if (config) this.paintNumber(config, element, sample, stats);
1127
1280
  }
1128
1281
  for (const [metric, graph] of this.hudGraphs) {
1129
1282
  const config = TIMINGS.find((timing) => timing.metric === metric);
@@ -1131,7 +1284,7 @@ var PerformanceView = class {
1131
1284
  }
1132
1285
  }
1133
1286
  paintNumber(config, element, sample, stats) {
1134
- const value = config.value(sample, stats);
1287
+ const value = config.value(sample, stats, this.budgets.frameMs);
1135
1288
  element.textContent = value === null ? "—" : config.format(value);
1136
1289
  element.classList.toggle("is-over", isOverBudget(this.budgets, config.key, value));
1137
1290
  }
@@ -1371,6 +1524,7 @@ var PerformanceView = class {
1371
1524
  const checkbox = this.buildCheckbox(false, "Show the scene's biggest draw costs", (on) => {
1372
1525
  this.costsOpen = on;
1373
1526
  this.costsBodyEl.hidden = !on;
1527
+ if (!on) this.showAllHidden();
1374
1528
  if (on) this.renderCosts();
1375
1529
  });
1376
1530
  toggleRow.append(createIcon("flame", "perf-monitor__icon"), label, checkbox);
@@ -1414,6 +1568,7 @@ var PerformanceView = class {
1414
1568
  this.costSortButtons.set(sort, button);
1415
1569
  head.append(button);
1416
1570
  }
1571
+ head.append(document.createElement("span"));
1417
1572
  this.costsListEl = document.createElement("div");
1418
1573
  this.costsListEl.className = "perf-monitor__costs-list";
1419
1574
  const hint = document.createElement("span");
@@ -1732,11 +1887,10 @@ const PERF_HUD_STYLES = `
1732
1887
  }
1733
1888
 
1734
1889
  .perf-monitor__cost {
1735
- /* Rows are buttons, which shrink to their content without a width. */
1736
1890
  box-sizing: border-box;
1737
1891
  width: 100%;
1738
1892
  display: grid;
1739
- grid-template-columns: minmax(0, 1fr) 5ch 6ch;
1893
+ grid-template-columns: minmax(0, 1fr) 5ch 6ch 14px;
1740
1894
  gap: 6px;
1741
1895
  align-items: baseline;
1742
1896
  padding: 2px 6px;
@@ -1746,13 +1900,58 @@ const PERF_HUD_STYLES = `
1746
1900
  color: inherit;
1747
1901
  font: inherit;
1748
1902
  text-align: left;
1749
- cursor: pointer;
1903
+ cursor: default;
1750
1904
  }
1751
1905
 
1752
1906
  .perf-monitor__cost:not(.perf-monitor__cost--head):hover {
1753
1907
  background: var(--perf-row);
1754
1908
  }
1755
1909
 
1910
+ .perf-monitor__cost.is-hidden .perf-monitor__cost-name {
1911
+ opacity: 0.6;
1912
+ text-decoration: line-through;
1913
+ }
1914
+
1915
+ /* What hiding saved, across the calls and tris columns. */
1916
+ .perf-monitor__cost-saved {
1917
+ grid-column: 2 / span 2;
1918
+ color: var(--perf-accent);
1919
+ font-variant-numeric: tabular-nums lining-nums;
1920
+ text-align: right;
1921
+ white-space: nowrap;
1922
+ }
1923
+
1924
+ .perf-monitor__cost-eye {
1925
+ display: flex;
1926
+ align-items: center;
1927
+ justify-content: center;
1928
+ width: 14px;
1929
+ height: 14px;
1930
+ padding: 0;
1931
+ border: 0;
1932
+ border-radius: 3px;
1933
+ background: transparent;
1934
+ color: var(--perf-muted);
1935
+ cursor: pointer;
1936
+ align-self: center;
1937
+ }
1938
+
1939
+ .perf-monitor__cost-eye .perf-monitor__icon {
1940
+ width: 12px;
1941
+ height: 12px;
1942
+ color: inherit;
1943
+ }
1944
+
1945
+ .perf-monitor__cost-eye:hover,
1946
+ .perf-monitor__cost-eye[aria-pressed="true"] {
1947
+ color: var(--perf-fg);
1948
+ }
1949
+
1950
+ .perf-monitor__cost-eye:focus-visible {
1951
+ outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);
1952
+ outline-offset: 1px;
1953
+ }
1954
+
1756
1955
  .perf-monitor__cost:focus-visible {
1757
1956
  outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);
1758
1957
  outline-offset: -1px;
@@ -1763,13 +1962,30 @@ const PERF_HUD_STYLES = `
1763
1962
  cursor: default;
1764
1963
  }
1765
1964
 
1965
+ /* The name is a button: a click logs the row's objects. */
1766
1966
  .perf-monitor__cost-name {
1967
+ min-width: 0;
1968
+ padding: 0;
1969
+ border: 0;
1970
+ background: transparent;
1971
+ font: inherit;
1972
+ text-align: left;
1973
+ cursor: pointer;
1767
1974
  overflow: hidden;
1768
1975
  text-overflow: ellipsis;
1769
1976
  white-space: nowrap;
1770
1977
  color: var(--perf-fg-dim);
1771
1978
  }
1772
1979
 
1980
+ .perf-monitor__cost-name:hover {
1981
+ color: var(--perf-fg);
1982
+ }
1983
+
1984
+ .perf-monitor__cost-name:focus-visible {
1985
+ outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);
1986
+ outline-offset: 1px;
1987
+ }
1988
+
1773
1989
  .perf-monitor__cost-copies {
1774
1990
  color: var(--perf-muted);
1775
1991
  }
@@ -2236,4 +2452,4 @@ const mountPerfHud = (monitor, options = {}) => {
2236
2452
  //#endregion
2237
2453
  export { formatReport as a, HudSettings as c, isThemeMode as d, resolveBudgets as f, 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 };
2238
2454
 
2239
- //# sourceMappingURL=mount-perf-hud-BL84ddLm.js.map
2455
+ //# sourceMappingURL=mount-perf-hud-U_Xlj0Od.js.map