@atolljs/devtools 0.1.8 → 0.1.10
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 +1 -1
- package/app/fw-logos.js +14 -0
- package/app/main.js +22 -97
- package/dist/{chunk-AGT6VNZI.js → chunk-6E54FXKS.js} +86 -27
- package/dist/index.js +1 -1
- package/dist/node.js +1 -1
- package/package.json +2 -2
- package/src/overlay.ts +97 -31
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ current and future viewers. Live sessions can't be dismissed.
|
|
|
24
24
|
|---|---|
|
|
25
25
|
| `@atolljs/devtools` | `connectDevtools({ transport?, url?, session?, flushMs?, bufferCap?, replayBatches?, network?, memory?, jank? })`: installs the core event sink and batches events to dashboards. Default `transport: 'auto'`: **BroadcastChannel** in a browser window (pure client, no backend; the dashboard is served on the app's own origin at `/__atoll/` by the vite plugin in dev, and emitted into `vite build` output whenever the app bundles devtools), WebSocket to the aggregate server everywhere else (`url` or `transport: 'websocket'` opts in; Node uses this path). `session` is `{ name?, hint?, framework? }`. `network` (default on) wraps `fetch` to emit `net:fetch` per request; `memory` (default on) samples JS heap into `runtime:memory`; `jank` (default on) installs the main-thread jank probe (`runtime:longframe`, `runtime:frames`). |
|
|
26
26
|
| `@atolljs/devtools` | `initDevtools({ enabled?, overlay?, ...connectOptions })`: the URL-gated one-liner (`?__atoll_devtools`), connects and mounts the overlay. |
|
|
27
|
-
| `@atolljs/devtools` | `mountDevtoolsOverlay({ src?, startOpen?, width?, height?, position?, persist?, hotkey? })`: the floating flyout (
|
|
27
|
+
| `@atolljs/devtools` | `mountDevtoolsOverlay({ src?, startOpen?, width?, height?, position?, persist?, hotkey? })`: the floating flyout (drag the header to move it, any edge or corner to resize; it stays inside the visible viewport). `persist` (default `true`, or a localStorage key, default `'atoll-devtools:overlay'`) remembers position, size and open state; `hotkey` (default `'Alt+Shift+D'`, `false` disables) toggles it. |
|
|
28
28
|
| `@atolljs/devtools/node` | Node entry: `initDevtools`/`connectDevtools` without the browser-only pieces (WebSocket-only, env-gated, no overlay). Use this import in Node apps. |
|
|
29
29
|
| `@atolljs/devtools/server` | `createDevtoolsServer({ port?, host?, appDir?, replayBatches?, closedTtlMs?, sweepMs? })`: Node http + dependency-free WebSocket server on `127.0.0.1` (default port 4780). |
|
|
30
30
|
| `@atolljs/devtools/otel` | `exportOtel({ endpoint?, headers?, serviceName?, resource?, signals?, flushIntervalMs?, maxBatch?, maxQueue?, maxSeries?, onError?, network?, memory?, jank? })`: dependency-free OTLP/HTTP JSON exporter (traces, metrics, logs) to any OpenTelemetry backend; returns `{ flush(), close() }`. Install before pools spawn. See [OpenTelemetry export](#opentelemetry-export). |
|
package/app/fw-logos.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Framework logos from Simple Icons 16.33.0 (CC0, https://simpleicons.org):
|
|
2
|
+
* 24x24 path data + official brand hex, keyed by the ramework tag. */
|
|
3
|
+
export const FW_LOGOS = {
|
|
4
|
+
react: { title: "React", hex: '#61DAFB', d: "M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z" },
|
|
5
|
+
vue: { title: "Vue.js", hex: '#4FC08D', d: "M24,1.61H14.06L12,5.16,9.94,1.61H0L12,22.39ZM12,14.08,5.16,2.23H9.59L12,6.41l2.41-4.18h4.43Z" },
|
|
6
|
+
svelte: { title: "Svelte", hex: '#FF3E00', d: "M10.354 21.125a4.44 4.44 0 0 1-4.765-1.767 4.109 4.109 0 0 1-.703-3.107 3.898 3.898 0 0 1 .134-.522l.105-.321.287.21a7.21 7.21 0 0 0 2.186 1.092l.208.063-.02.208a1.253 1.253 0 0 0 .226.83 1.337 1.337 0 0 0 1.435.533 1.231 1.231 0 0 0 .343-.15l5.59-3.562a1.164 1.164 0 0 0 .524-.778 1.242 1.242 0 0 0-.211-.937 1.338 1.338 0 0 0-1.435-.533 1.23 1.23 0 0 0-.343.15l-2.133 1.36a4.078 4.078 0 0 1-1.135.499 4.44 4.44 0 0 1-4.765-1.766 4.108 4.108 0 0 1-.702-3.108 3.855 3.855 0 0 1 1.742-2.582l5.589-3.563a4.072 4.072 0 0 1 1.135-.499 4.44 4.44 0 0 1 4.765 1.767 4.109 4.109 0 0 1 .703 3.107 3.943 3.943 0 0 1-.134.522l-.105.321-.286-.21a7.204 7.204 0 0 0-2.187-1.093l-.208-.063.02-.207a1.255 1.255 0 0 0-.226-.831 1.337 1.337 0 0 0-1.435-.532 1.231 1.231 0 0 0-.343.15L8.62 9.368a1.162 1.162 0 0 0-.524.778 1.24 1.24 0 0 0 .211.937 1.338 1.338 0 0 0 1.435.533 1.235 1.235 0 0 0 .344-.151l2.132-1.36a4.067 4.067 0 0 1 1.135-.498 4.44 4.44 0 0 1 4.765 1.766 4.108 4.108 0 0 1 .702 3.108 3.857 3.857 0 0 1-1.742 2.583l-5.589 3.562a4.072 4.072 0 0 1-1.135.499m10.358-17.95C18.484-.015 14.082-.96 10.9 1.068L5.31 4.63a6.412 6.412 0 0 0-2.896 4.295 6.753 6.753 0 0 0 .666 4.336 6.43 6.43 0 0 0-.96 2.396 6.833 6.833 0 0 0 1.168 5.167c2.229 3.19 6.63 4.135 9.812 2.108l5.59-3.562a6.41 6.41 0 0 0 2.896-4.295 6.756 6.756 0 0 0-.665-4.336 6.429 6.429 0 0 0 .958-2.396 6.831 6.831 0 0 0-1.167-5.168Z" },
|
|
7
|
+
solid: { title: "Solid", hex: '#2C4F7C', d: "M11.558.788A9.082 9.082 0 0 0 9.776.99l-.453.15c-.906.303-1.656.755-2.1 1.348l-.301.452-2.035 3.528c.426-.387.974-.698 1.643-.894h.001l.613-.154h.001a8.82 8.82 0 0 1 1.777-.206c2.916-.053 6.033 1.148 8.423 2.36 2.317 1.175 3.888 2.32 3.987 2.39L24 5.518c-.082-.06-1.66-1.21-3.991-2.386-2.393-1.206-5.521-2.396-8.45-2.343zM8.924 5.366a8.634 8.634 0 0 0-1.745.203l-.606.151c-1.278.376-2.095 1.16-2.43 2.108-.334.948-.188 2.065.487 3.116.33.43.747.813 1.216 1.147L12.328 10h.001a6.943 6.943 0 0 1 6.013 1.013l2.844-.963c-.17-.124-1.663-1.2-3.91-2.34-2.379-1.206-5.479-2.396-8.352-2.344zm5.435 4.497a6.791 6.791 0 0 0-1.984.283L2.94 13.189 0 18.334l9.276-2.992a6.945 6.945 0 0 1 7.408 2.314v.001c.695.903.89 1.906.66 2.808l2.572-4.63c.595-1.041.45-2.225-.302-3.429a6.792 6.792 0 0 0-5.255-2.543zm-3.031 5.341a6.787 6.787 0 0 0-2.006.283L.008 18.492c.175.131 2.02 1.498 4.687 2.768 2.797 1.332 6.37 2.467 9.468 1.712l.454-.152h.002c1.278-.376 2.134-1.162 2.487-2.09.353-.93.207-2.004-.541-2.978a6.791 6.791 0 0 0-5.237-2.548z" },
|
|
8
|
+
angular: { title: "Angular", hex: '#0F0F11', d: "M16.712 17.711H7.288l-1.204 2.916L12 24l5.916-3.373-1.204-2.916ZM14.692 0l7.832 16.855.814-12.856L14.692 0ZM9.308 0 .662 3.999l.814 12.856L9.308 0Zm-.405 13.93h6.198L12 6.396 8.903 13.93Z" },
|
|
9
|
+
vanilla: { title: "JavaScript", hex: '#F7DF1E', d: "M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71 1.005-1.14 1.291-.811 3.541.569 4.471 1.365 1.02 3.361 1.244 3.616 2.205.24 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.586-1.755-1.336l-1.83 1.051c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.666 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z" },
|
|
10
|
+
leaflet: { title: "Leaflet", hex: '#199900', d: "M17.69 0c-.355.574-8.432 4.74-10.856 8.649-2.424 3.91-3.116 6.988-2.237 9.882.879 2.893 2.559 2.763 3.516 3.717.958.954 2.257 2.113 4.332 1.645 2.717-.613 5.335-2.426 6.638-7.508 1.302-5.082.448-9.533-.103-11.99A35.395 35.395 0 0 0 17.69 0zm-.138.858l-9.22 21.585-.574-.577Z" },
|
|
11
|
+
nextjs: { title: "Next.js", hex: '#000000', d: "M18.665 21.978C16.758 23.255 14.465 24 12 24 5.377 24 0 18.623 0 12S5.377 0 12 0s12 5.377 12 12c0 3.583-1.574 6.801-4.067 9.001L9.219 7.2H7.2v9.596h1.615V9.251l9.85 12.727Zm-3.332-8.533 1.6 2.061V7.2h-1.6v6.245Z" },
|
|
12
|
+
};
|
|
13
|
+
FW_LOGOS.next = FW_LOGOS.nextjs;
|
|
14
|
+
FW_LOGOS.js = FW_LOGOS.vanilla;
|
package/app/main.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// Feature panels live in ./panels/*.js and plug in through ./api.js (see
|
|
10
10
|
// PANELS at the bottom): this file owns the reducer, core views, transport.
|
|
11
11
|
import { api } from './api.js';
|
|
12
|
+
import { FW_LOGOS } from './fw-logos.js';
|
|
12
13
|
|
|
13
14
|
const $ = (id) => document.getElementById(id);
|
|
14
15
|
const esc = (s) => String(s ?? '').replace(/[&<>]/g, (c) => ({ '&': '&', '<': '<', '>': '>' }[c]));
|
|
@@ -1394,17 +1395,6 @@ function fitTopo(canvas, world) {
|
|
|
1394
1395
|
topo.oy = (H - world.height * topo.scale) / 2;
|
|
1395
1396
|
}
|
|
1396
1397
|
|
|
1397
|
-
/** framework → [chip color, short tag] — HTML chips + fallback marks. */
|
|
1398
|
-
const FW_BADGES = {
|
|
1399
|
-
react: ['#61dafb', '⚛'],
|
|
1400
|
-
vue: ['#42b883', 'V'],
|
|
1401
|
-
svelte: ['#ff3e00', 'S'],
|
|
1402
|
-
angular: ['#dd0031', 'A'],
|
|
1403
|
-
solid: ['#4f88c6', 'So'],
|
|
1404
|
-
vanilla: ['#f0db4f', 'JS'],
|
|
1405
|
-
leaflet: ['#19993b', 'L'],
|
|
1406
|
-
};
|
|
1407
|
-
|
|
1408
1398
|
/** Structural (non-island) nodes — a lettered disc, so pools and plain
|
|
1409
1399
|
* workers never read as a framework island. */
|
|
1410
1400
|
const ROLE_MARKS = {
|
|
@@ -1426,96 +1416,31 @@ function drawRoleMark(ctx, m, x, y, s, dead) {
|
|
|
1426
1416
|
ctx.textBaseline = 'top';
|
|
1427
1417
|
}
|
|
1428
1418
|
|
|
1429
|
-
/**
|
|
1419
|
+
/** Framework logos (FW_LOGOS paths in their brand colours) on a disc, drawn
|
|
1420
|
+
* at icon size (s ≈ node radius). Logos too dark for the dashboard sit on
|
|
1421
|
+
* a light disc. Unknown tags fall back to a lettered disc. */
|
|
1422
|
+
const fwPaths = {};
|
|
1430
1423
|
function drawFwMark(ctx, fw, x, y, s, dead) {
|
|
1431
1424
|
if (ROLE_MARKS[fw]) return drawRoleMark(ctx, ROLE_MARKS[fw], x, y, s, dead);
|
|
1425
|
+
const logo = FW_LOGOS[fw];
|
|
1432
1426
|
ctx.save();
|
|
1433
1427
|
if (dead) ctx.globalAlpha = 0.35;
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
// nested V — green outer chevron, dark inner chevron
|
|
1451
|
-
const h = s * 0.92;
|
|
1452
|
-
ctx.beginPath();
|
|
1453
|
-
ctx.moveTo(x - s, y - h); ctx.lineTo(x + s, y - h); ctx.lineTo(x, y + h);
|
|
1454
|
-
ctx.closePath(); ctx.fillStyle = '#42b883'; ctx.fill();
|
|
1455
|
-
ctx.beginPath();
|
|
1456
|
-
ctx.moveTo(x - s * 0.58, y - h); ctx.lineTo(x + s * 0.58, y - h); ctx.lineTo(x, y + h * 0.14);
|
|
1457
|
-
ctx.closePath(); ctx.fillStyle = '#35495e'; ctx.fill();
|
|
1458
|
-
break;
|
|
1459
|
-
}
|
|
1460
|
-
case 'svelte': {
|
|
1461
|
-
ctx.beginPath(); ctx.arc(x, y, s, 0, 7);
|
|
1462
|
-
ctx.fillStyle = '#ff3e00'; ctx.fill();
|
|
1463
|
-
ctx.font = `bold ${s * 1.3}px ui-monospace, monospace`;
|
|
1464
|
-
ctx.fillStyle = '#fff'; ctx.textBaseline = 'middle';
|
|
1465
|
-
ctx.fillText('S', x, y + s * 0.06);
|
|
1466
|
-
break;
|
|
1467
|
-
}
|
|
1468
|
-
case 'angular': {
|
|
1469
|
-
// shield + A
|
|
1470
|
-
ctx.beginPath();
|
|
1471
|
-
ctx.moveTo(x, y - s); ctx.lineTo(x + s * 0.95, y - s * 0.62);
|
|
1472
|
-
ctx.lineTo(x + s * 0.78, y + s * 0.72); ctx.lineTo(x, y + s);
|
|
1473
|
-
ctx.lineTo(x - s * 0.78, y + s * 0.72); ctx.lineTo(x - s * 0.95, y - s * 0.62);
|
|
1474
|
-
ctx.closePath(); ctx.fillStyle = '#dd0031'; ctx.fill();
|
|
1475
|
-
ctx.font = `bold ${s * 1.15}px ui-monospace, monospace`;
|
|
1476
|
-
ctx.fillStyle = '#fff'; ctx.textBaseline = 'middle';
|
|
1477
|
-
ctx.fillText('A', x, y + s * 0.1);
|
|
1478
|
-
break;
|
|
1479
|
-
}
|
|
1480
|
-
case 'solid': {
|
|
1481
|
-
ctx.beginPath(); ctx.arc(x, y, s, 0, 7);
|
|
1482
|
-
ctx.fillStyle = '#4f88c6'; ctx.fill();
|
|
1483
|
-
ctx.font = `bold ${s * 1.15}px ui-monospace, monospace`;
|
|
1484
|
-
ctx.fillStyle = '#fff'; ctx.textBaseline = 'middle';
|
|
1485
|
-
ctx.fillText('S', x, y + s * 0.06);
|
|
1486
|
-
break;
|
|
1487
|
-
}
|
|
1488
|
-
case 'vanilla': {
|
|
1489
|
-
// the JS badge — yellow square, black JS
|
|
1490
|
-
ctx.beginPath();
|
|
1491
|
-
ctx.roundRect(x - s, y - s, s * 2, s * 2, s * 0.14);
|
|
1492
|
-
ctx.fillStyle = '#f0db4f'; ctx.fill();
|
|
1493
|
-
ctx.font = `bold ${s * 0.85}px ui-monospace, monospace`;
|
|
1494
|
-
ctx.fillStyle = '#0d1117'; ctx.textBaseline = 'middle';
|
|
1495
|
-
ctx.fillText('JS', x, y + s * 0.18);
|
|
1496
|
-
break;
|
|
1497
|
-
}
|
|
1498
|
-
case 'leaflet': {
|
|
1499
|
-
// green rounded square + white leaf
|
|
1500
|
-
ctx.beginPath();
|
|
1501
|
-
ctx.roundRect(x - s, y - s, s * 2, s * 2, s * 0.25);
|
|
1502
|
-
ctx.fillStyle = '#19993b'; ctx.fill();
|
|
1503
|
-
ctx.beginPath();
|
|
1504
|
-
ctx.ellipse(x, y - s * 0.08, s * 0.3, s * 0.58, Math.PI / 4, 0, 7);
|
|
1505
|
-
ctx.fillStyle = '#fff'; ctx.fill();
|
|
1506
|
-
ctx.beginPath();
|
|
1507
|
-
ctx.moveTo(x - s * 0.45, y + s * 0.42); ctx.lineTo(x + s * 0.45, y - s * 0.48);
|
|
1508
|
-
ctx.strokeStyle = '#19993b'; ctx.lineWidth = s * 0.07; ctx.stroke();
|
|
1509
|
-
break;
|
|
1510
|
-
}
|
|
1511
|
-
default: {
|
|
1512
|
-
// tagged but unknown framework — green disc + first letter
|
|
1513
|
-
ctx.beginPath(); ctx.arc(x, y, s * 0.85, 0, 7);
|
|
1514
|
-
ctx.fillStyle = dead ? '#484f58' : '#3fb950'; ctx.fill();
|
|
1515
|
-
ctx.font = `bold ${s * 0.9}px ui-monospace, monospace`;
|
|
1516
|
-
ctx.fillStyle = '#0d1117'; ctx.textBaseline = 'middle';
|
|
1517
|
-
ctx.fillText(fw[0].toUpperCase(), x, y);
|
|
1518
|
-
}
|
|
1428
|
+
if (logo && typeof Path2D === 'function') {
|
|
1429
|
+
const n = parseInt(logo.hex.slice(1), 16);
|
|
1430
|
+
const lum = (0.299 * (n >> 16) + 0.587 * ((n >> 8) & 255) + 0.114 * (n & 255)) / 255;
|
|
1431
|
+
ctx.beginPath(); ctx.arc(x, y, s, 0, 7);
|
|
1432
|
+
ctx.fillStyle = lum < 0.35 ? '#f0f3f6' : '#22272e'; ctx.fill();
|
|
1433
|
+
const k = (s * 1.3) / 24;
|
|
1434
|
+
ctx.translate(x - 12 * k, y - 12 * k);
|
|
1435
|
+
ctx.scale(k, k);
|
|
1436
|
+
ctx.fillStyle = logo.hex;
|
|
1437
|
+
ctx.fill(fwPaths[fw] ??= new Path2D(logo.d));
|
|
1438
|
+
} else {
|
|
1439
|
+
ctx.beginPath(); ctx.arc(x, y, s * 0.85, 0, 7);
|
|
1440
|
+
ctx.fillStyle = dead ? '#484f58' : '#3fb950'; ctx.fill();
|
|
1441
|
+
ctx.font = `bold ${s * 0.9}px ui-monospace, monospace`;
|
|
1442
|
+
ctx.fillStyle = '#0d1117'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
|
1443
|
+
ctx.fillText(fw[0].toUpperCase(), x, y);
|
|
1519
1444
|
}
|
|
1520
1445
|
ctx.restore();
|
|
1521
1446
|
ctx.textBaseline = 'top';
|
|
@@ -217,7 +217,17 @@ function connectWebSocket(opts, session) {
|
|
|
217
217
|
|
|
218
218
|
// src/overlay.ts
|
|
219
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";
|
|
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
|
+
};
|
|
221
231
|
var ANCHOR = {
|
|
222
232
|
topleft: (m) => `left:${m}px;top:${m}px`,
|
|
223
233
|
topcenter: (m) => `left:50%;top:${m}px;transform:translateX(-50%)`,
|
|
@@ -306,19 +316,44 @@ function mountDevtoolsOverlay(opts = {}) {
|
|
|
306
316
|
frame.src = src;
|
|
307
317
|
frame.title = "atoll devtools dashboard";
|
|
308
318
|
frame.style.cssText = "flex:1;border:0;background:#0d1117;min-height:0";
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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);
|
|
313
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()));
|
|
314
333
|
const placeAt = (left, top) => {
|
|
315
|
-
panel.style.
|
|
316
|
-
panel.style.
|
|
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`;
|
|
317
337
|
panel.style.right = "auto";
|
|
318
338
|
panel.style.bottom = "auto";
|
|
319
339
|
panel.style.transform = "none";
|
|
320
340
|
};
|
|
321
|
-
|
|
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
|
+
}
|
|
322
357
|
const mem = { ...saved };
|
|
323
358
|
const save = (what) => {
|
|
324
359
|
if (!storeKey) return;
|
|
@@ -327,7 +362,7 @@ function mountDevtoolsOverlay(opts = {}) {
|
|
|
327
362
|
mem.width = parseFloat(panel.style.width);
|
|
328
363
|
mem.height = parseFloat(panel.style.height);
|
|
329
364
|
}
|
|
330
|
-
if (what === "move") {
|
|
365
|
+
if (what === "move" || what === "size") {
|
|
331
366
|
mem.left = parseFloat(panel.style.left);
|
|
332
367
|
mem.top = parseFloat(panel.style.top);
|
|
333
368
|
}
|
|
@@ -338,13 +373,9 @@ function mountDevtoolsOverlay(opts = {}) {
|
|
|
338
373
|
};
|
|
339
374
|
const drag = (e, what, move) => {
|
|
340
375
|
e.preventDefault();
|
|
341
|
-
|
|
376
|
+
const sx = e.clientX, sy = e.clientY;
|
|
342
377
|
frame.style.pointerEvents = "none";
|
|
343
|
-
const mm = (ev) =>
|
|
344
|
-
move(ev.clientX - lx, ev.clientY - ly);
|
|
345
|
-
lx = ev.clientX;
|
|
346
|
-
ly = ev.clientY;
|
|
347
|
-
};
|
|
378
|
+
const mm = (ev) => move(ev.clientX - sx, ev.clientY - sy);
|
|
348
379
|
const up = () => {
|
|
349
380
|
removeEventListener("pointermove", mm);
|
|
350
381
|
removeEventListener("pointerup", up);
|
|
@@ -356,23 +387,43 @@ function mountDevtoolsOverlay(opts = {}) {
|
|
|
356
387
|
};
|
|
357
388
|
head.onpointerdown = (e) => {
|
|
358
389
|
if (e.target.closest("a,button")) return;
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
};
|
|
364
|
-
grip.onpointerdown = (e) => {
|
|
365
|
-
e.stopPropagation();
|
|
366
|
-
drag(e, "size", (dx, dy) => {
|
|
367
|
-
const r = panel.getBoundingClientRect();
|
|
368
|
-
panel.style.width = `${Math.max(MIN_W, r.width + dx)}px`;
|
|
369
|
-
panel.style.height = `${Math.max(MIN_H, r.height + dy)}px`;
|
|
370
|
-
});
|
|
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));
|
|
371
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
|
+
}
|
|
372
422
|
const set = (v, focus = false, persist = true) => {
|
|
373
423
|
const hadFocus = panel.contains(document.activeElement);
|
|
374
424
|
open = v;
|
|
375
425
|
panel.style.display = v ? "flex" : "none";
|
|
426
|
+
if (v) refit();
|
|
376
427
|
btn.style.display = v ? "none" : "block";
|
|
377
428
|
btn.setAttribute("aria-expanded", String(v));
|
|
378
429
|
if (v && focus) frame.focus();
|
|
@@ -394,8 +445,14 @@ function mountDevtoolsOverlay(opts = {}) {
|
|
|
394
445
|
route = d.hash;
|
|
395
446
|
}
|
|
396
447
|
};
|
|
448
|
+
const onResize = () => {
|
|
449
|
+
if (open) refit();
|
|
450
|
+
};
|
|
397
451
|
if (isHotkey) addEventListener("keydown", onKey);
|
|
398
452
|
addEventListener("message", onMessage);
|
|
453
|
+
addEventListener("resize", onResize);
|
|
454
|
+
const ro = typeof ResizeObserver === "function" ? new ResizeObserver(onResize) : void 0;
|
|
455
|
+
ro?.observe(document.documentElement);
|
|
399
456
|
set(opts.startOpen ?? saved.open === true, false, false);
|
|
400
457
|
return {
|
|
401
458
|
open: () => set(true),
|
|
@@ -404,6 +461,8 @@ function mountDevtoolsOverlay(opts = {}) {
|
|
|
404
461
|
unmount: () => {
|
|
405
462
|
removeEventListener("keydown", onKey);
|
|
406
463
|
removeEventListener("message", onMessage);
|
|
464
|
+
removeEventListener("resize", onResize);
|
|
465
|
+
ro?.disconnect();
|
|
407
466
|
panel.remove();
|
|
408
467
|
btn.remove();
|
|
409
468
|
}
|
package/dist/index.js
CHANGED
package/dist/node.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atolljs/devtools",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Devtools transport + local dashboard server for AtollJS instrumentation events",
|
|
5
5
|
"homepage": "https://jwhenry3.github.io/atolljs/consumer/quickstart/",
|
|
6
6
|
"type": "module",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"registry": "https://registry.npmjs.org"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@atolljs/core": "0.1.
|
|
42
|
+
"@atolljs/core": "0.1.10"
|
|
43
43
|
},
|
|
44
44
|
"repository": {
|
|
45
45
|
"type": "git",
|
package/src/overlay.ts
CHANGED
|
@@ -66,7 +66,21 @@ const BTN_STYLE =
|
|
|
66
66
|
const PANEL_STYLE =
|
|
67
67
|
'position:fixed;z-index:2147483646;display:flex;flex-direction:column;' +
|
|
68
68
|
'background:#0d1117;border:1px solid #30363d;border-radius:8px;' +
|
|
69
|
-
'box-shadow:0 12px 40px #000c;overflow:hidden;min-width:340px;min-height:220px'
|
|
69
|
+
'box-shadow:0 12px 40px #000c;overflow:hidden;min-width:340px;min-height:220px;' +
|
|
70
|
+
'max-width:100vw;max-height:100vh;box-sizing:border-box';
|
|
71
|
+
|
|
72
|
+
type Edge = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw';
|
|
73
|
+
const EDGE_CSS: Record<Edge, string> = {
|
|
74
|
+
n: 'top:0;left:10px;right:10px;height:4px;cursor:ns-resize',
|
|
75
|
+
s: 'bottom:0;left:10px;right:16px;height:5px;cursor:ns-resize',
|
|
76
|
+
e: 'right:0;top:10px;bottom:16px;width:5px;cursor:ew-resize',
|
|
77
|
+
w: 'left:0;top:10px;bottom:10px;width:5px;cursor:ew-resize',
|
|
78
|
+
ne: 'right:0;top:0;width:10px;height:10px;cursor:nesw-resize',
|
|
79
|
+
nw: 'left:0;top:0;width:10px;height:10px;cursor:nwse-resize',
|
|
80
|
+
sw: 'left:0;bottom:0;width:10px;height:10px;cursor:nesw-resize',
|
|
81
|
+
se: 'right:0;bottom:0;width:16px;height:16px;cursor:nwse-resize;' +
|
|
82
|
+
'background:linear-gradient(135deg,transparent 50%,#30363d 50%);border-radius:0 0 7px',
|
|
83
|
+
};
|
|
70
84
|
|
|
71
85
|
const ANCHOR: Record<DevtoolsOverlayPosition, (m: number) => string> = {
|
|
72
86
|
topleft: (m) => `left:${m}px;top:${m}px`,
|
|
@@ -173,24 +187,51 @@ export function mountDevtoolsOverlay(opts: DevtoolsOverlayOptions = {}): Devtool
|
|
|
173
187
|
frame.title = 'atoll devtools dashboard';
|
|
174
188
|
frame.style.cssText = 'flex:1;border:0;background:#0d1117;min-height:0';
|
|
175
189
|
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
'
|
|
181
|
-
|
|
182
|
-
|
|
190
|
+
// Manual resize handles on every edge and corner (CSS `resize` doesn't work
|
|
191
|
+
// over an iframe). The visible se grip stays the panel's last child.
|
|
192
|
+
const handles = (['n', 's', 'e', 'w', 'ne', 'nw', 'sw', 'se'] as const).map((edge) => {
|
|
193
|
+
const h = document.createElement('div');
|
|
194
|
+
h.setAttribute('aria-hidden', 'true');
|
|
195
|
+
h.dataset.edge = edge;
|
|
196
|
+
h.style.cssText = `position:absolute;z-index:1;${EDGE_CSS[edge]}`;
|
|
197
|
+
return h;
|
|
198
|
+
});
|
|
199
|
+
panel.append(head, frame, ...handles);
|
|
183
200
|
document.body.append(panel, btn);
|
|
184
201
|
|
|
202
|
+
const px = (v: string) => parseFloat(v) || 0;
|
|
203
|
+
const vw = () => document.documentElement.clientWidth || innerWidth;
|
|
204
|
+
const vh = () => document.documentElement.clientHeight || innerHeight;
|
|
205
|
+
const fitW = (w: number) => Math.max(MIN_W, Math.min(w, vw()));
|
|
206
|
+
const fitH = (h: number) => Math.max(MIN_H, Math.min(h, vh()));
|
|
207
|
+
|
|
208
|
+
/** Explicit left/top, clamped so the whole panel (close button included) stays in the viewport. */
|
|
185
209
|
const placeAt = (left: number, top: number) => {
|
|
186
|
-
|
|
187
|
-
panel.style.left = `${Math.min(Math.max(0, left), Math.max(0,
|
|
188
|
-
panel.style.top = `${Math.min(Math.max(0, top), Math.max(0,
|
|
210
|
+
const w = px(panel.style.width), h = px(panel.style.height);
|
|
211
|
+
panel.style.left = `${Math.min(Math.max(0, left), Math.max(0, vw() - w))}px`;
|
|
212
|
+
panel.style.top = `${Math.min(Math.max(0, top), Math.max(0, vh() - h))}px`;
|
|
189
213
|
panel.style.right = 'auto';
|
|
190
214
|
panel.style.bottom = 'auto';
|
|
191
215
|
panel.style.transform = 'none'; // drop the center anchor's translateX
|
|
192
216
|
};
|
|
193
|
-
|
|
217
|
+
const pinned = () => panel.style.right === 'auto';
|
|
218
|
+
/** Swap a corner/center anchor for explicit left/top at the panel's current spot. */
|
|
219
|
+
const pin = () => {
|
|
220
|
+
if (pinned()) return;
|
|
221
|
+
const r = panel.getBoundingClientRect();
|
|
222
|
+
placeAt(r.left, r.top);
|
|
223
|
+
};
|
|
224
|
+
/** Shrink to the viewport and pull back on screen (window resized, or reopened smaller). */
|
|
225
|
+
const refit = () => {
|
|
226
|
+
panel.style.width = `${fitW(px(panel.style.width))}px`;
|
|
227
|
+
panel.style.height = `${fitH(px(panel.style.height))}px`;
|
|
228
|
+
if (pinned()) placeAt(px(panel.style.left), px(panel.style.top));
|
|
229
|
+
};
|
|
230
|
+
if (typeof saved.left === 'number' && typeof saved.top === 'number') {
|
|
231
|
+
panel.style.width = `${fitW(W)}px`;
|
|
232
|
+
panel.style.height = `${fitH(H)}px`;
|
|
233
|
+
placeAt(saved.left, saved.top);
|
|
234
|
+
}
|
|
194
235
|
|
|
195
236
|
// Only what the user changed is remembered: an untouched size/position
|
|
196
237
|
// keeps following the options.
|
|
@@ -202,23 +243,21 @@ export function mountDevtoolsOverlay(opts: DevtoolsOverlayOptions = {}): Devtool
|
|
|
202
243
|
mem.width = parseFloat(panel.style.width);
|
|
203
244
|
mem.height = parseFloat(panel.style.height);
|
|
204
245
|
}
|
|
205
|
-
|
|
246
|
+
// resizing pins the panel and a w/n edge moves it, so size saves position too
|
|
247
|
+
if (what === 'move' || what === 'size') {
|
|
206
248
|
mem.left = parseFloat(panel.style.left);
|
|
207
249
|
mem.top = parseFloat(panel.style.top);
|
|
208
250
|
}
|
|
209
251
|
try { localStorage.setItem(storeKey, JSON.stringify(mem)); } catch { /* storage off */ }
|
|
210
252
|
};
|
|
211
253
|
|
|
212
|
-
/** Pointer-drag helper: run `move`
|
|
254
|
+
/** Pointer-drag helper: run `move` with the total offset from the press until pointerup. */
|
|
213
255
|
const drag = (e: PointerEvent, what: 'move' | 'size', move: (dx: number, dy: number) => void) => {
|
|
214
256
|
e.preventDefault();
|
|
215
|
-
|
|
257
|
+
const sx = e.clientX, sy = e.clientY;
|
|
216
258
|
// the iframe would swallow pointermove while the cursor is over it
|
|
217
259
|
frame.style.pointerEvents = 'none';
|
|
218
|
-
const mm = (ev: PointerEvent) =>
|
|
219
|
-
move(ev.clientX - lx, ev.clientY - ly);
|
|
220
|
-
lx = ev.clientX; ly = ev.clientY;
|
|
221
|
-
};
|
|
260
|
+
const mm = (ev: PointerEvent) => move(ev.clientX - sx, ev.clientY - sy);
|
|
222
261
|
const up = () => {
|
|
223
262
|
removeEventListener('pointermove', mm);
|
|
224
263
|
removeEventListener('pointerup', up);
|
|
@@ -231,25 +270,45 @@ export function mountDevtoolsOverlay(opts: DevtoolsOverlayOptions = {}): Devtool
|
|
|
231
270
|
|
|
232
271
|
head.onpointerdown = (e) => {
|
|
233
272
|
if ((e.target as HTMLElement).closest('a,button')) return;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
273
|
+
pin();
|
|
274
|
+
refit();
|
|
275
|
+
const l0 = px(panel.style.left), t0 = px(panel.style.top);
|
|
276
|
+
drag(e, 'move', (dx, dy) => placeAt(l0 + dx, t0 + dy));
|
|
238
277
|
};
|
|
239
278
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
279
|
+
for (const h of handles) {
|
|
280
|
+
const edge = h.dataset.edge as Edge;
|
|
281
|
+
h.onpointerdown = (e) => {
|
|
282
|
+
e.stopPropagation();
|
|
283
|
+
pin();
|
|
284
|
+
refit();
|
|
285
|
+
const l0 = px(panel.style.left), t0 = px(panel.style.top);
|
|
286
|
+
const w0 = px(panel.style.width), h0 = px(panel.style.height);
|
|
287
|
+
drag(e, 'size', (dx, dy) => {
|
|
288
|
+
let left = l0, top = t0, w = w0, hh = h0;
|
|
289
|
+
if (edge.includes('e')) w = Math.max(MIN_W, Math.min(w0 + dx, vw() - l0));
|
|
290
|
+
if (edge.includes('s')) hh = Math.max(MIN_H, Math.min(h0 + dy, vh() - t0));
|
|
291
|
+
if (edge.includes('w')) {
|
|
292
|
+
left = Math.max(0, Math.min(l0 + dx, l0 + w0 - MIN_W));
|
|
293
|
+
w = l0 + w0 - left;
|
|
294
|
+
}
|
|
295
|
+
if (edge.includes('n')) {
|
|
296
|
+
top = Math.max(0, Math.min(t0 + dy, t0 + h0 - MIN_H));
|
|
297
|
+
hh = t0 + h0 - top;
|
|
298
|
+
}
|
|
299
|
+
panel.style.width = `${w}px`;
|
|
300
|
+
panel.style.height = `${hh}px`;
|
|
301
|
+
panel.style.left = `${left}px`;
|
|
302
|
+
panel.style.top = `${top}px`;
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
}
|
|
248
306
|
|
|
249
307
|
const set = (v: boolean, focus = false, persist = true) => {
|
|
250
308
|
const hadFocus = panel.contains(document.activeElement);
|
|
251
309
|
open = v;
|
|
252
310
|
panel.style.display = v ? 'flex' : 'none';
|
|
311
|
+
if (v) refit();
|
|
253
312
|
btn.style.display = v ? 'none' : 'block';
|
|
254
313
|
btn.setAttribute('aria-expanded', String(v));
|
|
255
314
|
if (v && focus) frame.focus();
|
|
@@ -272,8 +331,13 @@ export function mountDevtoolsOverlay(opts: DevtoolsOverlayOptions = {}): Devtool
|
|
|
272
331
|
route = d.hash;
|
|
273
332
|
}
|
|
274
333
|
};
|
|
334
|
+
const onResize = () => { if (open) refit(); };
|
|
275
335
|
if (isHotkey) addEventListener('keydown', onKey);
|
|
276
336
|
addEventListener('message', onMessage);
|
|
337
|
+
addEventListener('resize', onResize);
|
|
338
|
+
// a page scrollbar appearing shrinks the visible width without a window resize event
|
|
339
|
+
const ro = typeof ResizeObserver === 'function' ? new ResizeObserver(onResize) : undefined;
|
|
340
|
+
ro?.observe(document.documentElement);
|
|
277
341
|
|
|
278
342
|
set(opts.startOpen ?? saved.open === true, false, false);
|
|
279
343
|
|
|
@@ -284,6 +348,8 @@ export function mountDevtoolsOverlay(opts: DevtoolsOverlayOptions = {}): Devtool
|
|
|
284
348
|
unmount: () => {
|
|
285
349
|
removeEventListener('keydown', onKey);
|
|
286
350
|
removeEventListener('message', onMessage);
|
|
351
|
+
removeEventListener('resize', onResize);
|
|
352
|
+
ro?.disconnect();
|
|
287
353
|
panel.remove();
|
|
288
354
|
btn.remove();
|
|
289
355
|
},
|