Skip to content

Commit af00fbb

Browse files
authored
Merge branch 'main' into reverts
2 parents 53a3f5e + 1ac2b12 commit af00fbb

File tree

23 files changed

+1074
-526
lines changed

23 files changed

+1074
-526
lines changed

.changeset/five-ligers-hear.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@preact/signals": patch
3+
---
4+
5+
Ensure aria/data attributes stick around when going back to an empty string

.changeset/mean-boats-care.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"preact-signals-devtools": patch
3+
---
4+
5+
Allow dependency graphs to be exported as JSON or mermaid syntax.

.changeset/sixty-rats-pull.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@preact/signals": patch
3+
"@preact/signals-react": patch
4+
---
5+
6+
Ensure `For` and `Show` account for nested reactivity

docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"react-dom": "^18.2.0"
2424
},
2525
"devDependencies": {
26-
"@babel/core": "^7.22.8",
26+
"@babel/core": "^7.28.4",
2727
"@preact/preset-vite": "^2.3.0",
2828
"@types/react": "^18.0.18",
2929
"@types/react-dom": "^18.0.6",

extension/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"name": "Preact Signals DevTools",
44
"version": "1.0.0",
55
"description": "Debug and inspect Preact Signals in your web applications",
6-
"permissions": ["activeTab", "scripting"],
6+
"permissions": ["activeTab", "scripting", "clipboardWrite"],
77
"host_permissions": ["<all_urls>"],
88
"content_scripts": [
99
{

extension/src/components/Graph.tsx

Lines changed: 130 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useRef } from "preact/hooks";
1+
import { useRef, useEffect } from "preact/hooks";
22
import { Signal, computed, useComputed, useSignal } from "@preact/signals";
33
import {
44
Divider,
@@ -10,16 +10,48 @@ import {
1010
} from "../types";
1111
import { updatesStore } from "../models/UpdatesModel";
1212

13+
const copyToClipboard = (text: string) => {
14+
const copyEl = document.createElement("textarea");
15+
try {
16+
copyEl.value = text;
17+
document.body.append(copyEl);
18+
copyEl.select();
19+
document.execCommand("copy");
20+
} finally {
21+
copyEl.remove();
22+
}
23+
};
24+
1325
export function GraphVisualization() {
1426
const updates = updatesStore.updates;
1527
const svgRef = useRef<SVGSVGElement>(null);
1628
const containerRef = useRef<HTMLDivElement>(null);
29+
const exportMenuRef = useRef<HTMLDivElement>(null);
1730

1831
// Pan and zoom state using signals
1932
const panOffset = useSignal({ x: 0, y: 0 });
2033
const zoom = useSignal(1);
2134
const isPanning = useSignal(false);
2235
const startPan = useSignal({ x: 0, y: 0 });
36+
const showExportMenu = useSignal(false);
37+
const toastText = useSignal<string>();
38+
39+
useEffect(() => {
40+
const handleClickOutside = (e: MouseEvent) => {
41+
if (
42+
showExportMenu.value &&
43+
exportMenuRef.current &&
44+
!exportMenuRef.current.contains(e.target as Node)
45+
) {
46+
showExportMenu.value = false;
47+
}
48+
};
49+
50+
document.addEventListener("mousedown", handleClickOutside);
51+
return () => {
52+
document.removeEventListener("mousedown", handleClickOutside);
53+
};
54+
}, []);
2355

2456
// Build graph data from updates signal using a computed
2557
const graphData = useComputed<GraphData>(() => {
@@ -181,6 +213,62 @@ export function GraphVisualization() {
181213
zoom.value = 1;
182214
};
183215

216+
const toggleExportMenu = () => {
217+
showExportMenu.value = !showExportMenu.value;
218+
};
219+
220+
const mermaidIdPattern = /[^a-zA-Z0-9]/g;
221+
const computeMermaidId = (id: string) => id.replace(mermaidIdPattern, "_");
222+
223+
const showToast = (text: string) => {
224+
toastText.value = text;
225+
setTimeout(() => {
226+
toastText.value = undefined;
227+
}, 2000);
228+
};
229+
230+
const handleExportMermaid = async () => {
231+
showExportMenu.value = false;
232+
233+
const lines: string[] = ["graph LR"];
234+
235+
graphData.value.nodes.forEach(node => {
236+
const id = computeMermaidId(node.id);
237+
const name = node.name;
238+
239+
switch (node.type) {
240+
case "signal":
241+
lines.push(` ${id}((${name}))`);
242+
break;
243+
case "computed":
244+
lines.push(` ${id}(${name})`);
245+
break;
246+
case "effect":
247+
lines.push(` ${id}([${name}])`);
248+
break;
249+
case "component":
250+
lines.push(` ${id}[${name}]`);
251+
break;
252+
}
253+
});
254+
255+
for (const link of graphData.value.links) {
256+
const sourceId = computeMermaidId(link.source);
257+
const targetId = computeMermaidId(link.target);
258+
lines.push(` ${sourceId} --> ${targetId}`);
259+
}
260+
261+
copyToClipboard(lines.join("\n"));
262+
showToast("Copied to clipboard!");
263+
};
264+
265+
const handleExportJSON = async () => {
266+
showExportMenu.value = false;
267+
const value = JSON.stringify(graphData.value, null, 2);
268+
copyToClipboard(value);
269+
showToast("Copied to clipboard!");
270+
};
271+
184272
if (graphData.value.nodes.length === 0) {
185273
return (
186274
<div className="graph-empty">
@@ -326,14 +414,47 @@ export function GraphVisualization() {
326414
</g>
327415
</svg>
328416

329-
{/* Reset view button */}
330-
<button
331-
className="graph-reset-button"
332-
onClick={resetView}
333-
title="Reset view"
334-
>
335-
⟲ Reset View
336-
</button>
417+
{/* Control buttons */}
418+
<div className="graph-controls">
419+
<button
420+
className="graph-reset-button"
421+
onClick={resetView}
422+
title="Reset view"
423+
>
424+
⟲ Reset View
425+
</button>
426+
427+
<div ref={exportMenuRef} className="graph-export-container">
428+
<button
429+
className="graph-export-button"
430+
onClick={toggleExportMenu}
431+
title="Export graph"
432+
>
433+
↓ Export
434+
</button>
435+
{showExportMenu.value && (
436+
<div className="graph-export-menu">
437+
<button
438+
className="graph-export-menu-item"
439+
onClick={handleExportMermaid}
440+
>
441+
Mermaid
442+
</button>
443+
<button
444+
className="graph-export-menu-item"
445+
onClick={handleExportJSON}
446+
>
447+
JSON
448+
</button>
449+
</div>
450+
)}
451+
</div>
452+
</div>
453+
454+
{/* Toast notification */}
455+
{toastText.value && (
456+
<div className="graph-toast">{toastText.value}</div>
457+
)}
337458

338459
{/* Legend */}
339460
<div className="graph-legend">

extension/styles/panel.css

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -423,10 +423,17 @@ body {
423423
min-height: 500px;
424424
}
425425

426-
.graph-reset-button {
426+
.graph-controls {
427427
position: absolute;
428428
top: 16px;
429429
left: 16px;
430+
display: flex;
431+
gap: 8px;
432+
z-index: 10;
433+
}
434+
435+
.graph-reset-button,
436+
.graph-export-button {
430437
background: white;
431438
border: 1px solid #e0e0e0;
432439
border-radius: 4px;
@@ -436,19 +443,62 @@ body {
436443
cursor: pointer;
437444
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
438445
transition: all 0.2s;
439-
z-index: 10;
440446
}
441447

442-
.graph-reset-button:hover {
448+
.graph-reset-button:hover,
449+
.graph-export-button:hover {
443450
background: #f5f5f5;
444451
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
445452
}
446453

447-
.graph-reset-button:active {
454+
.graph-reset-button:active,
455+
.graph-export-button:active {
448456
background: #eeeeee;
449457
transform: translateY(1px);
450458
}
451459

460+
.graph-export-container {
461+
position: relative;
462+
}
463+
464+
.graph-export-menu {
465+
position: absolute;
466+
top: 100%;
467+
left: 0;
468+
margin-top: 4px;
469+
background: white;
470+
border: 1px solid #e0e0e0;
471+
border-radius: 4px;
472+
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
473+
overflow: hidden;
474+
min-width: 180px;
475+
}
476+
477+
.graph-export-menu-item {
478+
display: block;
479+
width: 100%;
480+
background: white;
481+
border: none;
482+
padding: 10px 12px;
483+
font-size: 12px;
484+
text-align: left;
485+
cursor: pointer;
486+
transition: background 0.2s;
487+
white-space: nowrap;
488+
}
489+
490+
.graph-export-menu-item:hover {
491+
background: #f5f5f5;
492+
}
493+
494+
.graph-export-menu-item:active {
495+
background: #eeeeee;
496+
}
497+
498+
.graph-export-menu-item:not(:last-child) {
499+
border-bottom: 1px solid #e0e0e0;
500+
}
501+
452502
.graph-node {
453503
cursor: pointer;
454504
transition: all 0.2s;
@@ -551,6 +601,32 @@ body {
551601
color: #666;
552602
}
553603

604+
.graph-toast {
605+
position: absolute;
606+
top: 16px;
607+
right: 16px;
608+
background: #323232;
609+
color: white;
610+
padding: 12px 16px;
611+
border-radius: 4px;
612+
font-size: 13px;
613+
font-weight: 500;
614+
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
615+
z-index: 1000;
616+
animation: slideInFade 0.3s ease-out;
617+
}
618+
619+
@keyframes slideInFade {
620+
from {
621+
opacity: 0;
622+
transform: translateY(-8px);
623+
}
624+
to {
625+
opacity: 1;
626+
transform: translateY(0);
627+
}
628+
}
629+
554630
/* Tree Node Styles */
555631
.tree-node {
556632
margin-bottom: 4px;

karma.conf.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ var localLaunchers = {
3232

3333
const subPkgPath = pkgName => {
3434
if (!minify) {
35+
if (pkgName.includes("preact/utils") || pkgName.includes("react/utils")) {
36+
return path.join(__dirname, pkgName, "src", "index.tsx");
37+
}
3538
return path.join(__dirname, pkgName, "src", "index.ts");
3639
}
3740

@@ -185,7 +188,7 @@ function createEsbuildPlugin(filteredPkgList) {
185188
];
186189

187190
const explicitResourceManagement = [
188-
"@babel/plugin-proposal-explicit-resource-management",
191+
"@babel/plugin-transform-explicit-resource-management",
189192
];
190193

191194
const tmp = await babel.transformAsync(result, {

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,20 @@
4949
],
5050
"license": "MIT",
5151
"devDependencies": {
52-
"@babel/core": "^7.27.7",
53-
"@babel/plugin-proposal-explicit-resource-management": "^7.27.4",
52+
"@babel/core": "^7.28.4",
53+
"@babel/plugin-transform-explicit-resource-management": "^7.28.0",
5454
"@babel/plugin-syntax-jsx": "^7.27.1",
5555
"@babel/plugin-transform-modules-commonjs": "^7.27.1",
5656
"@babel/plugin-transform-react-jsx": "^7.27.1",
57-
"@babel/plugin-transform-typescript": "^7.27.1",
58-
"@babel/preset-env": "^7.27.2",
57+
"@babel/plugin-transform-typescript": "^7.28.0",
58+
"@babel/preset-env": "^7.28.3",
5959
"@babel/preset-react": "^7.27.1",
6060
"@babel/preset-typescript": "^7.27.1",
61-
"@babel/register": "^7.27.1",
62-
"@babel/standalone": "^7.27.7",
61+
"@babel/register": "^7.28.3",
62+
"@babel/standalone": "^7.28.4",
6363
"@changesets/changelog-github": "^0.5.0",
6464
"@changesets/cli": "^2.27.1",
65-
"@types/babel__traverse": "^7.18.5",
65+
"@types/babel__traverse": "^7.28.0",
6666
"@types/chai": "^4.3.3",
6767
"@types/mocha": "^9.1.1",
6868
"@types/node": "^18.19.103",

0 commit comments

Comments
 (0)