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.cjs CHANGED
@@ -27,9 +27,6 @@ module.exports = __toCommonJS(index_exports);
27
27
  // src/DevProfiler.tsx
28
28
  var import_react4 = require("react");
29
29
 
30
- // src/DevProfiler.module.css
31
- var DevProfiler_default = {};
32
-
33
30
  // src/types.ts
34
31
  var HISTORY_SIZE = 60;
35
32
  var INITIAL_PROFILER = {
@@ -63,32 +60,228 @@ var __DEV__ = typeof process !== "undefined" ? process.env.NODE_ENV !== "product
63
60
 
64
61
  // src/hooks.ts
65
62
  var import_react = require("react");
63
+
64
+ // src/styles.ts
65
+ var s = {
66
+ wrapper: {
67
+ display: "contents"
68
+ },
69
+ panel: {
70
+ position: "fixed",
71
+ zIndex: 99999,
72
+ background: "rgba(8, 8, 8, 0.95)",
73
+ border: "1px solid #222",
74
+ borderRadius: 10,
75
+ padding: "10px 14px",
76
+ minWidth: 220,
77
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.7)",
78
+ fontFamily: "'SF Mono', 'Fira Code', monospace",
79
+ backdropFilter: "blur(12px)",
80
+ userSelect: "none",
81
+ color: "#ccc",
82
+ fontSize: 11,
83
+ cursor: "grab",
84
+ touchAction: "none"
85
+ },
86
+ panelHeader: {
87
+ display: "flex",
88
+ justifyContent: "space-between",
89
+ alignItems: "center",
90
+ marginBottom: 8
91
+ },
92
+ panelTitle: {
93
+ color: "#666",
94
+ fontSize: 9,
95
+ fontWeight: 700,
96
+ letterSpacing: 1.5,
97
+ textTransform: "uppercase",
98
+ display: "flex",
99
+ alignItems: "center",
100
+ gap: 6
101
+ },
102
+ instanceBadge: {
103
+ background: "#222",
104
+ color: "#888",
105
+ fontSize: 8,
106
+ padding: "1px 5px",
107
+ borderRadius: 4,
108
+ letterSpacing: 0.5,
109
+ textTransform: "none"
110
+ },
111
+ headerActions: {
112
+ display: "flex",
113
+ alignItems: "center",
114
+ gap: 8
115
+ },
116
+ iconBtn: {
117
+ background: "none",
118
+ border: "none",
119
+ color: "#444",
120
+ cursor: "pointer",
121
+ padding: 4,
122
+ margin: -4,
123
+ lineHeight: 1,
124
+ display: "flex",
125
+ alignItems: "center",
126
+ transition: "color 0.15s"
127
+ },
128
+ iconBtnActive: {
129
+ color: "#4ade80"
130
+ },
131
+ closeBtn: {
132
+ background: "none",
133
+ border: "none",
134
+ color: "#444",
135
+ cursor: "pointer",
136
+ fontSize: 13,
137
+ padding: 4,
138
+ margin: -4,
139
+ lineHeight: 1
140
+ },
141
+ body: {
142
+ display: "flex",
143
+ flexDirection: "column",
144
+ gap: 5
145
+ },
146
+ section: {
147
+ color: "#444",
148
+ fontSize: 8,
149
+ fontWeight: 600,
150
+ letterSpacing: 1,
151
+ textTransform: "uppercase",
152
+ marginTop: 2
153
+ },
154
+ separator: {
155
+ height: 1,
156
+ background: "#1a1a1a",
157
+ margin: "2px 0"
158
+ },
159
+ row: {
160
+ display: "flex",
161
+ justifyContent: "space-between",
162
+ alignItems: "center",
163
+ padding: "1px 0"
164
+ },
165
+ rowLabel: {
166
+ color: "#555",
167
+ fontSize: 10
168
+ },
169
+ rowValue: {
170
+ fontSize: 11,
171
+ fontWeight: 600
172
+ },
173
+ miniRow: {
174
+ display: "flex",
175
+ justifyContent: "space-between",
176
+ color: "#444",
177
+ fontSize: 9,
178
+ marginTop: -2,
179
+ marginBottom: 2
180
+ },
181
+ graphWrap: {
182
+ marginTop: 4
183
+ },
184
+ toggleBtn: {
185
+ position: "fixed",
186
+ zIndex: 99998,
187
+ height: 26,
188
+ borderRadius: 13,
189
+ border: "1px solid rgba(255, 255, 255, 0.08)",
190
+ background: "rgba(28, 28, 30, 0.92)",
191
+ cursor: "pointer",
192
+ display: "flex",
193
+ alignItems: "center",
194
+ gap: 6,
195
+ padding: "0 10px 0 8px",
196
+ backdropFilter: "blur(20px)",
197
+ WebkitBackdropFilter: "blur(20px)",
198
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.4), inset 0 0.5px 0 rgba(255, 255, 255, 0.06)"
199
+ },
200
+ toggleDot: {
201
+ width: 6,
202
+ height: 6,
203
+ borderRadius: "50%",
204
+ flexShrink: 0
205
+ },
206
+ toggleFps: {
207
+ fontFamily: "'SF Mono', 'Fira Code', monospace",
208
+ fontSize: 11,
209
+ fontWeight: 600,
210
+ color: "rgba(255, 255, 255, 0.85)",
211
+ letterSpacing: -0.3,
212
+ lineHeight: 1
213
+ },
214
+ toggleLabel: {
215
+ fontFamily: "'SF Mono', 'Fira Code', monospace",
216
+ fontSize: 9,
217
+ fontWeight: 500,
218
+ color: "rgba(255, 255, 255, 0.35)",
219
+ letterSpacing: 0,
220
+ textTransform: "lowercase"
221
+ },
222
+ footer: {
223
+ marginTop: 8,
224
+ color: "#333",
225
+ fontSize: 8,
226
+ textAlign: "center"
227
+ }
228
+ };
229
+ var FLASH_OUTLINE = "2px solid rgba(99, 102, 241, 0.8)";
230
+
231
+ // src/hooks.ts
232
+ function getEffectiveRect(el) {
233
+ const rect = el.getBoundingClientRect();
234
+ if (rect.width > 0 || rect.height > 0) return rect;
235
+ const children = el.children;
236
+ if (children.length === 0) return rect;
237
+ let top = Infinity, left = Infinity, bottom = -Infinity, right = -Infinity;
238
+ for (let i = 0; i < children.length; i++) {
239
+ const cr = children[i].getBoundingClientRect();
240
+ if (cr.width === 0 && cr.height === 0) continue;
241
+ top = Math.min(top, cr.top);
242
+ left = Math.min(left, cr.left);
243
+ bottom = Math.max(bottom, cr.bottom);
244
+ right = Math.max(right, cr.right);
245
+ }
246
+ if (top === Infinity) return rect;
247
+ return new DOMRect(left, top, right - left, bottom - top);
248
+ }
249
+ function getObservableChildren(el) {
250
+ const result = [];
251
+ for (let i = 0; i < el.children.length; i++) {
252
+ const child = el.children[i];
253
+ const r = child.getBoundingClientRect();
254
+ if (r.width > 0 || r.height > 0) result.push(child);
255
+ }
256
+ return result;
257
+ }
66
258
  function useAnchorPosition(ref, position = "bottom-left") {
67
259
  const [pos, setPos] = (0, import_react.useState)({ top: 0, left: 0 });
68
260
  (0, import_react.useEffect)(() => {
69
261
  if (!ref.current) return;
262
+ const el = ref.current;
70
263
  const update = () => {
71
- if (!ref.current) return;
72
- const rect = ref.current.getBoundingClientRect();
73
- const margin = 8;
264
+ const rect = getEffectiveRect(el);
74
265
  switch (position) {
75
266
  case "top-left":
76
- setPos({ top: rect.top + margin, left: rect.left + margin });
267
+ setPos({ top: rect.top, left: rect.left });
77
268
  break;
78
269
  case "top-right":
79
- setPos({ top: rect.top + margin, left: rect.right - margin });
270
+ setPos({ top: rect.top, left: rect.right });
80
271
  break;
81
272
  case "bottom-right":
82
- setPos({ top: rect.bottom - margin, left: rect.right - margin });
273
+ setPos({ top: rect.bottom, left: rect.right });
83
274
  break;
84
275
  case "bottom-left":
85
276
  default:
86
- setPos({ top: rect.bottom - margin, left: rect.left + margin });
277
+ setPos({ top: rect.bottom, left: rect.left });
87
278
  break;
88
279
  }
89
280
  };
281
+ update();
90
282
  const observer = new ResizeObserver(update);
91
- observer.observe(ref.current);
283
+ observer.observe(el);
284
+ for (const child of getObservableChildren(el)) observer.observe(child);
92
285
  observer.observe(document.documentElement);
93
286
  return () => observer.disconnect();
94
287
  }, [ref, position]);
@@ -99,9 +292,10 @@ function useDraggable() {
99
292
  const dragging = (0, import_react.useRef)(false);
100
293
  const start = (0, import_react.useRef)({ x: 0, y: 0 });
101
294
  const onPointerDown = (0, import_react.useCallback)((e) => {
295
+ if (e.target.closest("button, a, [data-no-drag]")) return;
102
296
  dragging.current = true;
103
297
  start.current = { x: e.clientX - offset.x, y: e.clientY - offset.y };
104
- e.target.setPointerCapture(e.pointerId);
298
+ e.currentTarget.setPointerCapture(e.pointerId);
105
299
  }, [offset]);
106
300
  const onPointerMove = (0, import_react.useCallback)((e) => {
107
301
  if (!dragging.current) return;
@@ -121,21 +315,13 @@ function useDomTracker(wrapperRef, enabled) {
121
315
  const dirty = (0, import_react.useRef)(true);
122
316
  (0, import_react.useEffect)(() => {
123
317
  if (!enabled || !wrapperRef.current) return;
124
- const observer = new MutationObserver((records) => {
125
- let realMutation = false;
126
- for (const record of records) {
127
- if (record.type === "attributes" && record.attributeName === "class") continue;
128
- realMutation = true;
129
- }
130
- if (realMutation) {
131
- mutations.current++;
132
- dirty.current = true;
133
- }
318
+ const observer = new MutationObserver(() => {
319
+ mutations.current++;
320
+ dirty.current = true;
134
321
  });
135
322
  observer.observe(wrapperRef.current, {
136
323
  childList: true,
137
324
  subtree: true,
138
- attributes: true,
139
325
  attributeFilter: ["style"]
140
326
  });
141
327
  return () => observer.disconnect();
@@ -176,14 +362,19 @@ function useRenderFlash(wrapperRef, open) {
176
362
  const mutationCount = (0, import_react.useRef)(0);
177
363
  (0, import_react.useEffect)(() => {
178
364
  if (!open || !wrapperRef.current) return;
365
+ const wrapper = wrapperRef.current;
179
366
  const observer = new MutationObserver(() => {
180
367
  mutationCount.current++;
181
- if (!wrapperRef.current || mutationCount.current <= 1) return;
182
- const el = wrapperRef.current;
183
- el.classList.add(DevProfiler_default.flash);
184
- setTimeout(() => el.classList.remove(DevProfiler_default.flash), 150);
368
+ if (mutationCount.current <= 1) return;
369
+ const target = getObservableChildren(wrapper)[0] ?? wrapper;
370
+ target.style.outline = FLASH_OUTLINE;
371
+ target.style.outlineOffset = "-2px";
372
+ setTimeout(() => {
373
+ target.style.outline = "";
374
+ target.style.outlineOffset = "";
375
+ }, 150);
185
376
  });
186
- observer.observe(wrapperRef.current, { childList: true, subtree: true });
377
+ observer.observe(wrapper, { childList: true, subtree: true });
187
378
  return () => observer.disconnect();
188
379
  }, [wrapperRef, open]);
189
380
  }
@@ -215,7 +406,7 @@ function FrameTimeGraph({ history }) {
215
406
  const w = 140;
216
407
  const h = 32;
217
408
  const barW = Math.max(1, w / HISTORY_SIZE - 0.5);
218
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: DevProfiler_default.graphWrap, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: w, height: h, style: { display: "block" }, children: [
409
+ 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: [
219
410
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { width: w, height: h, rx: 3, fill: "#111" }),
220
411
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
221
412
  "line",
@@ -248,27 +439,29 @@ function FrameTimeGraph({ history }) {
248
439
  ] }) });
249
440
  }
250
441
  function StatRow({ label, value, sub, color = "#4ade80" }) {
251
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: DevProfiler_default.row, children: [
252
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: DevProfiler_default.rowLabel, children: label }),
442
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.row, children: [
443
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.rowLabel, children: label }),
253
444
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
254
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: DevProfiler_default.rowValue, style: { color }, children: value }),
445
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...s.rowValue, color }, children: value }),
255
446
  sub && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#444", fontSize: 9, marginLeft: 4 }, children: sub })
256
447
  ] })
257
448
  ] });
258
449
  }
450
+ var GAP = 8;
259
451
  function getPanelStyle(pos, offset, position) {
260
452
  const style = {
453
+ ...s.panel,
261
454
  transform: `translate(${offset.x}px, ${offset.y}px)`
262
455
  };
263
456
  if (position.startsWith("bottom")) {
264
- style.bottom = window.innerHeight - pos.top;
457
+ style.bottom = window.innerHeight - pos.top + GAP;
265
458
  } else {
266
- style.top = pos.top;
459
+ style.top = pos.top + GAP;
267
460
  }
268
461
  if (position.endsWith("right")) {
269
- style.right = window.innerWidth - pos.left;
462
+ style.right = window.innerWidth - pos.left + GAP;
270
463
  } else {
271
- style.left = pos.left;
464
+ style.left = pos.left + GAP;
272
465
  }
273
466
  return style;
274
467
  }
@@ -310,7 +503,8 @@ function DevStatsPanel({
310
503
  allFrameTimes.current.push(avgFrameTime);
311
504
  const sorted = [...allFrameTimes.current].sort((a, b) => a - b);
312
505
  const el = targetRef.current;
313
- const dims = el ? `${el.offsetWidth} x ${el.offsetHeight}` : "\u2013";
506
+ const r = el ? getEffectiveRect(el) : null;
507
+ const dims = r ? `${Math.round(r.width)} x ${Math.round(r.height)}` : "\u2013";
314
508
  const perf = performance;
315
509
  const mem = perf.memory ? Math.round(perf.memory.usedJSHeapSize / 1024 / 1024) : 0;
316
510
  setStats({
@@ -345,23 +539,17 @@ function DevStatsPanel({
345
539
  const handleExport = (0, import_react2.useCallback)(() => {
346
540
  const payload = { timestamp: (/* @__PURE__ */ new Date()).toISOString(), ...stats };
347
541
  const json = JSON.stringify(payload, null, 2);
348
- try {
349
- navigator.clipboard.writeText(json).then(() => {
350
- setExported(true);
351
- setTimeout(() => setExported(false), 1200);
352
- });
353
- } catch {
354
- const ta = document.createElement("textarea");
355
- ta.value = json;
356
- ta.style.position = "fixed";
357
- ta.style.opacity = "0";
358
- document.body.appendChild(ta);
359
- ta.select();
360
- document.execCommand("copy");
361
- document.body.removeChild(ta);
362
- setExported(true);
363
- setTimeout(() => setExported(false), 1200);
364
- }
542
+ const blob = new Blob([json], { type: "application/json" });
543
+ const url = URL.createObjectURL(blob);
544
+ const a = document.createElement("a");
545
+ a.href = url;
546
+ a.download = `devprofiler-${Date.now()}.json`;
547
+ document.body.appendChild(a);
548
+ a.click();
549
+ document.body.removeChild(a);
550
+ URL.revokeObjectURL(url);
551
+ setExported(true);
552
+ setTimeout(() => setExported(false), 1200);
365
553
  }, [stats]);
366
554
  const ftColor = stats.frameTime > 33 ? "#ef4444" : stats.frameTime > 16.67 ? "#f59e0b" : "#4ade80";
367
555
  const rpsColor = stats.rendersPerSecond > 30 ? "#ef4444" : stats.rendersPerSecond > 10 ? "#f59e0b" : "#4ade80";
@@ -369,42 +557,33 @@ function DevStatsPanel({
369
557
  const fps = stats.frameTime > 0 ? Math.round(1e3 / stats.frameTime) : 0;
370
558
  const memoGain = stats.profiler.baseDuration > 0 ? Math.round((1 - stats.profiler.actualDuration / stats.profiler.baseDuration) * 100) : 0;
371
559
  const p99Color = stats.frameTimeP99 > 33 ? "#ef4444" : stats.frameTimeP99 > 16.67 ? "#f59e0b" : "#4ade80";
560
+ const exportStyle = exported ? { ...s.iconBtn, ...s.iconBtnActive } : s.iconBtn;
372
561
  return (0, import_react_dom.createPortal)(
373
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: DevProfiler_default.panel, style: getPanelStyle(pos, offset, position), children: [
374
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
375
- "div",
376
- {
377
- className: DevProfiler_default.panelHeader,
378
- ...dragHandlers,
379
- children: [
380
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: DevProfiler_default.panelTitle, children: [
381
- "Dev Profiler",
382
- instanceCount > 1 && instanceId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: DevProfiler_default.instanceBadge, children: instanceId })
383
- ] }),
384
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: DevProfiler_default.headerActions, children: [
385
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
386
- "button",
387
- {
388
- className: `${DevProfiler_default.exportBtn} ${exported ? DevProfiler_default.exportBtnActive : ""}`,
389
- onClick: handleExport,
390
- title: exported ? "Copied!" : "Copy stats to clipboard",
391
- children: exported ? /* @__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: "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__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "currentColor", children: [
392
- /* @__PURE__ */ (0, import_jsx_runtime.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" }),
393
- /* @__PURE__ */ (0, import_jsx_runtime.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" })
394
- ] })
395
- }
396
- ),
397
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: DevProfiler_default.resetBtn, 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" }) }) }),
398
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: DevProfiler_default.closeBtn, onClick: onClose, children: "\u2715" })
399
- ] })
400
- ]
401
- }
402
- ),
403
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: DevProfiler_default.body, children: [
404
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: DevProfiler_default.section, children: "Rendering" }),
562
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: getPanelStyle(pos, offset, position), ...dragHandlers, children: [
563
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.panelHeader, children: [
564
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: s.panelTitle, children: [
565
+ "Dev Profiler",
566
+ instanceCount > 1 && instanceId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.instanceBadge, children: instanceId })
567
+ ] }),
568
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.headerActions, children: [
569
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
570
+ "button",
571
+ {
572
+ style: exportStyle,
573
+ onClick: handleExport,
574
+ title: exported ? "Exported!" : "Export stats as JSON",
575
+ 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" }) })
576
+ }
577
+ ),
578
+ /* @__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" }) }) }),
579
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: s.closeBtn, onClick: onClose, children: "\u2715" })
580
+ ] })
581
+ ] }),
582
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.body, children: [
583
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.section, children: "Rendering" }),
405
584
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Frame time", value: `${stats.frameTime.toFixed(1)}ms`, sub: `${fps} fps`, color: ftColor }),
406
585
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FrameTimeGraph, { history: stats.frameTimeHistory }),
407
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: DevProfiler_default.miniRow, children: [
586
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.miniRow, children: [
408
587
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
409
588
  "min ",
410
589
  stats.frameTimeMin.toFixed(1)
@@ -420,23 +599,23 @@ function DevStatsPanel({
420
599
  ] }),
421
600
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Renders/s", value: String(stats.rendersPerSecond), color: rpsColor }),
422
601
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Long tasks", value: String(stats.longTasks), color: stats.longTasks > 0 ? "#f59e0b" : "#4ade80" }),
423
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: DevProfiler_default.separator }),
424
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: DevProfiler_default.section, children: "React Profiler" }),
602
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.separator }),
603
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.section, children: "React Profiler" }),
425
604
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Phase", value: stats.profiler.phase, color: "#888" }),
426
605
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Render", value: `${stats.profiler.actualDuration.toFixed(2)}ms`, color: actualColor }),
427
606
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Base (no memo)", value: `${stats.profiler.baseDuration.toFixed(2)}ms`, color: "#888" }),
428
607
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Memo gain", value: `${memoGain}%`, color: memoGain > 50 ? "#4ade80" : memoGain > 20 ? "#f59e0b" : "#ef4444" }),
429
608
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Commits", value: String(stats.profiler.commitCount) }),
430
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: DevProfiler_default.separator }),
431
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: DevProfiler_default.section, children: "DOM" }),
609
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.separator }),
610
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.section, children: "DOM" }),
432
611
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Nodes", value: stats.domNodes.toLocaleString() }),
433
612
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Mutations", value: String(stats.domMutations) }),
434
613
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "Size", value: stats.dimensions, color: "#888" }),
435
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: DevProfiler_default.separator }),
436
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: DevProfiler_default.section, children: "Memory" }),
614
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.separator }),
615
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.section, children: "Memory" }),
437
616
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatRow, { label: "JS Heap", value: stats.memory > 0 ? `${stats.memory} MB` : "N/A" })
438
617
  ] }),
439
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: DevProfiler_default.footer, children: "Ctrl+I to toggle" })
618
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: s.footer, children: "Ctrl+I to toggle" })
440
619
  ] }),
441
620
  document.body
442
621
  );
@@ -446,35 +625,35 @@ function DevStatsPanel({
446
625
  var import_react3 = require("react");
447
626
  var import_react_dom2 = require("react-dom");
448
627
  var import_jsx_runtime2 = require("react/jsx-runtime");
628
+ var GAP2 = 8;
449
629
  function getButtonStyle(pos, position) {
450
- const style = {};
630
+ const style = { ...s.toggleBtn };
451
631
  if (position.startsWith("bottom")) {
452
- style.bottom = window.innerHeight - pos.top + 8;
632
+ style.bottom = window.innerHeight - pos.top + GAP2;
453
633
  } else {
454
- style.top = pos.top + 8;
634
+ style.top = pos.top + GAP2;
455
635
  }
456
636
  if (position.endsWith("right")) {
457
- style.right = window.innerWidth - pos.left;
637
+ style.right = window.innerWidth - pos.left + GAP2;
458
638
  } else {
459
- style.left = pos.left;
639
+ style.left = pos.left + GAP2;
460
640
  }
461
641
  return style;
462
642
  }
463
643
  function ToggleButton({
464
644
  targetRef,
465
645
  onClick,
466
- position = "bottom-left"
646
+ position = "bottom-left",
647
+ accentColor = "#6366f1"
467
648
  }) {
468
649
  const pos = useAnchorPosition(targetRef, position);
469
650
  const [fps, setFps] = (0, import_react3.useState)(0);
470
- const lastFrame = (0, import_react3.useRef)(performance.now());
471
651
  (0, import_react3.useEffect)(() => {
472
652
  let animId;
473
653
  let count = 0;
474
654
  let lastSecond = performance.now();
475
655
  const tick = () => {
476
656
  const now = performance.now();
477
- lastFrame.current = now;
478
657
  count++;
479
658
  if (now - lastSecond >= 1e3) {
480
659
  setFps(count);
@@ -486,16 +665,18 @@ function ToggleButton({
486
665
  animId = requestAnimationFrame(tick);
487
666
  return () => cancelAnimationFrame(animId);
488
667
  }, []);
489
- const fpsColor = fps < 30 ? "#ef4444" : fps < 55 ? "#f59e0b" : "#4ade80";
490
668
  return (0, import_react_dom2.createPortal)(
491
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
669
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
492
670
  "button",
493
671
  {
494
- className: DevProfiler_default.toggleBtn,
495
672
  onClick,
496
673
  title: "Dev Profiler (Ctrl+I)",
497
674
  style: getButtonStyle(pos, position),
498
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: DevProfiler_default.toggleFps, style: { color: fpsColor }, children: fps })
675
+ children: [
676
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { ...s.toggleDot, background: accentColor, boxShadow: `0 0 4px ${accentColor}` } }),
677
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: s.toggleFps, children: fps }),
678
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: s.toggleLabel, children: "fps" })
679
+ ]
499
680
  }
500
681
  ),
501
682
  document.body
@@ -520,7 +701,8 @@ var activeInstances = /* @__PURE__ */ new Set();
520
701
  function DevProfiler({
521
702
  children,
522
703
  position = "bottom-left",
523
- id
704
+ id,
705
+ accentColor = "#6366f1"
524
706
  }) {
525
707
  const wrapperRef = (0, import_react4.useRef)(null);
526
708
  const [open, setOpen] = (0, import_react4.useState)(false);
@@ -558,9 +740,9 @@ function DevProfiler({
558
740
  return () => window.removeEventListener(TOGGLE_EVENT, handler);
559
741
  }, []);
560
742
  if (!__DEV__) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children });
561
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { ref: wrapperRef, className: DevProfiler_default.wrapper, children: [
743
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { ref: wrapperRef, style: { display: "contents" }, children: [
562
744
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Profiler, { id: "DevProfiler", onRender, children }),
563
- !open && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ToggleButton, { targetRef: wrapperRef, onClick: toggle, position }),
745
+ !open && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ToggleButton, { targetRef: wrapperRef, onClick: toggle, position, accentColor }),
564
746
  open && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
565
747
  DevStatsPanel,
566
748
  {
@@ -594,6 +776,12 @@ function DevProfiler({
594
776
  * @author Frederic Denis (billywild87) — https://github.com/billywild87
595
777
  * @license MIT
596
778
  */
779
+ /**
780
+ * @module react-dev-profiler
781
+ * @description Inline styles for the profiler UI — no external CSS needed.
782
+ * @author Frederic Denis (billywild87) — https://github.com/billywild87
783
+ * @license MIT
784
+ */
597
785
  /**
598
786
  * @module react-dev-profiler
599
787
  * @description Custom hooks that power the profiler's data collection.