@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 +21 -0
- package/README.md +183 -0
- package/dist/components/DebugOverlay/DebugOverlay.js +352 -0
- package/dist/index.d.ts +100 -0
- package/dist/index.js +24 -0
- package/package.json +47 -0
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
|
+
[](https://www.npmjs.com/package/@versini/ui-debug-overlay)
|
|
4
|
+
>)
|
|
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
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|