@atolljs/devtools 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +408 -71
- package/app/api.js +68 -0
- package/app/fw-logos.js +14 -0
- package/app/index.html +161 -2
- package/app/main.js +897 -222
- package/app/panels/actions.js +324 -0
- package/app/panels/audit-rules.js +728 -0
- package/app/panels/audits.js +438 -0
- package/app/panels/inspect-island.js +351 -0
- package/app/panels/memory-values.js +287 -0
- package/app/panels/performance.js +304 -0
- package/app/panels/reactivity.js +271 -0
- package/app/panels/recorder.js +344 -0
- package/app/panels/recording-format.js +166 -0
- package/app/panels/route.d.ts +44 -0
- package/app/panels/route.js +264 -0
- package/app/panels/shell.js +789 -0
- package/app/panels/toast.js +38 -0
- package/dist/bin.js +704 -5
- package/dist/chunk-57NGHQUL.js +344 -0
- package/dist/chunk-6E54FXKS.js +495 -0
- package/dist/chunk-WIL2P7HG.js +661 -0
- package/dist/client.d.ts +7 -0
- package/dist/control.d.ts +4 -0
- package/dist/index.js +2 -1
- package/dist/node.js +2 -1
- package/dist/nodeWs.d.ts +1 -0
- package/dist/otel.d.ts +35 -0
- package/dist/otel.js +87 -0
- package/dist/otelMap.d.ts +232 -0
- package/dist/otelTransport.d.ts +69 -0
- package/dist/overlay.d.ts +16 -2
- package/dist/probes.d.ts +7 -0
- package/dist/protocol.d.ts +46 -4
- package/dist/server.d.ts +19 -0
- package/dist/server.js +61 -3
- package/package.json +6 -2
- package/src/bin.ts +27 -2
- package/src/broadcast.ts +6 -0
- package/src/client.ts +26 -2
- package/src/control.ts +51 -0
- package/src/nodeWs.ts +7 -1
- package/src/otel.ts +140 -0
- package/src/otelMap.ts +661 -0
- package/src/otelTransport.ts +286 -0
- package/src/overlay.ts +239 -47
- package/src/probes.ts +155 -0
- package/src/protocol.ts +52 -4
- package/src/server.ts +83 -3
- package/dist/chunk-4N3NBFL3.js +0 -474
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import {
|
|
2
|
+
installFetchProbe,
|
|
3
|
+
installJankProbe,
|
|
4
|
+
installMemoryProbe,
|
|
5
|
+
runDevtoolsCommand,
|
|
6
|
+
setDevtoolsSink
|
|
7
|
+
} from "./chunk-57NGHQUL.js";
|
|
8
|
+
|
|
9
|
+
// src/control.ts
|
|
10
|
+
var jsonSafe = (v) => {
|
|
11
|
+
if (v === void 0) return void 0;
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(JSON.stringify(v));
|
|
14
|
+
} catch {
|
|
15
|
+
return String(v);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var answerControl = async (req, reply) => {
|
|
19
|
+
try {
|
|
20
|
+
const result = await runDevtoolsCommand(req.cmd, req.args ?? {});
|
|
21
|
+
reply({ type: "control-result", sessionId: req.sessionId, id: req.id, ok: true, result: jsonSafe(result) });
|
|
22
|
+
} catch (err) {
|
|
23
|
+
reply({
|
|
24
|
+
type: "control-result",
|
|
25
|
+
sessionId: req.sessionId,
|
|
26
|
+
id: req.id,
|
|
27
|
+
ok: false,
|
|
28
|
+
error: err instanceof Error ? err.message : String(err)
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var captureEnv = () => {
|
|
33
|
+
const g = globalThis;
|
|
34
|
+
return {
|
|
35
|
+
crossOriginIsolated: g.crossOriginIsolated,
|
|
36
|
+
sharedArrayBuffer: typeof SharedArrayBuffer !== "undefined",
|
|
37
|
+
hardwareConcurrency: g.navigator?.hardwareConcurrency,
|
|
38
|
+
userAgent: g.navigator?.userAgent,
|
|
39
|
+
node: g.navigator ? void 0 : g.process?.version
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/protocol.ts
|
|
44
|
+
var DEVTOOLS_CHANNEL = "atoll-devtools";
|
|
45
|
+
|
|
46
|
+
// src/broadcast.ts
|
|
47
|
+
function connectBroadcast(opts, session) {
|
|
48
|
+
const bc = new BroadcastChannel(DEVTOOLS_CHANNEL);
|
|
49
|
+
const cap = opts.bufferCap ?? 5e4;
|
|
50
|
+
const tailCap = opts.replayBatches ?? 500;
|
|
51
|
+
const pending = [];
|
|
52
|
+
const tail = [];
|
|
53
|
+
let closed = false;
|
|
54
|
+
const post = (msg) => {
|
|
55
|
+
try {
|
|
56
|
+
bc.postMessage(msg);
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const hello = () => post({ type: "hello", session });
|
|
61
|
+
const flush = () => {
|
|
62
|
+
if (closed || pending.length === 0) return;
|
|
63
|
+
const events = pending.splice(0, pending.length);
|
|
64
|
+
tail.push(events);
|
|
65
|
+
if (tail.length > tailCap) tail.shift();
|
|
66
|
+
post({ type: "batch", session, events });
|
|
67
|
+
};
|
|
68
|
+
bc.onmessage = (m) => {
|
|
69
|
+
if (m.data?.type === "view") {
|
|
70
|
+
hello();
|
|
71
|
+
for (const events of tail) post({ type: "batch", session, events });
|
|
72
|
+
} else if (m.data?.type === "control" && m.data.sessionId === session.id) {
|
|
73
|
+
void answerControl(m.data, (res) => post(res));
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const timer = setInterval(flush, opts.flushMs ?? 100);
|
|
77
|
+
timer.unref?.();
|
|
78
|
+
const bye = () => {
|
|
79
|
+
flush();
|
|
80
|
+
post({ type: "bye", sessionId: session.id });
|
|
81
|
+
};
|
|
82
|
+
const onHide = () => bye();
|
|
83
|
+
if (typeof addEventListener === "function") addEventListener("pagehide", onHide);
|
|
84
|
+
if (opts.network !== false) installFetchProbe(globalThis);
|
|
85
|
+
if (opts.memory !== false) installMemoryProbe(globalThis);
|
|
86
|
+
const releaseJank = opts.jank !== false ? installJankProbe() : () => {
|
|
87
|
+
};
|
|
88
|
+
setDevtoolsSink((event) => {
|
|
89
|
+
if (closed) return;
|
|
90
|
+
pending.push(event);
|
|
91
|
+
if (pending.length > cap) pending.shift();
|
|
92
|
+
if (pending.length >= 1e3) flush();
|
|
93
|
+
});
|
|
94
|
+
hello();
|
|
95
|
+
return {
|
|
96
|
+
session,
|
|
97
|
+
get open() {
|
|
98
|
+
return !closed;
|
|
99
|
+
},
|
|
100
|
+
close() {
|
|
101
|
+
if (closed) return;
|
|
102
|
+
closed = true;
|
|
103
|
+
bye();
|
|
104
|
+
setDevtoolsSink(null);
|
|
105
|
+
clearInterval(timer);
|
|
106
|
+
releaseJank();
|
|
107
|
+
if (typeof removeEventListener === "function") removeEventListener("pagehide", onHide);
|
|
108
|
+
bc.close();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/client.ts
|
|
114
|
+
var sessionSeq = 0;
|
|
115
|
+
var makeSession = (opts) => {
|
|
116
|
+
const runtime = typeof window !== "undefined" && typeof window.document !== "undefined" ? "browser" : "node";
|
|
117
|
+
const hint = opts.session?.hint ?? (runtime === "browser" ? globalThis.location?.href : globalThis.process?.title);
|
|
118
|
+
return {
|
|
119
|
+
id: `s-${Date.now().toString(36)}-${++sessionSeq}`,
|
|
120
|
+
name: opts.session?.name,
|
|
121
|
+
framework: opts.session?.framework,
|
|
122
|
+
runtime,
|
|
123
|
+
hint,
|
|
124
|
+
env: captureEnv()
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
var autoBroadcast = () => typeof BroadcastChannel !== "undefined" && typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
128
|
+
function connectDevtools(opts = {}) {
|
|
129
|
+
const session = makeSession(opts);
|
|
130
|
+
const t = opts.url !== void 0 ? "websocket" : opts.transport ?? "auto";
|
|
131
|
+
const useBc = t === "broadcast" ? typeof BroadcastChannel !== "undefined" : t === "auto" && autoBroadcast();
|
|
132
|
+
return useBc ? connectBroadcast(opts, session) : connectWebSocket(opts, session);
|
|
133
|
+
}
|
|
134
|
+
function connectWebSocket(opts, session) {
|
|
135
|
+
const url = opts.url ?? "ws://127.0.0.1:4780/events";
|
|
136
|
+
const cap = opts.bufferCap ?? 5e4;
|
|
137
|
+
const pending = [];
|
|
138
|
+
let closed = false;
|
|
139
|
+
let sock = null;
|
|
140
|
+
const send = (msg) => {
|
|
141
|
+
if (sock?.open) sock.send(JSON.stringify(msg));
|
|
142
|
+
};
|
|
143
|
+
const flush = () => {
|
|
144
|
+
if (!sock?.open || pending.length === 0) return;
|
|
145
|
+
const events = pending.splice(0, pending.length);
|
|
146
|
+
send({ type: "batch", events });
|
|
147
|
+
};
|
|
148
|
+
const onOpen = () => {
|
|
149
|
+
send({ type: "hello", session });
|
|
150
|
+
flush();
|
|
151
|
+
};
|
|
152
|
+
const onMessage = (text) => {
|
|
153
|
+
let msg;
|
|
154
|
+
try {
|
|
155
|
+
msg = JSON.parse(text);
|
|
156
|
+
} catch {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (msg.type === "control") void answerControl(msg, (res) => send(res));
|
|
160
|
+
};
|
|
161
|
+
const isNode = typeof window === "undefined" || typeof window.document === "undefined";
|
|
162
|
+
if (typeof WebSocket !== "undefined") {
|
|
163
|
+
const ws = new WebSocket(url);
|
|
164
|
+
sock = {
|
|
165
|
+
get open() {
|
|
166
|
+
return ws.readyState === ws.OPEN;
|
|
167
|
+
},
|
|
168
|
+
send: (t) => ws.send(t),
|
|
169
|
+
close: () => ws.close()
|
|
170
|
+
};
|
|
171
|
+
ws.onopen = onOpen;
|
|
172
|
+
ws.onmessage = (m) => onMessage(String(m.data));
|
|
173
|
+
ws.onclose = () => clearInterval(timer);
|
|
174
|
+
ws.onerror = () => ws.close();
|
|
175
|
+
} else if (isNode) {
|
|
176
|
+
const mod = "./nodeWs.ts";
|
|
177
|
+
void import(
|
|
178
|
+
/* @vite-ignore */
|
|
179
|
+
mod
|
|
180
|
+
).then((m) => {
|
|
181
|
+
if (closed) return;
|
|
182
|
+
const ws = m.connectNodeWebSocket(url);
|
|
183
|
+
sock = ws;
|
|
184
|
+
ws.onOpen = onOpen;
|
|
185
|
+
ws.onMessage = onMessage;
|
|
186
|
+
ws.onClose = () => clearInterval(timer);
|
|
187
|
+
}).catch(() => {
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
const timer = setInterval(flush, opts.flushMs ?? 100);
|
|
191
|
+
timer.unref?.();
|
|
192
|
+
if (opts.network !== false) installFetchProbe(globalThis);
|
|
193
|
+
if (opts.memory !== false) installMemoryProbe(globalThis);
|
|
194
|
+
const releaseJank = opts.jank !== false ? installJankProbe() : () => {
|
|
195
|
+
};
|
|
196
|
+
setDevtoolsSink((event) => {
|
|
197
|
+
if (closed) return;
|
|
198
|
+
pending.push(event);
|
|
199
|
+
if (pending.length > cap) pending.shift();
|
|
200
|
+
if (pending.length >= 1e3) flush();
|
|
201
|
+
});
|
|
202
|
+
return {
|
|
203
|
+
session,
|
|
204
|
+
get open() {
|
|
205
|
+
return sock?.open ?? false;
|
|
206
|
+
},
|
|
207
|
+
close() {
|
|
208
|
+
closed = true;
|
|
209
|
+
setDevtoolsSink(null);
|
|
210
|
+
flush();
|
|
211
|
+
sock?.close();
|
|
212
|
+
clearInterval(timer);
|
|
213
|
+
releaseJank();
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/overlay.ts
|
|
219
|
+
var BTN_STYLE = "position:fixed;z-index:2147483646;font:11px ui-monospace,monospace;color:#c9d1d9;background:#161b22;border:1px solid #30363d;border-radius:6px;padding:5px 9px;cursor:pointer";
|
|
220
|
+
var PANEL_STYLE = "position:fixed;z-index:2147483646;display:flex;flex-direction:column;background:#0d1117;border:1px solid #30363d;border-radius:8px;box-shadow:0 12px 40px #000c;overflow:hidden;min-width:340px;min-height:220px;max-width:100vw;max-height:100vh;box-sizing:border-box";
|
|
221
|
+
var EDGE_CSS = {
|
|
222
|
+
n: "top:0;left:10px;right:10px;height:4px;cursor:ns-resize",
|
|
223
|
+
s: "bottom:0;left:10px;right:16px;height:5px;cursor:ns-resize",
|
|
224
|
+
e: "right:0;top:10px;bottom:16px;width:5px;cursor:ew-resize",
|
|
225
|
+
w: "left:0;top:10px;bottom:10px;width:5px;cursor:ew-resize",
|
|
226
|
+
ne: "right:0;top:0;width:10px;height:10px;cursor:nesw-resize",
|
|
227
|
+
nw: "left:0;top:0;width:10px;height:10px;cursor:nwse-resize",
|
|
228
|
+
sw: "left:0;bottom:0;width:10px;height:10px;cursor:nesw-resize",
|
|
229
|
+
se: "right:0;bottom:0;width:16px;height:16px;cursor:nwse-resize;background:linear-gradient(135deg,transparent 50%,#30363d 50%);border-radius:0 0 7px"
|
|
230
|
+
};
|
|
231
|
+
var ANCHOR = {
|
|
232
|
+
topleft: (m) => `left:${m}px;top:${m}px`,
|
|
233
|
+
topcenter: (m) => `left:50%;top:${m}px;transform:translateX(-50%)`,
|
|
234
|
+
topright: (m) => `right:${m}px;top:${m}px`,
|
|
235
|
+
bottomleft: (m) => `left:${m}px;bottom:${m}px`,
|
|
236
|
+
bottomcenter: (m) => `left:50%;bottom:${m}px;transform:translateX(-50%)`,
|
|
237
|
+
bottomright: (m) => `right:${m}px;bottom:${m}px`
|
|
238
|
+
};
|
|
239
|
+
var MIN_W = 340;
|
|
240
|
+
var MIN_H = 220;
|
|
241
|
+
var seq = 0;
|
|
242
|
+
function parseHotkey(spec) {
|
|
243
|
+
const parts = spec.split("+").map((p) => p.trim().toLowerCase()).filter(Boolean);
|
|
244
|
+
const key = parts.pop() ?? "";
|
|
245
|
+
const mods = new Set(parts.map((m) => m === "option" ? "alt" : m === "cmd" || m === "meta" ? "meta" : m === "control" ? "ctrl" : m));
|
|
246
|
+
const code = /^[a-z]$/.test(key) ? `Key${key.toUpperCase()}` : /^[0-9]$/.test(key) ? `Digit${key}` : null;
|
|
247
|
+
return (e) => e.altKey === mods.has("alt") && e.shiftKey === mods.has("shift") && e.ctrlKey === mods.has("ctrl") && e.metaKey === mods.has("meta") && (code ? e.code === code : e.key.toLowerCase() === key);
|
|
248
|
+
}
|
|
249
|
+
function mountDevtoolsOverlay(opts = {}) {
|
|
250
|
+
const src = opts.src ?? "/__atoll/?mini=1";
|
|
251
|
+
const storeKey = opts.persist === false ? null : typeof opts.persist === "string" ? opts.persist : "atoll-devtools:overlay";
|
|
252
|
+
const load = () => {
|
|
253
|
+
if (!storeKey) return {};
|
|
254
|
+
try {
|
|
255
|
+
return JSON.parse(localStorage.getItem(storeKey) ?? "{}");
|
|
256
|
+
} catch {
|
|
257
|
+
return {};
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
const saved = load();
|
|
261
|
+
const W = Math.max(MIN_W, saved.width ?? opts.width ?? 760);
|
|
262
|
+
const H = Math.max(MIN_H, saved.height ?? opts.height ?? 580);
|
|
263
|
+
const anchor = ANCHOR[opts.position ?? "bottomright"];
|
|
264
|
+
const hotkey = opts.hotkey === false ? null : opts.hotkey ?? "Alt+Shift+D";
|
|
265
|
+
const isHotkey = hotkey ? parseHotkey(hotkey) : null;
|
|
266
|
+
const hint = hotkey ? ` (${hotkey})` : "";
|
|
267
|
+
const id = `atoll-devtools-overlay-${++seq}`;
|
|
268
|
+
let open = false;
|
|
269
|
+
const btn = document.createElement("button");
|
|
270
|
+
btn.type = "button";
|
|
271
|
+
btn.textContent = "atoll devtools";
|
|
272
|
+
btn.title = `Open atoll devtools${hint}`;
|
|
273
|
+
btn.setAttribute("aria-controls", id);
|
|
274
|
+
btn.setAttribute("aria-expanded", "false");
|
|
275
|
+
btn.style.cssText = `${BTN_STYLE};${anchor(12)}`;
|
|
276
|
+
const panel = document.createElement("div");
|
|
277
|
+
panel.id = id;
|
|
278
|
+
panel.setAttribute("role", "dialog");
|
|
279
|
+
panel.setAttribute("aria-label", "atoll devtools");
|
|
280
|
+
panel.style.cssText = `${PANEL_STYLE};width:${W}px;height:${H}px;display:none;${anchor(16)}`;
|
|
281
|
+
const head = document.createElement("div");
|
|
282
|
+
head.style.cssText = "display:flex;align-items:center;gap:8px;padding:5px 10px;flex:none;border-bottom:1px solid #21262d;font:11px ui-monospace,monospace;color:#8b949e;background:#161b22;cursor:move;user-select:none";
|
|
283
|
+
const title = document.createElement("span");
|
|
284
|
+
title.textContent = "atoll devtools";
|
|
285
|
+
const base = src.split(/[?#]/)[0];
|
|
286
|
+
const frame = document.createElement("iframe");
|
|
287
|
+
let route = "";
|
|
288
|
+
const currentRoute = () => {
|
|
289
|
+
try {
|
|
290
|
+
const h = frame.contentWindow?.location.hash;
|
|
291
|
+
if (typeof h === "string" && h.startsWith("#/")) return h;
|
|
292
|
+
} catch {
|
|
293
|
+
}
|
|
294
|
+
return route;
|
|
295
|
+
};
|
|
296
|
+
const pop = document.createElement("a");
|
|
297
|
+
pop.href = base;
|
|
298
|
+
pop.target = "_blank";
|
|
299
|
+
pop.rel = "noopener";
|
|
300
|
+
pop.textContent = "full page \u2197";
|
|
301
|
+
pop.title = "Open this view in the full-page dashboard";
|
|
302
|
+
pop.style.cssText = "color:#58a6ff;text-decoration:none;margin-left:auto";
|
|
303
|
+
const syncPop = () => {
|
|
304
|
+
pop.href = base + currentRoute();
|
|
305
|
+
};
|
|
306
|
+
pop.addEventListener("pointerenter", syncPop);
|
|
307
|
+
pop.addEventListener("focus", syncPop);
|
|
308
|
+
pop.addEventListener("click", syncPop);
|
|
309
|
+
const x = document.createElement("button");
|
|
310
|
+
x.type = "button";
|
|
311
|
+
x.textContent = "\xD7";
|
|
312
|
+
x.title = `Close${hint}`;
|
|
313
|
+
x.setAttribute("aria-label", "Close atoll devtools");
|
|
314
|
+
x.style.cssText = "color:#8b949e;background:none;border:none;cursor:pointer;font:14px/1 ui-monospace,monospace;padding:0 2px";
|
|
315
|
+
head.append(title, pop, x);
|
|
316
|
+
frame.src = src;
|
|
317
|
+
frame.title = "atoll devtools dashboard";
|
|
318
|
+
frame.style.cssText = "flex:1;border:0;background:#0d1117;min-height:0";
|
|
319
|
+
const handles = ["n", "s", "e", "w", "ne", "nw", "sw", "se"].map((edge) => {
|
|
320
|
+
const h = document.createElement("div");
|
|
321
|
+
h.setAttribute("aria-hidden", "true");
|
|
322
|
+
h.dataset.edge = edge;
|
|
323
|
+
h.style.cssText = `position:absolute;z-index:1;${EDGE_CSS[edge]}`;
|
|
324
|
+
return h;
|
|
325
|
+
});
|
|
326
|
+
panel.append(head, frame, ...handles);
|
|
327
|
+
document.body.append(panel, btn);
|
|
328
|
+
const px = (v) => parseFloat(v) || 0;
|
|
329
|
+
const vw = () => document.documentElement.clientWidth || innerWidth;
|
|
330
|
+
const vh = () => document.documentElement.clientHeight || innerHeight;
|
|
331
|
+
const fitW = (w) => Math.max(MIN_W, Math.min(w, vw()));
|
|
332
|
+
const fitH = (h) => Math.max(MIN_H, Math.min(h, vh()));
|
|
333
|
+
const placeAt = (left, top) => {
|
|
334
|
+
const w = px(panel.style.width), h = px(panel.style.height);
|
|
335
|
+
panel.style.left = `${Math.min(Math.max(0, left), Math.max(0, vw() - w))}px`;
|
|
336
|
+
panel.style.top = `${Math.min(Math.max(0, top), Math.max(0, vh() - h))}px`;
|
|
337
|
+
panel.style.right = "auto";
|
|
338
|
+
panel.style.bottom = "auto";
|
|
339
|
+
panel.style.transform = "none";
|
|
340
|
+
};
|
|
341
|
+
const pinned = () => panel.style.right === "auto";
|
|
342
|
+
const pin = () => {
|
|
343
|
+
if (pinned()) return;
|
|
344
|
+
const r = panel.getBoundingClientRect();
|
|
345
|
+
placeAt(r.left, r.top);
|
|
346
|
+
};
|
|
347
|
+
const refit = () => {
|
|
348
|
+
panel.style.width = `${fitW(px(panel.style.width))}px`;
|
|
349
|
+
panel.style.height = `${fitH(px(panel.style.height))}px`;
|
|
350
|
+
if (pinned()) placeAt(px(panel.style.left), px(panel.style.top));
|
|
351
|
+
};
|
|
352
|
+
if (typeof saved.left === "number" && typeof saved.top === "number") {
|
|
353
|
+
panel.style.width = `${fitW(W)}px`;
|
|
354
|
+
panel.style.height = `${fitH(H)}px`;
|
|
355
|
+
placeAt(saved.left, saved.top);
|
|
356
|
+
}
|
|
357
|
+
const mem = { ...saved };
|
|
358
|
+
const save = (what) => {
|
|
359
|
+
if (!storeKey) return;
|
|
360
|
+
if (what === "open") mem.open = open;
|
|
361
|
+
if (what === "size") {
|
|
362
|
+
mem.width = parseFloat(panel.style.width);
|
|
363
|
+
mem.height = parseFloat(panel.style.height);
|
|
364
|
+
}
|
|
365
|
+
if (what === "move" || what === "size") {
|
|
366
|
+
mem.left = parseFloat(panel.style.left);
|
|
367
|
+
mem.top = parseFloat(panel.style.top);
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
localStorage.setItem(storeKey, JSON.stringify(mem));
|
|
371
|
+
} catch {
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
const drag = (e, what, move) => {
|
|
375
|
+
e.preventDefault();
|
|
376
|
+
const sx = e.clientX, sy = e.clientY;
|
|
377
|
+
frame.style.pointerEvents = "none";
|
|
378
|
+
const mm = (ev) => move(ev.clientX - sx, ev.clientY - sy);
|
|
379
|
+
const up = () => {
|
|
380
|
+
removeEventListener("pointermove", mm);
|
|
381
|
+
removeEventListener("pointerup", up);
|
|
382
|
+
frame.style.pointerEvents = "";
|
|
383
|
+
save(what);
|
|
384
|
+
};
|
|
385
|
+
addEventListener("pointermove", mm);
|
|
386
|
+
addEventListener("pointerup", up);
|
|
387
|
+
};
|
|
388
|
+
head.onpointerdown = (e) => {
|
|
389
|
+
if (e.target.closest("a,button")) return;
|
|
390
|
+
pin();
|
|
391
|
+
refit();
|
|
392
|
+
const l0 = px(panel.style.left), t0 = px(panel.style.top);
|
|
393
|
+
drag(e, "move", (dx, dy) => placeAt(l0 + dx, t0 + dy));
|
|
394
|
+
};
|
|
395
|
+
for (const h of handles) {
|
|
396
|
+
const edge = h.dataset.edge;
|
|
397
|
+
h.onpointerdown = (e) => {
|
|
398
|
+
e.stopPropagation();
|
|
399
|
+
pin();
|
|
400
|
+
refit();
|
|
401
|
+
const l0 = px(panel.style.left), t0 = px(panel.style.top);
|
|
402
|
+
const w0 = px(panel.style.width), h0 = px(panel.style.height);
|
|
403
|
+
drag(e, "size", (dx, dy) => {
|
|
404
|
+
let left = l0, top = t0, w = w0, hh = h0;
|
|
405
|
+
if (edge.includes("e")) w = Math.max(MIN_W, Math.min(w0 + dx, vw() - l0));
|
|
406
|
+
if (edge.includes("s")) hh = Math.max(MIN_H, Math.min(h0 + dy, vh() - t0));
|
|
407
|
+
if (edge.includes("w")) {
|
|
408
|
+
left = Math.max(0, Math.min(l0 + dx, l0 + w0 - MIN_W));
|
|
409
|
+
w = l0 + w0 - left;
|
|
410
|
+
}
|
|
411
|
+
if (edge.includes("n")) {
|
|
412
|
+
top = Math.max(0, Math.min(t0 + dy, t0 + h0 - MIN_H));
|
|
413
|
+
hh = t0 + h0 - top;
|
|
414
|
+
}
|
|
415
|
+
panel.style.width = `${w}px`;
|
|
416
|
+
panel.style.height = `${hh}px`;
|
|
417
|
+
panel.style.left = `${left}px`;
|
|
418
|
+
panel.style.top = `${top}px`;
|
|
419
|
+
});
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
const set = (v, focus = false, persist = true) => {
|
|
423
|
+
const hadFocus = panel.contains(document.activeElement);
|
|
424
|
+
open = v;
|
|
425
|
+
panel.style.display = v ? "flex" : "none";
|
|
426
|
+
if (v) refit();
|
|
427
|
+
btn.style.display = v ? "none" : "block";
|
|
428
|
+
btn.setAttribute("aria-expanded", String(v));
|
|
429
|
+
if (v && focus) frame.focus();
|
|
430
|
+
if (!v && hadFocus) btn.focus();
|
|
431
|
+
if (persist) save("open");
|
|
432
|
+
};
|
|
433
|
+
btn.onclick = () => set(true, true);
|
|
434
|
+
x.onclick = () => set(false);
|
|
435
|
+
const onKey = (e) => {
|
|
436
|
+
if (!isHotkey?.(e)) return;
|
|
437
|
+
e.preventDefault();
|
|
438
|
+
set(!open, true);
|
|
439
|
+
};
|
|
440
|
+
const onMessage = (e) => {
|
|
441
|
+
if (e.source !== frame.contentWindow || !e.data || typeof e.data !== "object") return;
|
|
442
|
+
const d = e.data;
|
|
443
|
+
if (d.type === "atoll-devtools:toggle") set(!open);
|
|
444
|
+
else if (d.type === "atoll-devtools:route" && typeof d.hash === "string" && d.hash.startsWith("#/")) {
|
|
445
|
+
route = d.hash;
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
const onResize = () => {
|
|
449
|
+
if (open) refit();
|
|
450
|
+
};
|
|
451
|
+
if (isHotkey) addEventListener("keydown", onKey);
|
|
452
|
+
addEventListener("message", onMessage);
|
|
453
|
+
addEventListener("resize", onResize);
|
|
454
|
+
const ro = typeof ResizeObserver === "function" ? new ResizeObserver(onResize) : void 0;
|
|
455
|
+
ro?.observe(document.documentElement);
|
|
456
|
+
set(opts.startOpen ?? saved.open === true, false, false);
|
|
457
|
+
return {
|
|
458
|
+
open: () => set(true),
|
|
459
|
+
close: () => set(false),
|
|
460
|
+
toggle: () => set(!open),
|
|
461
|
+
unmount: () => {
|
|
462
|
+
removeEventListener("keydown", onKey);
|
|
463
|
+
removeEventListener("message", onMessage);
|
|
464
|
+
removeEventListener("resize", onResize);
|
|
465
|
+
ro?.disconnect();
|
|
466
|
+
panel.remove();
|
|
467
|
+
btn.remove();
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/init.ts
|
|
473
|
+
var DEVTOOLS_PARAM = "__atoll_devtools";
|
|
474
|
+
var devtoolsEnabled = (enabled) => {
|
|
475
|
+
if (enabled !== void 0) return enabled;
|
|
476
|
+
if (typeof location !== "undefined" && typeof location.search === "string") {
|
|
477
|
+
return new URLSearchParams(location.search).has(DEVTOOLS_PARAM);
|
|
478
|
+
}
|
|
479
|
+
return !!globalThis.process?.env?.ATOLL_DEVTOOLS;
|
|
480
|
+
};
|
|
481
|
+
function initDevtools(opts = {}) {
|
|
482
|
+
if (!devtoolsEnabled(opts.enabled)) return null;
|
|
483
|
+
const conn = connectDevtools(opts);
|
|
484
|
+
const inWindow = typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
485
|
+
const overlay = inWindow && opts.overlay !== false ? mountDevtoolsOverlay(typeof opts.overlay === "object" ? opts.overlay : {}) : null;
|
|
486
|
+
return { conn, overlay };
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export {
|
|
490
|
+
connectDevtools,
|
|
491
|
+
mountDevtoolsOverlay,
|
|
492
|
+
DEVTOOLS_PARAM,
|
|
493
|
+
devtoolsEnabled,
|
|
494
|
+
initDevtools
|
|
495
|
+
};
|