react-dev-profiler 1.1.1 → 1.2.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 CHANGED
@@ -88,11 +88,12 @@ All observers are created lazily (only when the panel is open) and cleaned up on
88
88
 
89
89
  ### `<DevProfiler>`
90
90
 
91
- | Prop | Type | Default | Description |
92
- | ---------- | --------------- | --------------- | ---------------------------------------- |
93
- | `children` | `ReactNode` | — | The subtree to profile |
94
- | `position` | `PanelPosition` | `'bottom-left'` | Where to anchor the panel |
95
- | `id` | `string` | auto-generated | Instance label (shown with multi-panels) |
91
+ | Prop | Type | Default | Description |
92
+ | ------------- | --------------- | --------------- | ------------------------------------------ |
93
+ | `children` | `ReactNode` | — | The subtree to profile |
94
+ | `position` | `PanelPosition` | `'bottom-left'` | Where to anchor the panel |
95
+ | `id` | `string` | auto-generated | Instance label (shown with multi-panels) |
96
+ | `accentColor` | `string` | `'#6366f1'` | Accent color for the toggle button's glow |
96
97
 
97
98
  ### `PanelPosition`
98
99
 
package/dist/index.cjs CHANGED
@@ -20,8 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- DevProfiler: () => DevProfiler,
24
- default: () => DevProfiler
23
+ DevProfiler: () => DevProfiler
25
24
  });
26
25
  module.exports = __toCommonJS(index_exports);
27
26
 
@@ -56,6 +55,25 @@ function percentile(sorted, p) {
56
55
  return sorted[Math.max(0, idx)];
57
56
  }
58
57
 
58
+ // src/constants.ts
59
+ var COLOR_GREEN = "#4ade80";
60
+ var COLOR_AMBER = "#f59e0b";
61
+ var COLOR_RED = "#ef4444";
62
+ var COLOR_MUTED = "#888";
63
+ var COLOR_DIM = "#444";
64
+ var COLOR_ACCENT = "#6366f1";
65
+ var FPS_60_MS = 16.67;
66
+ var FPS_30_MS = 33;
67
+ var GRAPH_BG = "#111";
68
+ var GRAPH_60FPS_LINE = "#1a3a1a";
69
+ var GRAPH_30FPS_LINE = "#3a1a1a";
70
+ var PANEL_GAP = 8;
71
+ function ftColor(ms) {
72
+ if (ms > FPS_30_MS) return COLOR_RED;
73
+ if (ms > FPS_60_MS) return COLOR_AMBER;
74
+ return COLOR_GREEN;
75
+ }
76
+
59
77
  // src/env.ts
60
78
  var __DEV__ = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
61
79
 
@@ -102,7 +120,7 @@ var s = {
102
120
  },
103
121
  instanceBadge: {
104
122
  background: "#222",
105
- color: "#888",
123
+ color: COLOR_MUTED,
106
124
  fontSize: 8,
107
125
  padding: "1px 5px",
108
126
  borderRadius: 4,
@@ -117,7 +135,7 @@ var s = {
117
135
  iconBtn: {
118
136
  background: "none",
119
137
  border: "none",
120
- color: "#444",
138
+ color: COLOR_DIM,
121
139
  cursor: "pointer",
122
140
  padding: 4,
123
141
  margin: -4,
@@ -127,12 +145,12 @@ var s = {
127
145
  transition: "color 0.15s"
128
146
  },
129
147
  iconBtnActive: {
130
- color: "#4ade80"
148
+ color: COLOR_GREEN
131
149
  },
132
150
  closeBtn: {
133
151
  background: "none",
134
152
  border: "none",
135
- color: "#444",
153
+ color: COLOR_DIM,
136
154
  cursor: "pointer",
137
155
  fontSize: 13,
138
156
  padding: 4,
@@ -145,7 +163,7 @@ var s = {
145
163
  gap: 5
146
164
  },
147
165
  section: {
148
- color: "#444",
166
+ color: COLOR_DIM,
149
167
  fontSize: 8,
150
168
  fontWeight: 600,
151
169
  letterSpacing: 1,
@@ -174,7 +192,7 @@ var s = {
174
192
  miniRow: {
175
193
  display: "flex",
176
194
  justifyContent: "space-between",
177
- color: "#444",
195
+ color: COLOR_DIM,
178
196
  fontSize: 9,
179
197
  marginTop: -2,
180
198
  marginBottom: 2
@@ -227,9 +245,9 @@ var s = {
227
245
  textAlign: "center"
228
246
  }
229
247
  };
230
- var FLASH_OUTLINE = "2px solid rgba(99, 102, 241, 0.8)";
248
+ var FLASH_OUTLINE = `2px solid ${COLOR_ACCENT}cc`;
231
249
 
232
- // src/hooks.ts
250
+ // src/utils.ts
233
251
  function getEffectiveRect(el) {
234
252
  const rect = el.getBoundingClientRect();
235
253
  if (rect.width > 0 || rect.height > 0) return rect;
@@ -256,6 +274,8 @@ function getObservableChildren(el) {
256
274
  }
257
275
  return result;
258
276
  }
277
+
278
+ // src/hooks.ts
259
279
  function useAnchorPosition(ref, position = "bottom-left") {
260
280
  const [pos, setPos] = (0, import_react.useState)({ top: 0, left: 0 });
261
281
  (0, import_react.useEffect)(() => {
@@ -401,22 +421,24 @@ function useLongTasks(enabled) {
401
421
  // src/DevStatsPanel.tsx
402
422
  var import_react2 = require("react");
403
423
  var import_react_dom = require("react-dom");
424
+
425
+ // src/FrameTimeGraph.tsx
404
426
  var import_jsx_runtime = require("react/jsx-runtime");
405
427
  function FrameTimeGraph({ history }) {
406
- const max = Math.max(33, ...history);
428
+ const max = Math.max(FPS_30_MS, ...history);
407
429
  const w = 140;
408
430
  const h = 32;
409
431
  const barW = Math.max(1, w / HISTORY_SIZE - 0.5);
410
432
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.graphWrap, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: w, height: h, style: { display: "block" }, children: [
411
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { width: w, height: h, rx: 3, fill: "#111" }),
433
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { width: w, height: h, rx: 3, fill: GRAPH_BG }),
412
434
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
413
435
  "line",
414
436
  {
415
437
  x1: 0,
416
- y1: h - 16.67 / max * h,
438
+ y1: h - FPS_60_MS / max * h,
417
439
  x2: w,
418
- y2: h - 16.67 / max * h,
419
- stroke: "#1a3a1a",
440
+ y2: h - FPS_60_MS / max * h,
441
+ stroke: GRAPH_60FPS_LINE,
420
442
  strokeWidth: 1
421
443
  }
422
444
  ),
@@ -424,31 +446,36 @@ function FrameTimeGraph({ history }) {
424
446
  "line",
425
447
  {
426
448
  x1: 0,
427
- y1: h - 33 / max * h,
449
+ y1: h - FPS_30_MS / max * h,
428
450
  x2: w,
429
- y2: h - 33 / max * h,
430
- stroke: "#3a1a1a",
451
+ y2: h - FPS_30_MS / max * h,
452
+ stroke: GRAPH_30FPS_LINE,
431
453
  strokeWidth: 1
432
454
  }
433
455
  ),
434
456
  history.map((ms, i) => {
435
457
  const x = i / HISTORY_SIZE * w;
436
458
  const barH = Math.min(ms / max * h, h);
437
- const color = ms > 33 ? "#ef4444" : ms > 16.67 ? "#f59e0b" : "#4ade80";
438
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x, y: h - barH, width: barW, height: barH, fill: color, opacity: 0.8, rx: 0.5 }, i);
459
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x, y: h - barH, width: barW, height: barH, fill: ftColor(ms), opacity: 0.8, rx: 0.5 }, i);
439
460
  })
440
461
  ] }) });
441
462
  }
442
- function StatRow({ label, value, sub, color = "#4ade80" }) {
443
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.row, children: [
444
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.rowLabel, children: label }),
445
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
446
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...s.rowValue, color }, children: value }),
447
- sub && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#444", fontSize: 9, marginLeft: 4 }, children: sub })
463
+
464
+ // src/StatRow.tsx
465
+ var import_jsx_runtime2 = require("react/jsx-runtime");
466
+ function StatRow({ label, value, sub, color = COLOR_GREEN }) {
467
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: s.row, children: [
468
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: s.rowLabel, children: label }),
469
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
470
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { ...s.rowValue, color }, children: value }),
471
+ sub && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: COLOR_DIM, fontSize: 9, marginLeft: 4 }, children: sub })
448
472
  ] })
449
473
  ] });
450
474
  }
451
- var GAP = 8;
475
+
476
+ // src/DevStatsPanel.tsx
477
+ var import_jsx_runtime3 = require("react/jsx-runtime");
478
+ var GAP = PANEL_GAP;
452
479
  function getPanelStyle(pos, offset, position) {
453
480
  const style = {
454
481
  ...s.panel,
@@ -552,71 +579,71 @@ function DevStatsPanel({
552
579
  setExported(true);
553
580
  setTimeout(() => setExported(false), 1200);
554
581
  }, [stats]);
555
- const ftColor = stats.frameTime > 33 ? "#ef4444" : stats.frameTime > 16.67 ? "#f59e0b" : "#4ade80";
556
- const rpsColor = stats.rendersPerSecond > 30 ? "#ef4444" : stats.rendersPerSecond > 10 ? "#f59e0b" : "#4ade80";
557
- const actualColor = stats.profiler.actualDuration > 16 ? "#ef4444" : stats.profiler.actualDuration > 8 ? "#f59e0b" : "#4ade80";
582
+ const frameTColor = ftColor(stats.frameTime);
583
+ const rpsColor = stats.rendersPerSecond > 30 ? COLOR_RED : stats.rendersPerSecond > 10 ? COLOR_AMBER : COLOR_GREEN;
584
+ const actualColor = stats.profiler.actualDuration > 16 ? COLOR_RED : stats.profiler.actualDuration > 8 ? COLOR_AMBER : COLOR_GREEN;
558
585
  const fps = stats.frameTime > 0 ? Math.round(1e3 / stats.frameTime) : 0;
559
586
  const memoGain = stats.profiler.baseDuration > 0 ? Math.round((1 - stats.profiler.actualDuration / stats.profiler.baseDuration) * 100) : 0;
560
- const p99Color = stats.frameTimeP99 > 33 ? "#ef4444" : stats.frameTimeP99 > 16.67 ? "#f59e0b" : "#4ade80";
587
+ const p99Color = ftColor(stats.frameTimeP99);
561
588
  const exportStyle = exported ? { ...s.iconBtn, ...s.iconBtnActive } : s.iconBtn;
562
589
  return (0, import_react_dom.createPortal)(
563
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: getPanelStyle(pos, offset, position), ...dragHandlers, children: [
564
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.panelHeader, children: [
565
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: s.panelTitle, children: [
590
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: getPanelStyle(pos, offset, position), ...dragHandlers, children: [
591
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: s.panelHeader, children: [
592
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: s.panelTitle, children: [
566
593
  "Dev Profiler",
567
- instanceCount > 1 && instanceId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.instanceBadge, children: instanceId })
594
+ instanceCount > 1 && instanceId && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: s.instanceBadge, children: instanceId })
568
595
  ] }),
569
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.headerActions, children: [
570
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
596
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: s.headerActions, children: [
597
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
571
598
  "button",
572
599
  {
573
600
  style: exportStyle,
574
601
  onClick: handleExport,
575
602
  title: exported ? "Exported!" : "Export stats as JSON",
576
- children: exported ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "3.5 8.5 6.5 11.5 12.5 4.5" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 2v8M4 7l4 4 4-4M2 14h12" }) })
603
+ children: exported ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "3.5 8.5 6.5 11.5 12.5 4.5" }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M8 2v8M4 7l4 4 4-4M2 14h12" }) })
577
604
  }
578
605
  ),
579
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.iconBtn, onClick: handleReset, title: "Reset counters", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 1a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4a1 1 0 0 1 0-2h1.6A6 6 0 0 0 2.07 7.5a1 1 0 1 1-1.97-.36A8 8 0 0 1 13 3.35V2a1 1 0 0 1 1-1zM2 15a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h4a1 1 0 0 1 0 2H4.4A6 6 0 0 0 13.93 8.5a1 1 0 1 1 1.97.36A8 8 0 0 1 3 12.65V14a1 1 0 0 1-1 1z" }) }) }),
580
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.closeBtn, onClick: onClose, children: "\u2715" })
606
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { style: s.iconBtn, onClick: handleReset, title: "Reset counters", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M14 1a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-4a1 1 0 0 1 0-2h1.6A6 6 0 0 0 2.07 7.5a1 1 0 1 1-1.97-.36A8 8 0 0 1 13 3.35V2a1 1 0 0 1 1-1zM2 15a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h4a1 1 0 0 1 0 2H4.4A6 6 0 0 0 13.93 8.5a1 1 0 1 1 1.97.36A8 8 0 0 1 3 12.65V14a1 1 0 0 1-1 1z" }) }) }),
607
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { style: s.closeBtn, onClick: onClose, children: "\u2715" })
581
608
  ] })
582
609
  ] }),
583
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.body, children: [
584
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.section, children: "Rendering" }),
585
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Frame time", value: `${stats.frameTime.toFixed(1)}ms`, sub: `${fps} fps`, color: ftColor }),
586
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FrameTimeGraph, { history: stats.frameTimeHistory }),
587
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.miniRow, children: [
588
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
610
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: s.body, children: [
611
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: s.section, children: "Rendering" }),
612
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatRow, { label: "Frame time", value: `${stats.frameTime.toFixed(1)}ms`, sub: `${fps} fps`, color: frameTColor }),
613
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FrameTimeGraph, { history: stats.frameTimeHistory }),
614
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: s.miniRow, children: [
615
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
589
616
  "min ",
590
617
  stats.frameTimeMin.toFixed(1)
591
618
  ] }),
592
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
619
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
593
620
  "max ",
594
621
  stats.frameTimeMax.toFixed(1)
595
622
  ] }),
596
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { color: p99Color }, children: [
623
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: { color: p99Color }, children: [
597
624
  "p99 ",
598
625
  stats.frameTimeP99.toFixed(1)
599
626
  ] })
600
627
  ] }),
601
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Renders/s", value: String(stats.rendersPerSecond), color: rpsColor }),
602
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Long tasks", value: String(stats.longTasks), color: stats.longTasks > 0 ? "#f59e0b" : "#4ade80" }),
603
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.separator }),
604
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.section, children: "React Profiler" }),
605
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Phase", value: stats.profiler.phase, color: "#888" }),
606
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Render", value: `${stats.profiler.actualDuration.toFixed(2)}ms`, color: actualColor }),
607
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Base (no memo)", value: `${stats.profiler.baseDuration.toFixed(2)}ms`, color: "#888" }),
608
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Memo gain", value: `${memoGain}%`, color: memoGain > 50 ? "#4ade80" : memoGain > 20 ? "#f59e0b" : "#ef4444" }),
609
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Commits", value: String(stats.profiler.commitCount) }),
610
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.separator }),
611
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.section, children: "DOM" }),
612
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Nodes", value: stats.domNodes.toLocaleString() }),
613
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Mutations", value: String(stats.domMutations) }),
614
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Size", value: stats.dimensions, color: "#888" }),
615
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.separator }),
616
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.section, children: "Memory" }),
617
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "JS Heap", value: stats.memory > 0 ? `${stats.memory} MB` : "N/A" })
628
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatRow, { label: "Renders/s", value: String(stats.rendersPerSecond), color: rpsColor }),
629
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatRow, { label: "Long tasks", value: String(stats.longTasks), color: stats.longTasks > 0 ? COLOR_AMBER : COLOR_GREEN }),
630
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: s.separator }),
631
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: s.section, children: "React Profiler" }),
632
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatRow, { label: "Phase", value: stats.profiler.phase, color: COLOR_MUTED }),
633
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatRow, { label: "Render", value: `${stats.profiler.actualDuration.toFixed(2)}ms`, color: actualColor }),
634
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatRow, { label: "Base (no memo)", value: `${stats.profiler.baseDuration.toFixed(2)}ms`, color: COLOR_MUTED }),
635
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatRow, { label: "Memo gain", value: `${memoGain}%`, color: memoGain > 50 ? COLOR_GREEN : memoGain > 20 ? COLOR_AMBER : COLOR_RED }),
636
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatRow, { label: "Commits", value: String(stats.profiler.commitCount) }),
637
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: s.separator }),
638
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: s.section, children: "DOM" }),
639
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatRow, { label: "Nodes", value: stats.domNodes.toLocaleString() }),
640
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatRow, { label: "Mutations", value: String(stats.domMutations) }),
641
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatRow, { label: "Size", value: stats.dimensions, color: COLOR_MUTED }),
642
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: s.separator }),
643
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: s.section, children: "Memory" }),
644
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatRow, { label: "JS Heap", value: stats.memory > 0 ? `${stats.memory} MB` : "N/A" })
618
645
  ] }),
619
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.footer, children: "Ctrl+I to toggle" })
646
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: s.footer, children: "Ctrl+I to toggle" })
620
647
  ] }),
621
648
  document.body
622
649
  );
@@ -625,8 +652,8 @@ function DevStatsPanel({
625
652
  // src/ToggleButton.tsx
626
653
  var import_react3 = require("react");
627
654
  var import_react_dom2 = require("react-dom");
628
- var import_jsx_runtime2 = require("react/jsx-runtime");
629
- var GAP2 = 8;
655
+ var import_jsx_runtime4 = require("react/jsx-runtime");
656
+ var GAP2 = PANEL_GAP;
630
657
  function getButtonStyle(pos, position) {
631
658
  const style = { ...s.toggleBtn };
632
659
  if (position.startsWith("bottom")) {
@@ -645,7 +672,7 @@ function ToggleButton({
645
672
  targetRef,
646
673
  onClick,
647
674
  position = "bottom-left",
648
- accentColor = "#6366f1"
675
+ accentColor = COLOR_ACCENT
649
676
  }) {
650
677
  const pos = useAnchorPosition(targetRef, position);
651
678
  const [fps, setFps] = (0, import_react3.useState)(0);
@@ -667,16 +694,16 @@ function ToggleButton({
667
694
  return () => cancelAnimationFrame(animId);
668
695
  }, []);
669
696
  return (0, import_react_dom2.createPortal)(
670
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
697
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
671
698
  "button",
672
699
  {
673
700
  onClick,
674
701
  title: "Dev Profiler (Ctrl+I)",
675
702
  style: getButtonStyle(pos, position),
676
703
  children: [
677
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { ...s.toggleDot, background: accentColor, boxShadow: `0 0 4px ${accentColor}` } }),
678
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: s.toggleFps, children: fps }),
679
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: s.toggleLabel, children: "fps" })
704
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { ...s.toggleDot, background: accentColor, boxShadow: `0 0 4px ${accentColor}` } }),
705
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: s.toggleFps, children: fps }),
706
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: s.toggleLabel, children: "fps" })
680
707
  ]
681
708
  }
682
709
  ),
@@ -685,7 +712,7 @@ function ToggleButton({
685
712
  }
686
713
 
687
714
  // src/DevProfiler.tsx
688
- var import_jsx_runtime3 = require("react/jsx-runtime");
715
+ var import_jsx_runtime5 = require("react/jsx-runtime");
689
716
  var TOGGLE_EVENT = "devprofiler:toggle";
690
717
  var BOUND_KEY = "__devprofiler_bound";
691
718
  if (typeof window !== "undefined" && __DEV__ && !window[BOUND_KEY]) {
@@ -703,7 +730,7 @@ function DevProfiler({
703
730
  children,
704
731
  position = "bottom-left",
705
732
  id,
706
- accentColor = "#6366f1"
733
+ accentColor = COLOR_ACCENT
707
734
  }) {
708
735
  const wrapperRef = (0, import_react4.useRef)(null);
709
736
  const [open, setOpen] = (0, import_react4.useState)(false);
@@ -740,11 +767,11 @@ function DevProfiler({
740
767
  window.addEventListener(TOGGLE_EVENT, handler);
741
768
  return () => window.removeEventListener(TOGGLE_EVENT, handler);
742
769
  }, []);
743
- if (!__DEV__) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children });
744
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { ref: wrapperRef, style: { display: "contents" }, children: [
745
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Profiler, { id: "DevProfiler", onRender, children }),
746
- !open && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ToggleButton, { targetRef: wrapperRef, onClick: toggle, position, accentColor }),
747
- open && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
770
+ if (!__DEV__) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children });
771
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: wrapperRef, style: { display: "contents" }, children: [
772
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react4.Profiler, { id: "DevProfiler", onRender, children }),
773
+ !open && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ToggleButton, { targetRef: wrapperRef, onClick: toggle, position, accentColor }),
774
+ open && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
748
775
  DevStatsPanel,
749
776
  {
750
777
  targetRef: wrapperRef,
@@ -771,6 +798,12 @@ function DevProfiler({
771
798
  * @author Frederic Denis (billywild87) — https://github.com/billywild87
772
799
  * @license MIT
773
800
  */
801
+ /**
802
+ * @module react-dev-profiler
803
+ * @description Shared colors and thresholds used across the profiler UI.
804
+ * @author Frederic Denis (billywild87) — https://github.com/billywild87
805
+ * @license MIT
806
+ */
774
807
  /**
775
808
  * @module react-dev-profiler
776
809
  * @description Environment detection — works with Vite, webpack, Next.js, and any bundler.
@@ -783,12 +816,30 @@ function DevProfiler({
783
816
  * @author Frederic Denis (billywild87) — https://github.com/billywild87
784
817
  * @license MIT
785
818
  */
819
+ /**
820
+ * @module react-dev-profiler
821
+ * @description Pure utility functions for DOM measurement.
822
+ * @author Frederic Denis (billywild87) — https://github.com/billywild87
823
+ * @license MIT
824
+ */
786
825
  /**
787
826
  * @module react-dev-profiler
788
827
  * @description Custom hooks that power the profiler's data collection.
789
828
  * @author Frederic Denis (billywild87) — https://github.com/billywild87
790
829
  * @license MIT
791
830
  */
831
+ /**
832
+ * @module react-dev-profiler
833
+ * @description Rolling bar chart of frame times (last 60 samples).
834
+ * @author Frederic Denis (billywild87) — https://github.com/billywild87
835
+ * @license MIT
836
+ */
837
+ /**
838
+ * @module react-dev-profiler
839
+ * @description Single label/value row used throughout the profiler panel.
840
+ * @author Frederic Denis (billywild87) — https://github.com/billywild87
841
+ * @license MIT
842
+ */
792
843
  /**
793
844
  * @module react-dev-profiler
794
845
  * @description The main stats panel — renders all performance metrics in a floating overlay.