react-dev-profiler 1.0.1 → 1.1.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/dist/index.js CHANGED
@@ -1,8 +1,5 @@
1
1
  // src/DevProfiler.tsx
2
- import { Profiler, useRef as useRef4, useState as useState4, useEffect as useEffect4, useCallback as useCallback3 } from "react";
3
-
4
- // src/DevProfiler.module.css
5
- var DevProfiler_default = {};
2
+ import { Profiler, useRef as useRef3, useState as useState4, useEffect as useEffect4, useCallback as useCallback3 } from "react";
6
3
 
7
4
  // src/types.ts
8
5
  var HISTORY_SIZE = 60;
@@ -37,32 +34,228 @@ var __DEV__ = typeof process !== "undefined" ? process.env.NODE_ENV !== "product
37
34
 
38
35
  // src/hooks.ts
39
36
  import { useState, useEffect, useCallback, useRef } from "react";
37
+
38
+ // src/styles.ts
39
+ var s = {
40
+ wrapper: {
41
+ display: "contents"
42
+ },
43
+ panel: {
44
+ position: "fixed",
45
+ zIndex: 99999,
46
+ background: "rgba(8, 8, 8, 0.95)",
47
+ border: "1px solid #222",
48
+ borderRadius: 10,
49
+ padding: "10px 14px",
50
+ minWidth: 220,
51
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.7)",
52
+ fontFamily: "'SF Mono', 'Fira Code', monospace",
53
+ backdropFilter: "blur(12px)",
54
+ userSelect: "none",
55
+ color: "#ccc",
56
+ fontSize: 11,
57
+ cursor: "grab",
58
+ touchAction: "none"
59
+ },
60
+ panelHeader: {
61
+ display: "flex",
62
+ justifyContent: "space-between",
63
+ alignItems: "center",
64
+ marginBottom: 8
65
+ },
66
+ panelTitle: {
67
+ color: "#666",
68
+ fontSize: 9,
69
+ fontWeight: 700,
70
+ letterSpacing: 1.5,
71
+ textTransform: "uppercase",
72
+ display: "flex",
73
+ alignItems: "center",
74
+ gap: 6
75
+ },
76
+ instanceBadge: {
77
+ background: "#222",
78
+ color: "#888",
79
+ fontSize: 8,
80
+ padding: "1px 5px",
81
+ borderRadius: 4,
82
+ letterSpacing: 0.5,
83
+ textTransform: "none"
84
+ },
85
+ headerActions: {
86
+ display: "flex",
87
+ alignItems: "center",
88
+ gap: 8
89
+ },
90
+ iconBtn: {
91
+ background: "none",
92
+ border: "none",
93
+ color: "#444",
94
+ cursor: "pointer",
95
+ padding: 4,
96
+ margin: -4,
97
+ lineHeight: 1,
98
+ display: "flex",
99
+ alignItems: "center",
100
+ transition: "color 0.15s"
101
+ },
102
+ iconBtnActive: {
103
+ color: "#4ade80"
104
+ },
105
+ closeBtn: {
106
+ background: "none",
107
+ border: "none",
108
+ color: "#444",
109
+ cursor: "pointer",
110
+ fontSize: 13,
111
+ padding: 4,
112
+ margin: -4,
113
+ lineHeight: 1
114
+ },
115
+ body: {
116
+ display: "flex",
117
+ flexDirection: "column",
118
+ gap: 5
119
+ },
120
+ section: {
121
+ color: "#444",
122
+ fontSize: 8,
123
+ fontWeight: 600,
124
+ letterSpacing: 1,
125
+ textTransform: "uppercase",
126
+ marginTop: 2
127
+ },
128
+ separator: {
129
+ height: 1,
130
+ background: "#1a1a1a",
131
+ margin: "2px 0"
132
+ },
133
+ row: {
134
+ display: "flex",
135
+ justifyContent: "space-between",
136
+ alignItems: "center",
137
+ padding: "1px 0"
138
+ },
139
+ rowLabel: {
140
+ color: "#555",
141
+ fontSize: 10
142
+ },
143
+ rowValue: {
144
+ fontSize: 11,
145
+ fontWeight: 600
146
+ },
147
+ miniRow: {
148
+ display: "flex",
149
+ justifyContent: "space-between",
150
+ color: "#444",
151
+ fontSize: 9,
152
+ marginTop: -2,
153
+ marginBottom: 2
154
+ },
155
+ graphWrap: {
156
+ marginTop: 4
157
+ },
158
+ toggleBtn: {
159
+ position: "fixed",
160
+ zIndex: 99998,
161
+ height: 26,
162
+ borderRadius: 13,
163
+ border: "1px solid rgba(255, 255, 255, 0.08)",
164
+ background: "rgba(28, 28, 30, 0.92)",
165
+ cursor: "pointer",
166
+ display: "flex",
167
+ alignItems: "center",
168
+ gap: 6,
169
+ padding: "0 10px 0 8px",
170
+ backdropFilter: "blur(20px)",
171
+ WebkitBackdropFilter: "blur(20px)",
172
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.4), inset 0 0.5px 0 rgba(255, 255, 255, 0.06)"
173
+ },
174
+ toggleDot: {
175
+ width: 6,
176
+ height: 6,
177
+ borderRadius: "50%",
178
+ flexShrink: 0
179
+ },
180
+ toggleFps: {
181
+ fontFamily: "'SF Mono', 'Fira Code', monospace",
182
+ fontSize: 11,
183
+ fontWeight: 600,
184
+ color: "rgba(255, 255, 255, 0.85)",
185
+ letterSpacing: -0.3,
186
+ lineHeight: 1
187
+ },
188
+ toggleLabel: {
189
+ fontFamily: "'SF Mono', 'Fira Code', monospace",
190
+ fontSize: 9,
191
+ fontWeight: 500,
192
+ color: "rgba(255, 255, 255, 0.35)",
193
+ letterSpacing: 0,
194
+ textTransform: "lowercase"
195
+ },
196
+ footer: {
197
+ marginTop: 8,
198
+ color: "#333",
199
+ fontSize: 8,
200
+ textAlign: "center"
201
+ }
202
+ };
203
+ var FLASH_OUTLINE = "2px solid rgba(99, 102, 241, 0.8)";
204
+
205
+ // src/hooks.ts
206
+ function getEffectiveRect(el) {
207
+ const rect = el.getBoundingClientRect();
208
+ if (rect.width > 0 || rect.height > 0) return rect;
209
+ const children = el.children;
210
+ if (children.length === 0) return rect;
211
+ let top = Infinity, left = Infinity, bottom = -Infinity, right = -Infinity;
212
+ for (let i = 0; i < children.length; i++) {
213
+ const cr = children[i].getBoundingClientRect();
214
+ if (cr.width === 0 && cr.height === 0) continue;
215
+ top = Math.min(top, cr.top);
216
+ left = Math.min(left, cr.left);
217
+ bottom = Math.max(bottom, cr.bottom);
218
+ right = Math.max(right, cr.right);
219
+ }
220
+ if (top === Infinity) return rect;
221
+ return new DOMRect(left, top, right - left, bottom - top);
222
+ }
223
+ function getObservableChildren(el) {
224
+ const result = [];
225
+ for (let i = 0; i < el.children.length; i++) {
226
+ const child = el.children[i];
227
+ const r = child.getBoundingClientRect();
228
+ if (r.width > 0 || r.height > 0) result.push(child);
229
+ }
230
+ return result;
231
+ }
40
232
  function useAnchorPosition(ref, position = "bottom-left") {
41
233
  const [pos, setPos] = useState({ top: 0, left: 0 });
42
234
  useEffect(() => {
43
235
  if (!ref.current) return;
236
+ const el = ref.current;
44
237
  const update = () => {
45
- if (!ref.current) return;
46
- const rect = ref.current.getBoundingClientRect();
47
- const margin = 8;
238
+ const rect = getEffectiveRect(el);
48
239
  switch (position) {
49
240
  case "top-left":
50
- setPos({ top: rect.top + margin, left: rect.left + margin });
241
+ setPos({ top: rect.top, left: rect.left });
51
242
  break;
52
243
  case "top-right":
53
- setPos({ top: rect.top + margin, left: rect.right - margin });
244
+ setPos({ top: rect.top, left: rect.right });
54
245
  break;
55
246
  case "bottom-right":
56
- setPos({ top: rect.bottom - margin, left: rect.right - margin });
247
+ setPos({ top: rect.bottom, left: rect.right });
57
248
  break;
58
249
  case "bottom-left":
59
250
  default:
60
- setPos({ top: rect.bottom - margin, left: rect.left + margin });
251
+ setPos({ top: rect.bottom, left: rect.left });
61
252
  break;
62
253
  }
63
254
  };
255
+ update();
64
256
  const observer = new ResizeObserver(update);
65
- observer.observe(ref.current);
257
+ observer.observe(el);
258
+ for (const child of getObservableChildren(el)) observer.observe(child);
66
259
  observer.observe(document.documentElement);
67
260
  return () => observer.disconnect();
68
261
  }, [ref, position]);
@@ -73,9 +266,10 @@ function useDraggable() {
73
266
  const dragging = useRef(false);
74
267
  const start = useRef({ x: 0, y: 0 });
75
268
  const onPointerDown = useCallback((e) => {
269
+ if (e.target.closest("button, a, [data-no-drag]")) return;
76
270
  dragging.current = true;
77
271
  start.current = { x: e.clientX - offset.x, y: e.clientY - offset.y };
78
- e.target.setPointerCapture(e.pointerId);
272
+ e.currentTarget.setPointerCapture(e.pointerId);
79
273
  }, [offset]);
80
274
  const onPointerMove = useCallback((e) => {
81
275
  if (!dragging.current) return;
@@ -95,21 +289,13 @@ function useDomTracker(wrapperRef, enabled) {
95
289
  const dirty = useRef(true);
96
290
  useEffect(() => {
97
291
  if (!enabled || !wrapperRef.current) return;
98
- const observer = new MutationObserver((records) => {
99
- let realMutation = false;
100
- for (const record of records) {
101
- if (record.type === "attributes" && record.attributeName === "class") continue;
102
- realMutation = true;
103
- }
104
- if (realMutation) {
105
- mutations.current++;
106
- dirty.current = true;
107
- }
292
+ const observer = new MutationObserver(() => {
293
+ mutations.current++;
294
+ dirty.current = true;
108
295
  });
109
296
  observer.observe(wrapperRef.current, {
110
297
  childList: true,
111
298
  subtree: true,
112
- attributes: true,
113
299
  attributeFilter: ["style"]
114
300
  });
115
301
  return () => observer.disconnect();
@@ -150,14 +336,19 @@ function useRenderFlash(wrapperRef, open) {
150
336
  const mutationCount = useRef(0);
151
337
  useEffect(() => {
152
338
  if (!open || !wrapperRef.current) return;
339
+ const wrapper = wrapperRef.current;
153
340
  const observer = new MutationObserver(() => {
154
341
  mutationCount.current++;
155
- if (!wrapperRef.current || mutationCount.current <= 1) return;
156
- const el = wrapperRef.current;
157
- el.classList.add(DevProfiler_default.flash);
158
- setTimeout(() => el.classList.remove(DevProfiler_default.flash), 150);
342
+ if (mutationCount.current <= 1) return;
343
+ const target = getObservableChildren(wrapper)[0] ?? wrapper;
344
+ target.style.outline = FLASH_OUTLINE;
345
+ target.style.outlineOffset = "-2px";
346
+ setTimeout(() => {
347
+ target.style.outline = "";
348
+ target.style.outlineOffset = "";
349
+ }, 150);
159
350
  });
160
- observer.observe(wrapperRef.current, { childList: true, subtree: true });
351
+ observer.observe(wrapper, { childList: true, subtree: true });
161
352
  return () => observer.disconnect();
162
353
  }, [wrapperRef, open]);
163
354
  }
@@ -189,7 +380,7 @@ function FrameTimeGraph({ history }) {
189
380
  const w = 140;
190
381
  const h = 32;
191
382
  const barW = Math.max(1, w / HISTORY_SIZE - 0.5);
192
- return /* @__PURE__ */ jsx("div", { className: DevProfiler_default.graphWrap, children: /* @__PURE__ */ jsxs("svg", { width: w, height: h, style: { display: "block" }, children: [
383
+ return /* @__PURE__ */ jsx("div", { style: s.graphWrap, children: /* @__PURE__ */ jsxs("svg", { width: w, height: h, style: { display: "block" }, children: [
193
384
  /* @__PURE__ */ jsx("rect", { width: w, height: h, rx: 3, fill: "#111" }),
194
385
  /* @__PURE__ */ jsx(
195
386
  "line",
@@ -222,27 +413,29 @@ function FrameTimeGraph({ history }) {
222
413
  ] }) });
223
414
  }
224
415
  function StatRow({ label, value, sub, color = "#4ade80" }) {
225
- return /* @__PURE__ */ jsxs("div", { className: DevProfiler_default.row, children: [
226
- /* @__PURE__ */ jsx("span", { className: DevProfiler_default.rowLabel, children: label }),
416
+ return /* @__PURE__ */ jsxs("div", { style: s.row, children: [
417
+ /* @__PURE__ */ jsx("span", { style: s.rowLabel, children: label }),
227
418
  /* @__PURE__ */ jsxs("span", { children: [
228
- /* @__PURE__ */ jsx("span", { className: DevProfiler_default.rowValue, style: { color }, children: value }),
419
+ /* @__PURE__ */ jsx("span", { style: { ...s.rowValue, color }, children: value }),
229
420
  sub && /* @__PURE__ */ jsx("span", { style: { color: "#444", fontSize: 9, marginLeft: 4 }, children: sub })
230
421
  ] })
231
422
  ] });
232
423
  }
424
+ var GAP = 8;
233
425
  function getPanelStyle(pos, offset, position) {
234
426
  const style = {
427
+ ...s.panel,
235
428
  transform: `translate(${offset.x}px, ${offset.y}px)`
236
429
  };
237
430
  if (position.startsWith("bottom")) {
238
- style.bottom = window.innerHeight - pos.top;
431
+ style.bottom = window.innerHeight - pos.top + GAP;
239
432
  } else {
240
- style.top = pos.top;
433
+ style.top = pos.top + GAP;
241
434
  }
242
435
  if (position.endsWith("right")) {
243
- style.right = window.innerWidth - pos.left;
436
+ style.right = window.innerWidth - pos.left + GAP;
244
437
  } else {
245
- style.left = pos.left;
438
+ style.left = pos.left + GAP;
246
439
  }
247
440
  return style;
248
441
  }
@@ -284,7 +477,8 @@ function DevStatsPanel({
284
477
  allFrameTimes.current.push(avgFrameTime);
285
478
  const sorted = [...allFrameTimes.current].sort((a, b) => a - b);
286
479
  const el = targetRef.current;
287
- const dims = el ? `${el.offsetWidth} x ${el.offsetHeight}` : "\u2013";
480
+ const r = el ? getEffectiveRect(el) : null;
481
+ const dims = r ? `${Math.round(r.width)} x ${Math.round(r.height)}` : "\u2013";
288
482
  const perf = performance;
289
483
  const mem = perf.memory ? Math.round(perf.memory.usedJSHeapSize / 1024 / 1024) : 0;
290
484
  setStats({
@@ -319,23 +513,17 @@ function DevStatsPanel({
319
513
  const handleExport = useCallback2(() => {
320
514
  const payload = { timestamp: (/* @__PURE__ */ new Date()).toISOString(), ...stats };
321
515
  const json = JSON.stringify(payload, null, 2);
322
- try {
323
- navigator.clipboard.writeText(json).then(() => {
324
- setExported(true);
325
- setTimeout(() => setExported(false), 1200);
326
- });
327
- } catch {
328
- const ta = document.createElement("textarea");
329
- ta.value = json;
330
- ta.style.position = "fixed";
331
- ta.style.opacity = "0";
332
- document.body.appendChild(ta);
333
- ta.select();
334
- document.execCommand("copy");
335
- document.body.removeChild(ta);
336
- setExported(true);
337
- setTimeout(() => setExported(false), 1200);
338
- }
516
+ const blob = new Blob([json], { type: "application/json" });
517
+ const url = URL.createObjectURL(blob);
518
+ const a = document.createElement("a");
519
+ a.href = url;
520
+ a.download = `devprofiler-${Date.now()}.json`;
521
+ document.body.appendChild(a);
522
+ a.click();
523
+ document.body.removeChild(a);
524
+ URL.revokeObjectURL(url);
525
+ setExported(true);
526
+ setTimeout(() => setExported(false), 1200);
339
527
  }, [stats]);
340
528
  const ftColor = stats.frameTime > 33 ? "#ef4444" : stats.frameTime > 16.67 ? "#f59e0b" : "#4ade80";
341
529
  const rpsColor = stats.rendersPerSecond > 30 ? "#ef4444" : stats.rendersPerSecond > 10 ? "#f59e0b" : "#4ade80";
@@ -343,42 +531,33 @@ function DevStatsPanel({
343
531
  const fps = stats.frameTime > 0 ? Math.round(1e3 / stats.frameTime) : 0;
344
532
  const memoGain = stats.profiler.baseDuration > 0 ? Math.round((1 - stats.profiler.actualDuration / stats.profiler.baseDuration) * 100) : 0;
345
533
  const p99Color = stats.frameTimeP99 > 33 ? "#ef4444" : stats.frameTimeP99 > 16.67 ? "#f59e0b" : "#4ade80";
534
+ const exportStyle = exported ? { ...s.iconBtn, ...s.iconBtnActive } : s.iconBtn;
346
535
  return createPortal(
347
- /* @__PURE__ */ jsxs("div", { className: DevProfiler_default.panel, style: getPanelStyle(pos, offset, position), children: [
348
- /* @__PURE__ */ jsxs(
349
- "div",
350
- {
351
- className: DevProfiler_default.panelHeader,
352
- ...dragHandlers,
353
- children: [
354
- /* @__PURE__ */ jsxs("span", { className: DevProfiler_default.panelTitle, children: [
355
- "Dev Profiler",
356
- instanceCount > 1 && instanceId && /* @__PURE__ */ jsx("span", { className: DevProfiler_default.instanceBadge, children: instanceId })
357
- ] }),
358
- /* @__PURE__ */ jsxs("div", { className: DevProfiler_default.headerActions, children: [
359
- /* @__PURE__ */ jsx(
360
- "button",
361
- {
362
- className: `${DevProfiler_default.exportBtn} ${exported ? DevProfiler_default.exportBtnActive : ""}`,
363
- onClick: handleExport,
364
- title: exported ? "Copied!" : "Copy stats to clipboard",
365
- children: exported ? /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.75.75 0 0 1 1.06-1.06L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0z" }) }) : /* @__PURE__ */ jsxs("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "currentColor", children: [
366
- /* @__PURE__ */ jsx("path", { d: "M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25z" }),
367
- /* @__PURE__ */ jsx("path", { d: "M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25z" })
368
- ] })
369
- }
370
- ),
371
- /* @__PURE__ */ jsx("button", { className: DevProfiler_default.resetBtn, onClick: handleReset, title: "Reset counters", children: /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ 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" }) }) }),
372
- /* @__PURE__ */ jsx("button", { className: DevProfiler_default.closeBtn, onClick: onClose, children: "\u2715" })
373
- ] })
374
- ]
375
- }
376
- ),
377
- /* @__PURE__ */ jsxs("div", { className: DevProfiler_default.body, children: [
378
- /* @__PURE__ */ jsx("span", { className: DevProfiler_default.section, children: "Rendering" }),
536
+ /* @__PURE__ */ jsxs("div", { style: getPanelStyle(pos, offset, position), ...dragHandlers, children: [
537
+ /* @__PURE__ */ jsxs("div", { style: s.panelHeader, children: [
538
+ /* @__PURE__ */ jsxs("span", { style: s.panelTitle, children: [
539
+ "Dev Profiler",
540
+ instanceCount > 1 && instanceId && /* @__PURE__ */ jsx("span", { style: s.instanceBadge, children: instanceId })
541
+ ] }),
542
+ /* @__PURE__ */ jsxs("div", { style: s.headerActions, children: [
543
+ /* @__PURE__ */ jsx(
544
+ "button",
545
+ {
546
+ style: exportStyle,
547
+ onClick: handleExport,
548
+ title: exported ? "Exported!" : "Export stats as JSON",
549
+ children: exported ? /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "3.5 8.5 6.5 11.5 12.5 4.5" }) }) : /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M8 2v8M4 7l4 4 4-4M2 14h12" }) })
550
+ }
551
+ ),
552
+ /* @__PURE__ */ jsx("button", { style: s.iconBtn, onClick: handleReset, title: "Reset counters", children: /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ 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" }) }) }),
553
+ /* @__PURE__ */ jsx("button", { style: s.closeBtn, onClick: onClose, children: "\u2715" })
554
+ ] })
555
+ ] }),
556
+ /* @__PURE__ */ jsxs("div", { style: s.body, children: [
557
+ /* @__PURE__ */ jsx("span", { style: s.section, children: "Rendering" }),
379
558
  /* @__PURE__ */ jsx(StatRow, { label: "Frame time", value: `${stats.frameTime.toFixed(1)}ms`, sub: `${fps} fps`, color: ftColor }),
380
559
  /* @__PURE__ */ jsx(FrameTimeGraph, { history: stats.frameTimeHistory }),
381
- /* @__PURE__ */ jsxs("div", { className: DevProfiler_default.miniRow, children: [
560
+ /* @__PURE__ */ jsxs("div", { style: s.miniRow, children: [
382
561
  /* @__PURE__ */ jsxs("span", { children: [
383
562
  "min ",
384
563
  stats.frameTimeMin.toFixed(1)
@@ -394,61 +573,61 @@ function DevStatsPanel({
394
573
  ] }),
395
574
  /* @__PURE__ */ jsx(StatRow, { label: "Renders/s", value: String(stats.rendersPerSecond), color: rpsColor }),
396
575
  /* @__PURE__ */ jsx(StatRow, { label: "Long tasks", value: String(stats.longTasks), color: stats.longTasks > 0 ? "#f59e0b" : "#4ade80" }),
397
- /* @__PURE__ */ jsx("div", { className: DevProfiler_default.separator }),
398
- /* @__PURE__ */ jsx("span", { className: DevProfiler_default.section, children: "React Profiler" }),
576
+ /* @__PURE__ */ jsx("div", { style: s.separator }),
577
+ /* @__PURE__ */ jsx("span", { style: s.section, children: "React Profiler" }),
399
578
  /* @__PURE__ */ jsx(StatRow, { label: "Phase", value: stats.profiler.phase, color: "#888" }),
400
579
  /* @__PURE__ */ jsx(StatRow, { label: "Render", value: `${stats.profiler.actualDuration.toFixed(2)}ms`, color: actualColor }),
401
580
  /* @__PURE__ */ jsx(StatRow, { label: "Base (no memo)", value: `${stats.profiler.baseDuration.toFixed(2)}ms`, color: "#888" }),
402
581
  /* @__PURE__ */ jsx(StatRow, { label: "Memo gain", value: `${memoGain}%`, color: memoGain > 50 ? "#4ade80" : memoGain > 20 ? "#f59e0b" : "#ef4444" }),
403
582
  /* @__PURE__ */ jsx(StatRow, { label: "Commits", value: String(stats.profiler.commitCount) }),
404
- /* @__PURE__ */ jsx("div", { className: DevProfiler_default.separator }),
405
- /* @__PURE__ */ jsx("span", { className: DevProfiler_default.section, children: "DOM" }),
583
+ /* @__PURE__ */ jsx("div", { style: s.separator }),
584
+ /* @__PURE__ */ jsx("span", { style: s.section, children: "DOM" }),
406
585
  /* @__PURE__ */ jsx(StatRow, { label: "Nodes", value: stats.domNodes.toLocaleString() }),
407
586
  /* @__PURE__ */ jsx(StatRow, { label: "Mutations", value: String(stats.domMutations) }),
408
587
  /* @__PURE__ */ jsx(StatRow, { label: "Size", value: stats.dimensions, color: "#888" }),
409
- /* @__PURE__ */ jsx("div", { className: DevProfiler_default.separator }),
410
- /* @__PURE__ */ jsx("span", { className: DevProfiler_default.section, children: "Memory" }),
588
+ /* @__PURE__ */ jsx("div", { style: s.separator }),
589
+ /* @__PURE__ */ jsx("span", { style: s.section, children: "Memory" }),
411
590
  /* @__PURE__ */ jsx(StatRow, { label: "JS Heap", value: stats.memory > 0 ? `${stats.memory} MB` : "N/A" })
412
591
  ] }),
413
- /* @__PURE__ */ jsx("div", { className: DevProfiler_default.footer, children: "Ctrl+I to toggle" })
592
+ /* @__PURE__ */ jsx("div", { style: s.footer, children: "Ctrl+I to toggle" })
414
593
  ] }),
415
594
  document.body
416
595
  );
417
596
  }
418
597
 
419
598
  // src/ToggleButton.tsx
420
- import { useState as useState3, useEffect as useEffect3, useRef as useRef3 } from "react";
599
+ import { useState as useState3, useEffect as useEffect3 } from "react";
421
600
  import { createPortal as createPortal2 } from "react-dom";
422
- import { jsx as jsx2 } from "react/jsx-runtime";
601
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
602
+ var GAP2 = 8;
423
603
  function getButtonStyle(pos, position) {
424
- const style = {};
604
+ const style = { ...s.toggleBtn };
425
605
  if (position.startsWith("bottom")) {
426
- style.bottom = window.innerHeight - pos.top + 8;
606
+ style.bottom = window.innerHeight - pos.top + GAP2;
427
607
  } else {
428
- style.top = pos.top + 8;
608
+ style.top = pos.top + GAP2;
429
609
  }
430
610
  if (position.endsWith("right")) {
431
- style.right = window.innerWidth - pos.left;
611
+ style.right = window.innerWidth - pos.left + GAP2;
432
612
  } else {
433
- style.left = pos.left;
613
+ style.left = pos.left + GAP2;
434
614
  }
435
615
  return style;
436
616
  }
437
617
  function ToggleButton({
438
618
  targetRef,
439
619
  onClick,
440
- position = "bottom-left"
620
+ position = "bottom-left",
621
+ accentColor = "#6366f1"
441
622
  }) {
442
623
  const pos = useAnchorPosition(targetRef, position);
443
624
  const [fps, setFps] = useState3(0);
444
- const lastFrame = useRef3(performance.now());
445
625
  useEffect3(() => {
446
626
  let animId;
447
627
  let count = 0;
448
628
  let lastSecond = performance.now();
449
629
  const tick = () => {
450
630
  const now = performance.now();
451
- lastFrame.current = now;
452
631
  count++;
453
632
  if (now - lastSecond >= 1e3) {
454
633
  setFps(count);
@@ -460,16 +639,18 @@ function ToggleButton({
460
639
  animId = requestAnimationFrame(tick);
461
640
  return () => cancelAnimationFrame(animId);
462
641
  }, []);
463
- const fpsColor = fps < 30 ? "#ef4444" : fps < 55 ? "#f59e0b" : "#4ade80";
464
642
  return createPortal2(
465
- /* @__PURE__ */ jsx2(
643
+ /* @__PURE__ */ jsxs2(
466
644
  "button",
467
645
  {
468
- className: DevProfiler_default.toggleBtn,
469
646
  onClick,
470
647
  title: "Dev Profiler (Ctrl+I)",
471
648
  style: getButtonStyle(pos, position),
472
- children: /* @__PURE__ */ jsx2("span", { className: DevProfiler_default.toggleFps, style: { color: fpsColor }, children: fps })
649
+ children: [
650
+ /* @__PURE__ */ jsx2("span", { style: { ...s.toggleDot, background: accentColor, boxShadow: `0 0 4px ${accentColor}` } }),
651
+ /* @__PURE__ */ jsx2("span", { style: s.toggleFps, children: fps }),
652
+ /* @__PURE__ */ jsx2("span", { style: s.toggleLabel, children: "fps" })
653
+ ]
473
654
  }
474
655
  ),
475
656
  document.body
@@ -477,7 +658,7 @@ function ToggleButton({
477
658
  }
478
659
 
479
660
  // src/DevProfiler.tsx
480
- import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
661
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
481
662
  var TOGGLE_EVENT = "devprofiler:toggle";
482
663
  var BOUND_KEY = "__devprofiler_bound";
483
664
  if (typeof window !== "undefined" && __DEV__ && !window[BOUND_KEY]) {
@@ -494,12 +675,13 @@ var activeInstances = /* @__PURE__ */ new Set();
494
675
  function DevProfiler({
495
676
  children,
496
677
  position = "bottom-left",
497
- id
678
+ id,
679
+ accentColor = "#6366f1"
498
680
  }) {
499
- const wrapperRef = useRef4(null);
681
+ const wrapperRef = useRef3(null);
500
682
  const [open, setOpen] = useState4(false);
501
683
  const toggle = useCallback3(() => setOpen((prev) => !prev), []);
502
- const instanceId = useRef4(id ?? `profiler-${++instanceCounter}`).current;
684
+ const instanceId = useRef3(id ?? `profiler-${++instanceCounter}`).current;
503
685
  useEffect4(() => {
504
686
  activeInstances.add(instanceId);
505
687
  return () => {
@@ -510,7 +692,7 @@ function DevProfiler({
510
692
  const renderRate = useRenderRate();
511
693
  const longTasks = useLongTasks(open);
512
694
  useRenderFlash(wrapperRef, open);
513
- const profilerData = useRef4({ ...INITIAL_PROFILER });
695
+ const profilerData = useRef3({ ...INITIAL_PROFILER });
514
696
  const onRender = useCallback3((_id, phase, actualDuration, baseDuration) => {
515
697
  profilerData.current = {
516
698
  phase,
@@ -532,9 +714,9 @@ function DevProfiler({
532
714
  return () => window.removeEventListener(TOGGLE_EVENT, handler);
533
715
  }, []);
534
716
  if (!__DEV__) return /* @__PURE__ */ jsx3(Fragment, { children });
535
- return /* @__PURE__ */ jsxs2("div", { ref: wrapperRef, className: DevProfiler_default.wrapper, children: [
717
+ return /* @__PURE__ */ jsxs3("div", { ref: wrapperRef, style: { display: "contents" }, children: [
536
718
  /* @__PURE__ */ jsx3(Profiler, { id: "DevProfiler", onRender, children }),
537
- !open && /* @__PURE__ */ jsx3(ToggleButton, { targetRef: wrapperRef, onClick: toggle, position }),
719
+ !open && /* @__PURE__ */ jsx3(ToggleButton, { targetRef: wrapperRef, onClick: toggle, position, accentColor }),
538
720
  open && /* @__PURE__ */ jsx3(
539
721
  DevStatsPanel,
540
722
  {
@@ -567,6 +749,12 @@ export {
567
749
  * @author Frederic Denis (billywild87) — https://github.com/billywild87
568
750
  * @license MIT
569
751
  */
752
+ /**
753
+ * @module react-dev-profiler
754
+ * @description Inline styles for the profiler UI — no external CSS needed.
755
+ * @author Frederic Denis (billywild87) — https://github.com/billywild87
756
+ * @license MIT
757
+ */
570
758
  /**
571
759
  * @module react-dev-profiler
572
760
  * @description Custom hooks that power the profiler's data collection.