@versini/ui-debug-overlay 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Arno Versini
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # @versini/ui-debug-overlay
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@versini/ui-debug-overlay?style=flat-square)](https://www.npmjs.com/package/@versini/ui-debug-overlay)
4
+ ![npm package minimized gzipped size](<https://img.shields.io/bundlejs/size/%40versini%2Fui-debug-overlay?style=flat-square&label=size%20(gzip)>)
5
+
6
+ > Lightweight always‑on React debug overlay for inspecting live state and manual snapshots (great on mobile / PWA / hybrid shells where devtools are awkward).
7
+
8
+ `@versini/ui-debug-overlay` lets you:
9
+
10
+ - Automatically snapshot an `appState` object on referential changes
11
+ - Push manual, labeled, color‑coded snapshots from anywhere (via a hook)
12
+ - Target snapshots to specific overlay instances
13
+ - Copy a merged chronological timeline (optionally excluding manual snapshots)
14
+ - Clear everything in a click
15
+ - Position the panel with shorthands or explicit edges
16
+ - Keep bundle weight minimal (inline styles only, no CSS frameworks)
17
+
18
+ ## Features
19
+
20
+ - **⚡ Zero Config** – Drop in and pass `appState`
21
+ - **🔄 Auto State Capture** – Retains a rolling window (FIFO) of snapshots
22
+ - **📝 Manual Snapshots** – Arbitrary data logging with labels & colors
23
+ - **🎯 Targetable** – Route manual events to named overlays
24
+ - **🪄 Cycle‑Safe JSON** – Deterministic, circular reference tolerant stringify
25
+ - **📋 One‑Click Copy** – Chronological timeline, numbering stable regardless of UI order
26
+ - **🧹 Clear Control** – Reset buffer instantly
27
+ - **📐 Flexible Position** – Shorthands or `{ top/right/bottom/left }`
28
+ - **🔍 Order Toggle** – Show newest first while numbering still reflects true time
29
+ - **🪶 Lightweight** – Pure inline styling, TypeScript types
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ npm install @versini/ui-debug-overlay
35
+ ```
36
+
37
+ Peer deps: `react`, `react-dom`.
38
+
39
+ ## Quick Start
40
+
41
+ ```tsx
42
+ import { DebugOverlay } from "@versini/ui-debug-overlay";
43
+
44
+ export function App() {
45
+ const appState = { user: { id: 1, name: "Ada" }, online: true };
46
+ return (
47
+ <>
48
+ <YourRealApp />
49
+ <DebugOverlay appState={appState} />
50
+ </>
51
+ );
52
+ }
53
+ ```
54
+
55
+ ## Manual Snapshots
56
+
57
+ Use the hook to push ad‑hoc snapshots (successful responses, errors, timings, etc.).
58
+
59
+ ```tsx
60
+ import {
61
+ useDebugSnapshot,
62
+ LOG_GREEN,
63
+ LOG_RED
64
+ } from "@versini/ui-debug-overlay";
65
+
66
+ function SomeLogic() {
67
+ const debug = useDebugSnapshot();
68
+
69
+ async function run() {
70
+ debug("start", { ts: Date.now() });
71
+ try {
72
+ const data = await fetch("/api/data").then((r) => r.json());
73
+ debug("success", data, LOG_GREEN);
74
+ } catch (e) {
75
+ debug("error", { message: (e as Error).message }, LOG_RED);
76
+ }
77
+ }
78
+
79
+ return <button onClick={run}>Run</button>;
80
+ }
81
+ ```
82
+
83
+ ## Multiple / Targeted Overlays
84
+
85
+ ```tsx
86
+ const debug = useDebugSnapshot();
87
+
88
+ debug("metrics", { fps: 60 }, { targetOverlays: ["perf"] });
89
+
90
+ debug("state-change", someStateSlice); // broadcast to all overlays
91
+
92
+ return (
93
+ <>
94
+ <DebugOverlay overlayId="perf" position="left" title="Performance" />
95
+ <DebugOverlay
96
+ overlayId="main"
97
+ position="right"
98
+ title="Main State"
99
+ appState={rootState}
100
+ />
101
+ </>
102
+ );
103
+ ```
104
+
105
+ Rules:
106
+
107
+ - Omit `targetOverlays` (or empty array) ⇒ broadcast
108
+ - Non‑empty array ⇒ deliver only to overlays whose `overlayId` matches
109
+ - Buffered (pre‑mount) manual snapshots still respect targeting when overlays mount
110
+
111
+ ## Positioning
112
+
113
+ ```tsx
114
+ <DebugOverlay position="bottom-right" />
115
+ <DebugOverlay position="left" /> {/* alias of top-left */}
116
+ <DebugOverlay position={{ bottom: "8px", left: "8px" }} />
117
+ ```
118
+
119
+ Supported shorthands: `left`, `right`, `top`, `bottom`, `top-left`, `top-right`, `bottom-left`, `bottom-right`.
120
+
121
+ ## Copy Behavior
122
+
123
+ Pressing "Copy" produces a plain text timeline. Chronological numbering always starts at `#1` (oldest), even if UI order shows newest first. To exclude manual snapshots:
124
+
125
+ ```tsx
126
+ <DebugOverlay appState={state} includeManualInCopy={false} />
127
+ ```
128
+
129
+ ## Collapsing
130
+
131
+ ```tsx
132
+ <DebugOverlay initialCollapsed />
133
+ ```
134
+
135
+ ## Props
136
+
137
+ | Prop | Type | Default | Description |
138
+ | ---------------------------- | -------------------------- | ------------ | -------------------------------------------------------------------- | ---------------------------------------------------- |
139
+ | `appState` | `unknown` | – | Value to snapshot on reference change. Omit for manual‑only overlay. |
140
+ | `title` | `string` | `"AppState"` | Panel header. |
141
+ | `initialCollapsed` | `boolean` | `false` | Start minimized. |
142
+ | `overlayId` | `string` | `"default"` | Name used for targeted manual snapshots. |
143
+ | `position` | shorthand \| partial edges | – | Placement via shorthand or `{ top/right/bottom/left }`. |
144
+ | `maxBodyHeight` | `number | string` | `320` | Scroll region max height. |
145
+ | `indent` | `number` | `2` | JSON indent spaces. |
146
+ | `maxSnapshots` | `number` | `50` | Retained automatic state snapshot cap (FIFO). |
147
+ | `maxVisibleSnapshots` | `number` | `2` | Visible snapshot limit; remainder summarized. |
148
+ | `snapshotOrder` | `"asc" | "desc"` | `"desc"` | Visual ordering only. Numbering stays chronological. |
149
+ | `appendSnapshotCountInTitle` | `boolean` | `false` | Append live count to title. |
150
+ | `includeManualInCopy` | `boolean` | `true` | Include manual snapshots in copy output. |
151
+
152
+ ## Hook: `useDebugSnapshot()`
153
+
154
+ ```ts
155
+ const debug = useDebugSnapshot();
156
+
157
+ debug(
158
+ label: string,
159
+ data: unknown,
160
+ options?: LogColor | { color?: LogColor; targetOverlays?: string[] }
161
+ );
162
+ ```
163
+
164
+ Color constants: `LOG_YELLOW`, `LOG_GREEN`, `LOG_BLUE`, `LOG_MAGENTA`, `LOG_RED` (yellow is default fallback).
165
+
166
+ ## Tips
167
+
168
+ - Keep `maxVisibleSnapshots` small (1–3) on narrow/mobile viewports
169
+ - Derive / shape very large objects before passing to reduce noise & stringify cost
170
+ - Multiple overlays can focus on separate domains (state vs. performance vs. network)
171
+ - Use colors to visually cluster snapshot categories (success, error, timing)
172
+
173
+ ## Accessibility
174
+
175
+ Buttons include accessible labels; simple semantic markup keeps the overlay screen‑reader friendly while remaining visually unobtrusive. Inline styles avoid CSS collisions.
176
+
177
+ ## License
178
+
179
+ MIT © Arno Versini
180
+
181
+ ---
182
+
183
+ Open an issue for enhancements (redaction hooks, diff mode, retention strategies, pause/resume, etc.).
@@ -0,0 +1,352 @@
1
+ import { jsxs as L, jsx as g } from "react/jsx-runtime";
2
+ import { useCallback as _, useState as x, useRef as tt, useMemo as h, useEffect as G } from "react";
3
+ const nt = "LOG_YELLOW", st = "LOG_GREEN", at = "LOG_BLUE", lt = "LOG_MAGENTA", it = "LOG_RED", et = 200;
4
+ let F = 0;
5
+ const p = [], $ = /* @__PURE__ */ new Set();
6
+ function ct() {
7
+ p.length = 0, F = 0;
8
+ }
9
+ function U() {
10
+ return F++;
11
+ }
12
+ function N(s) {
13
+ const a = {
14
+ kind: "manual",
15
+ ts: Date.now(),
16
+ seq: U(),
17
+ label: s.label,
18
+ data: s.data,
19
+ color: s.color,
20
+ targetOverlays: s.targetOverlays && s.targetOverlays.length ? [...s.targetOverlays] : void 0
21
+ };
22
+ p.push(a), p.length > et && p.shift(), $.forEach((l) => l(a));
23
+ }
24
+ function dt() {
25
+ return _(
26
+ (s, a, l) => {
27
+ N(typeof l == "string" ? { label: s, data: a, color: l } : {
28
+ label: s,
29
+ data: a,
30
+ color: l?.color,
31
+ targetOverlays: l?.targetOverlays
32
+ });
33
+ },
34
+ []
35
+ );
36
+ }
37
+ function W(s, a = 2) {
38
+ const l = /* @__PURE__ */ new WeakSet(), c = (o) => {
39
+ if (!o || typeof o != "object" || o instanceof Date || o instanceof RegExp)
40
+ return o;
41
+ if (l.has(o))
42
+ return "[Circular]";
43
+ if (l.add(o), Array.isArray(o))
44
+ return o.map(c);
45
+ const y = {};
46
+ for (const d of Object.keys(o).sort())
47
+ y[d] = c(o[d]);
48
+ return y;
49
+ };
50
+ try {
51
+ return JSON.stringify(c(s), null, a);
52
+ } catch (o) {
53
+ return `<<unserializable: ${o.message}>>`;
54
+ }
55
+ }
56
+ const ut = ({
57
+ appState: s,
58
+ title: a = "AppState",
59
+ initialCollapsed: l = !1,
60
+ overlayId: c = "default",
61
+ position: o,
62
+ maxBodyHeight: y = 320,
63
+ indent: d = 2,
64
+ maxSnapshots: C = 50,
65
+ maxVisibleSnapshots: v = 2,
66
+ snapshotOrder: A = "desc",
67
+ appendSnapshotCountInTitle: Y = !1,
68
+ includeManualInCopy: R = !0
69
+ }) => {
70
+ const [b, I] = x(l), [T, D] = x(!1), m = tt(null), [j, q] = x([]), [B, z] = x(
71
+ () => (
72
+ // Apply same targeting filter to initial buffered snapshots as we do for
73
+ // live subscription events so targeted snapshots don't leak to unrelated overlays.
74
+ p.filter(
75
+ (t) => !t.targetOverlays || t.targetOverlays.length === 0 || t.targetOverlays.includes(c)
76
+ )
77
+ )
78
+ ), E = h(
79
+ () => s !== void 0 ? W(s, d) : null,
80
+ [s, d]
81
+ );
82
+ G(() => {
83
+ E != null && q(
84
+ (t) => [{ ts: Date.now(), json: E }, ...t].slice(0, C)
85
+ );
86
+ }, [E, C]), G(() => {
87
+ const t = (e) => {
88
+ (!e.targetOverlays || e.targetOverlays.length === 0 || e.targetOverlays.includes(c)) && z((r) => [...r, e]);
89
+ };
90
+ return $.add(t), () => {
91
+ $.delete(t);
92
+ };
93
+ }, [c]);
94
+ const i = h(() => {
95
+ const t = [...j].reverse().map((n) => ({
96
+ kind: "state",
97
+ ts: n.ts,
98
+ json: n.json,
99
+ seq: U()
100
+ })), e = B.map((n) => ({
101
+ kind: "manual",
102
+ ts: n.ts,
103
+ seq: n.seq,
104
+ label: n.label,
105
+ color: n.color,
106
+ json: W(n.data, d)
107
+ })), r = [...t, ...e];
108
+ return r.sort((n, f) => n.ts === f.ts ? n.seq - f.seq : n.ts - f.ts), r;
109
+ }, [j, B, d]), k = h(
110
+ () => A === "asc" ? i : [...i].reverse(),
111
+ [A, i]
112
+ ), P = _(() => {
113
+ const t = [];
114
+ t.push(`${a} (total snapshots: ${i.length})`), i.forEach((r, n) => {
115
+ if (r.kind === "manual" && !R)
116
+ return;
117
+ const S = new Date(r.ts).toLocaleTimeString(void 0, {
118
+ hour: "2-digit",
119
+ minute: "2-digit",
120
+ second: "2-digit",
121
+ hour12: !1
122
+ }), V = r.kind === "manual" && r.label ? ` [${r.label}]` : "";
123
+ t.push(`--- ${S} (#${n + 1})${V} ---`), t.push(r.json);
124
+ });
125
+ const e = t.join(`
126
+ `);
127
+ navigator?.clipboard?.writeText && navigator.clipboard.writeText(e).then(() => {
128
+ D(!0), setTimeout(() => D(!1), 1300);
129
+ });
130
+ }, [a, i, R]), H = _(() => {
131
+ q([]), z([]), p.length = 0;
132
+ }, []), M = h(() => {
133
+ if (!o)
134
+ return {};
135
+ if (typeof o == "string")
136
+ switch (o) {
137
+ case "left":
138
+ case "top-left":
139
+ return { top: "0", left: "0" };
140
+ case "right":
141
+ case "top-right":
142
+ return { top: "0", right: "0" };
143
+ case "bottom-left":
144
+ return { bottom: "0", left: "0" };
145
+ case "bottom-right":
146
+ return { bottom: "0", right: "0" };
147
+ case "bottom":
148
+ return { bottom: "0", right: "0" };
149
+ case "top":
150
+ default:
151
+ return { top: "0", right: "0" };
152
+ }
153
+ const t = {};
154
+ for (const e of ["top", "right", "bottom", "left"]) {
155
+ const r = o[e];
156
+ r !== void 0 && (t[e] = String(r));
157
+ }
158
+ return t;
159
+ }, [o]), u = h(() => {
160
+ const t = { ...M };
161
+ return !("top" in t) && !("bottom" in t) && (t.top = "0"), !("left" in t) && !("right" in t) && (t.right = "0"), t;
162
+ }, [M]);
163
+ G(() => {
164
+ const t = typeof window < "u" ? window.visualViewport : null;
165
+ if (!t)
166
+ return;
167
+ const e = () => {
168
+ if (m.current && (!("top" in u) && !("bottom" in u) && (m.current.style.top = `${t.offsetTop}px`), !("right" in u) && !("left" in u))) {
169
+ const r = Math.max(
170
+ 0,
171
+ Math.round(window.innerWidth - (t.offsetLeft + t.width))
172
+ );
173
+ m.current.style.right = `${r}px`;
174
+ }
175
+ };
176
+ return t.addEventListener("resize", e), t.addEventListener("scroll", e), e(), () => {
177
+ t.removeEventListener("resize", e), t.removeEventListener("scroll", e);
178
+ };
179
+ }, [u]);
180
+ const J = i.length, K = Y ? `${a} (${J})` : a, Q = { margin: 0, padding: 0 }, X = {
181
+ margin: 0,
182
+ padding: 0,
183
+ paddingLeft: 4
184
+ }, w = {
185
+ LOG_YELLOW: {
186
+ color: "#ffcf7f",
187
+ background: "rgba(255,200,0,0.08)",
188
+ border: "2px solid #cc9a00"
189
+ },
190
+ LOG_GREEN: {
191
+ color: "#b6ffb6",
192
+ background: "rgba(0,255,0,0.08)",
193
+ border: "2px solid #00aa00"
194
+ },
195
+ LOG_BLUE: {
196
+ color: "#b6d9ff",
197
+ background: "rgba(0,136,255,0.08)",
198
+ border: "2px solid #0066cc"
199
+ },
200
+ LOG_MAGENTA: {
201
+ color: "#ffb6ef",
202
+ background: "rgba(255,0,200,0.08)",
203
+ border: "2px solid #cc0088"
204
+ },
205
+ LOG_RED: {
206
+ color: "#ffb6b6",
207
+ background: "rgba(255,0,0,0.08)",
208
+ border: "2px solid #cc0000"
209
+ }
210
+ };
211
+ function Z(t) {
212
+ const e = t && w[t] ? w[t] : w.LOG_YELLOW;
213
+ return {
214
+ ...X,
215
+ color: e.color,
216
+ background: e.background,
217
+ borderLeft: e.border
218
+ };
219
+ }
220
+ return /* @__PURE__ */ L(
221
+ "div",
222
+ {
223
+ ref: m,
224
+ "aria-label": "Debug overlay",
225
+ style: {
226
+ position: "fixed",
227
+ fontFamily: "monospace",
228
+ zIndex: 9999,
229
+ fontSize: 12,
230
+ color: "#d1faff",
231
+ background: "rgba(0,0,0,0.78)",
232
+ border: "1px solid #055",
233
+ borderTop: "none",
234
+ borderRight: "none",
235
+ borderBottomLeftRadius: 6,
236
+ maxWidth: 360,
237
+ boxShadow: "0 2px 6px rgba(0,0,0,0.4)",
238
+ ...u
239
+ },
240
+ children: [
241
+ /* @__PURE__ */ L(
242
+ "div",
243
+ {
244
+ style: {
245
+ display: "flex",
246
+ alignItems: "center",
247
+ gap: 6,
248
+ padding: "4px 6px 4px 8px",
249
+ borderBottom: b ? "none" : "1px solid #044",
250
+ background: "#022",
251
+ borderBottomLeftRadius: b ? 6 : 0,
252
+ userSelect: "none"
253
+ },
254
+ children: [
255
+ /* @__PURE__ */ g("strong", { style: { fontWeight: 600, fontSize: 11 }, children: K }),
256
+ /* @__PURE__ */ L("div", { style: { marginLeft: "auto", display: "flex", gap: 4 }, children: [
257
+ /* @__PURE__ */ g(
258
+ "button",
259
+ {
260
+ type: "button",
261
+ onClick: () => I((t) => !t),
262
+ "aria-label": b ? "Expand debug overlay" : "Collapse debug overlay",
263
+ style: O,
264
+ children: b ? "+" : "−"
265
+ }
266
+ ),
267
+ /* @__PURE__ */ g(
268
+ "button",
269
+ {
270
+ type: "button",
271
+ onClick: P,
272
+ "aria-label": "Copy debug JSON",
273
+ style: {
274
+ ...O,
275
+ color: T ? "#0f0" : O.color
276
+ },
277
+ children: T ? "Copied" : "Copy"
278
+ }
279
+ ),
280
+ i.length > 1 && /* @__PURE__ */ g(
281
+ "button",
282
+ {
283
+ type: "button",
284
+ onClick: H,
285
+ "aria-label": "Clear stored snapshots",
286
+ style: O,
287
+ children: "Clear"
288
+ }
289
+ )
290
+ ] })
291
+ ]
292
+ }
293
+ ),
294
+ !b && /* @__PURE__ */ L(
295
+ "div",
296
+ {
297
+ style: {
298
+ margin: 0,
299
+ padding: "6px 8px 8px 8px",
300
+ maxHeight: y,
301
+ overflow: "auto",
302
+ lineHeight: 1.25,
303
+ fontSize: 11,
304
+ fontFamily: "monospace",
305
+ whiteSpace: "pre-wrap",
306
+ wordBreak: "break-word"
307
+ },
308
+ children: [
309
+ k.slice(0, v).map((t) => {
310
+ const e = i.findIndex((S) => S.seq === t.seq), n = new Date(t.ts).toLocaleTimeString(void 0, {
311
+ hour: "2-digit",
312
+ minute: "2-digit",
313
+ second: "2-digit",
314
+ hour12: !1
315
+ }), f = t.kind === "manual" && t.label ? ` [${t.label}]` : "";
316
+ return /* @__PURE__ */ g(
317
+ "pre",
318
+ {
319
+ style: t.kind === "manual" ? Z(t.color) : Q,
320
+ children: `--- ${n} (#${e + 1})${f} ---
321
+ ${t.json}`
322
+ },
323
+ `${t.kind}-${t.seq}`
324
+ );
325
+ }),
326
+ k.length > v && /* @__PURE__ */ g("div", { style: { opacity: 0.7, marginTop: 4 }, children: `(+${k.length - v} snapshots not shown)` })
327
+ ]
328
+ }
329
+ )
330
+ ]
331
+ }
332
+ );
333
+ }, O = {
334
+ background: "#033",
335
+ color: "#0ff",
336
+ border: "1px solid #055",
337
+ borderRadius: 4,
338
+ fontSize: 10,
339
+ padding: "2px 6px",
340
+ cursor: "pointer",
341
+ fontFamily: "inherit"
342
+ };
343
+ export {
344
+ ut as DebugOverlay,
345
+ at as LOG_BLUE,
346
+ st as LOG_GREEN,
347
+ lt as LOG_MAGENTA,
348
+ it as LOG_RED,
349
+ nt as LOG_YELLOW,
350
+ ct as __resetDebugOverlayTestState,
351
+ dt as useDebugSnapshot
352
+ };
@@ -0,0 +1,100 @@
1
+ import React from 'react';
2
+
3
+ type DebugOverlayProps = {
4
+ /**
5
+ * Optional application state to display. If omitted, only manual snapshots
6
+ * appear.
7
+ */
8
+ appState?: unknown;
9
+ /**
10
+ * Short label shown in the header. Defaults to "AppState".
11
+ */
12
+ title?: string;
13
+ /**
14
+ * Start collapsed (header only).
15
+ */
16
+ initialCollapsed?: boolean;
17
+ /**
18
+ * Identifier for manual snapshot targeting. Defaults to "default".
19
+ */
20
+ overlayId?: string;
21
+ /**
22
+ * Panel positioning.
23
+ *
24
+ * You can either provide a partial style object with any of the four edges
25
+ * (top/right/bottom/left) OR use a shorthand keyword. Shorthand avoids having
26
+ * to pass an object and also prevents the default (top/right) edges from being
27
+ * applied when the intent is a left anchor.
28
+ *
29
+ * Supported shorthands:
30
+ * - "left" (alias of "top-left")
31
+ * - "right" (alias of "top-right")
32
+ * - "top", "bottom"
33
+ * - "top-left", "top-right", "bottom-left", "bottom-right"
34
+ *
35
+ */
36
+ position?:
37
+ | Partial<Pick<CSSStyleDeclaration, "top" | "right" | "bottom" | "left">>
38
+ | "left"
39
+ | "right"
40
+ | "top"
41
+ | "bottom"
42
+ | "top-left"
43
+ | "top-right"
44
+ | "bottom-left"
45
+ | "bottom-right";
46
+ /**
47
+ * Maximum body height before scrolling (px number or CSS length e.g. "50svh").
48
+ */
49
+ maxBodyHeight?: number | string;
50
+ /**
51
+ * Pretty print spacing for JSON output (default 2).
52
+ */
53
+ indent?: number;
54
+ /**
55
+ * Maximum snapshots retained in memory when persist=true (FIFO). Default 50.
56
+ */
57
+ maxSnapshots?: number;
58
+ /**
59
+ * Maximum snapshots shown without expanding. Default 2.
60
+ */
61
+ maxVisibleSnapshots?: number;
62
+ /**
63
+ * Visual ordering of snapshots in the panel.
64
+ * - "desc" (default): newest snapshot at the top.
65
+ * - "asc": oldest snapshot at the top.
66
+ * Numbering ALWAYS reflects chronological order with #1 being the oldest
67
+ * snapshot regardless of display order.
68
+ */
69
+ snapshotOrder?: "asc" | "desc";
70
+ /**
71
+ * When true, appends the current snapshot count to the title (e.g. "AppState
72
+ * (5)").
73
+ */
74
+ appendSnapshotCountInTitle?: boolean;
75
+ /**
76
+ * Include manual snapshots when copying all snapshots.
77
+ */
78
+ includeManualInCopy?: boolean;
79
+ };
80
+
81
+ /**
82
+ * Manual snapshot input now requires an explicit label for clarity; we remove
83
+ * the previous flexible single-argument signature to keep debug calls
84
+ * unambiguous (always: label, data).
85
+ */
86
+ declare const LOG_YELLOW: "LOG_YELLOW";
87
+ declare const LOG_GREEN: "LOG_GREEN";
88
+ declare const LOG_BLUE: "LOG_BLUE";
89
+ declare const LOG_MAGENTA: "LOG_MAGENTA";
90
+ declare const LOG_RED: "LOG_RED";
91
+ type LogColor = typeof LOG_YELLOW | typeof LOG_GREEN | typeof LOG_BLUE | typeof LOG_MAGENTA | typeof LOG_RED;
92
+ declare function __resetDebugOverlayTestState(): void;
93
+ interface DebugFnOptions {
94
+ color?: LogColor;
95
+ targetOverlays?: string[];
96
+ }
97
+ declare function useDebugSnapshot(): (label: string, data: unknown, options?: LogColor | DebugFnOptions) => void;
98
+ declare const DebugOverlay: React.FC<DebugOverlayProps>;
99
+
100
+ export { type DebugFnOptions, DebugOverlay, type DebugOverlayProps, LOG_BLUE, LOG_GREEN, LOG_MAGENTA, LOG_RED, LOG_YELLOW, type LogColor, __resetDebugOverlayTestState, useDebugSnapshot };
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ import { DebugOverlay as t, LOG_BLUE as i, LOG_GREEN as o, LOG_MAGENTA as r, LOG_RED as L, LOG_YELLOW as O, __resetDebugOverlayTestState as s, useDebugSnapshot as G } from "./components/DebugOverlay/DebugOverlay.js";
2
+ /*!
3
+ @versini/ui-debug-overlay v1.1.0
4
+ © 2025 gizmette.com
5
+ */
6
+ try {
7
+ window.__VERSINI_UI_DEBUG_OVERLAY__ || (window.__VERSINI_UI_DEBUG_OVERLAY__ = {
8
+ version: "1.1.0",
9
+ buildTime: "09/13/2025 11:11 PM EDT",
10
+ homepage: "https://github.com/aversini/ui-components",
11
+ license: "MIT"
12
+ });
13
+ } catch {
14
+ }
15
+ export {
16
+ t as DebugOverlay,
17
+ i as LOG_BLUE,
18
+ o as LOG_GREEN,
19
+ r as LOG_MAGENTA,
20
+ L as LOG_RED,
21
+ O as LOG_YELLOW,
22
+ s as __resetDebugOverlayTestState,
23
+ G as useDebugSnapshot
24
+ };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@versini/ui-debug-overlay",
3
+ "version": "1.1.0",
4
+ "license": "MIT",
5
+ "author": "Arno Versini",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "homepage": "https://github.com/aversini/ui-components",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git@github.com:aversini/ui-components.git"
13
+ },
14
+ "type": "module",
15
+ "main": "dist/index.js",
16
+ "types": "dist/index.d.ts",
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "build:check": "tsc",
23
+ "build:js": "vite build",
24
+ "build:types": "tsup",
25
+ "build": "npm-run-all --serial clean build:check build:js build:types",
26
+ "clean": "rimraf dist tmp",
27
+ "dev:js": "vite build --watch --mode development",
28
+ "dev:types": "tsup --watch src",
29
+ "dev": "npm-run-all clean --parallel dev:js dev:types",
30
+ "lint": "biome lint src",
31
+ "lint:fix": "biome check src --write --no-errors-on-unmatched",
32
+ "prettier": "biome check --write --no-errors-on-unmatched",
33
+ "start": "static-server dist --port 5173",
34
+ "test:coverage:ui": "vitest --coverage --ui",
35
+ "test:coverage": "vitest run --coverage",
36
+ "test:watch": "vitest",
37
+ "test": "vitest run"
38
+ },
39
+ "peerDependencies": {
40
+ "react": "^19.1.0",
41
+ "react-dom": "^19.1.0"
42
+ },
43
+ "devDependencies": {
44
+ "@testing-library/jest-dom": "6.8.0"
45
+ },
46
+ "gitHead": "6fef25217acc22bda8276b58e40d776a68af5ed9"
47
+ }