@preact/signals-devtools-ui 0.3.0 → 0.4.1
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/CHANGELOG.md +17 -0
- package/dist/devtools-ui.js +1 -1
- package/dist/devtools-ui.js.map +1 -1
- package/dist/devtools-ui.min.js +1 -1
- package/dist/devtools-ui.min.js.map +1 -1
- package/dist/devtools-ui.mjs +1 -1
- package/dist/devtools-ui.mjs.map +1 -1
- package/dist/devtools-ui.module.js +1 -1
- package/dist/devtools-ui.module.js.map +1 -1
- package/dist/styles.css +445 -113
- package/package.json +3 -3
- package/src/components/Graph.tsx +13 -14
- package/src/components/Header.tsx +21 -1
- package/src/components/UpdateItem.tsx +5 -2
- package/src/context.ts +75 -3
- package/src/styles.css +445 -113
- package/src/types.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@preact/signals-devtools-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "DevTools UI components for @preact/signals",
|
|
6
6
|
"keywords": [
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"README.md"
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@preact/signals-devtools-adapter": "0.
|
|
47
|
+
"@preact/signals-devtools-adapter": "0.4.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/node": "^20.0.0",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"typescript": "~5.8.3",
|
|
55
55
|
"vite": "^7.0.0",
|
|
56
56
|
"vitest": "^4.0.17",
|
|
57
|
-
"@preact/signals": "2.
|
|
57
|
+
"@preact/signals": "2.8.0"
|
|
58
58
|
},
|
|
59
59
|
"publishConfig": {
|
|
60
60
|
"access": "public",
|
package/src/components/Graph.tsx
CHANGED
|
@@ -212,7 +212,8 @@ export function GraphVisualization() {
|
|
|
212
212
|
if (!update.signalId) continue;
|
|
213
213
|
if (!showDisposed && disposed.has(update.signalId)) continue;
|
|
214
214
|
|
|
215
|
-
const type: "signal" | "computed" | "effect" =
|
|
215
|
+
const type: "signal" | "computed" | "effect" | "component" =
|
|
216
|
+
update.signalType;
|
|
216
217
|
|
|
217
218
|
if (!nodes.has(update.signalId)) {
|
|
218
219
|
nodes.set(update.signalId, {
|
|
@@ -424,6 +425,9 @@ export function GraphVisualization() {
|
|
|
424
425
|
case "effect":
|
|
425
426
|
lines.push(` ${id}([${name}])`);
|
|
426
427
|
break;
|
|
428
|
+
case "component":
|
|
429
|
+
lines.push(` ${id}{{${name}}}`);
|
|
430
|
+
break;
|
|
427
431
|
}
|
|
428
432
|
});
|
|
429
433
|
|
|
@@ -489,7 +493,7 @@ export function GraphVisualization() {
|
|
|
489
493
|
refY="3"
|
|
490
494
|
orient="auto"
|
|
491
495
|
>
|
|
492
|
-
<polygon points="0 0, 8 3, 0 6"
|
|
496
|
+
<polygon points="0 0, 8 3, 0 6" className="graph-arrowhead" />
|
|
493
497
|
</marker>
|
|
494
498
|
</defs>
|
|
495
499
|
|
|
@@ -635,26 +639,21 @@ export function GraphVisualization() {
|
|
|
635
639
|
|
|
636
640
|
<div className="graph-legend">
|
|
637
641
|
<div className="legend-item">
|
|
638
|
-
<div
|
|
639
|
-
className="legend-color"
|
|
640
|
-
style={{ backgroundColor: "#2196f3" }}
|
|
641
|
-
></div>
|
|
642
|
+
<div className="legend-color signal"></div>
|
|
642
643
|
<span>Signal</span>
|
|
643
644
|
</div>
|
|
644
645
|
<div className="legend-item">
|
|
645
|
-
<div
|
|
646
|
-
className="legend-color"
|
|
647
|
-
style={{ backgroundColor: "#ff9800" }}
|
|
648
|
-
></div>
|
|
646
|
+
<div className="legend-color computed"></div>
|
|
649
647
|
<span>Computed</span>
|
|
650
648
|
</div>
|
|
651
649
|
<div className="legend-item">
|
|
652
|
-
<div
|
|
653
|
-
className="legend-color"
|
|
654
|
-
style={{ backgroundColor: "#4caf50" }}
|
|
655
|
-
></div>
|
|
650
|
+
<div className="legend-color effect"></div>
|
|
656
651
|
<span>Effect</span>
|
|
657
652
|
</div>
|
|
653
|
+
<div className="legend-item">
|
|
654
|
+
<div className="legend-color component"></div>
|
|
655
|
+
<span>Component</span>
|
|
656
|
+
</div>
|
|
658
657
|
</div>
|
|
659
658
|
</div>
|
|
660
659
|
</div>
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { StatusIndicator } from "./StatusIndicator";
|
|
2
2
|
import { Button } from "./Button";
|
|
3
3
|
import { getContext } from "../context";
|
|
4
|
+
import type { ThemeMode } from "../context";
|
|
5
|
+
|
|
6
|
+
const themeLabels: Record<ThemeMode, string> = {
|
|
7
|
+
auto: "Auto",
|
|
8
|
+
light: "Light",
|
|
9
|
+
dark: "Dark",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const themeIcons: Record<ThemeMode, string> = {
|
|
13
|
+
auto: "\u25D1",
|
|
14
|
+
light: "\u2600",
|
|
15
|
+
dark: "\u263E",
|
|
16
|
+
};
|
|
4
17
|
|
|
5
18
|
export function Header() {
|
|
6
|
-
const { connectionStore, updatesStore } = getContext();
|
|
19
|
+
const { connectionStore, updatesStore, themeStore } = getContext();
|
|
7
20
|
|
|
8
21
|
const onTogglePause = () => {
|
|
9
22
|
updatesStore.isPaused.value = !updatesStore.isPaused.value;
|
|
@@ -23,6 +36,13 @@ export function Header() {
|
|
|
23
36
|
/>
|
|
24
37
|
</div>
|
|
25
38
|
<div className="header-controls">
|
|
39
|
+
<button
|
|
40
|
+
className="theme-toggle"
|
|
41
|
+
onClick={themeStore.toggleTheme}
|
|
42
|
+
title={`Theme: ${themeLabels[themeStore.theme]}`}
|
|
43
|
+
>
|
|
44
|
+
{themeIcons[themeStore.theme]} {themeLabels[themeStore.theme]}
|
|
45
|
+
</button>
|
|
26
46
|
{onClear && <Button onClick={onClear}>Clear</Button>}
|
|
27
47
|
{onTogglePause && (
|
|
28
48
|
<Button onClick={onTogglePause} active={updatesStore.isPaused.value}>
|
|
@@ -31,14 +31,17 @@ export function UpdateItem({ update, count, firstUpdate }: UpdateItemProps) {
|
|
|
31
31
|
</span>
|
|
32
32
|
);
|
|
33
33
|
|
|
34
|
-
if (update.type === "effect") {
|
|
34
|
+
if (update.type === "effect" || update.type === "component") {
|
|
35
|
+
const icon = update.type === "component" ? "🔄" : "↪️";
|
|
36
|
+
const label = update.type === "component" ? "Component render" : "Effect";
|
|
35
37
|
return (
|
|
36
38
|
<div className={`update-item ${update.type}`}>
|
|
37
39
|
<div className="update-header">
|
|
38
40
|
<span className="signal-name">
|
|
39
|
-
|
|
41
|
+
{icon} {update.signalName}
|
|
40
42
|
{countLabel}
|
|
41
43
|
</span>
|
|
44
|
+
<span className="update-type-badge">{label}</span>
|
|
42
45
|
<span className="update-time">{time}</span>
|
|
43
46
|
</div>
|
|
44
47
|
</div>
|
package/src/context.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { signal, computed } from "@preact/signals";
|
|
1
|
+
import { signal, computed, effect } from "@preact/signals";
|
|
2
2
|
import type {
|
|
3
3
|
DevToolsAdapter,
|
|
4
4
|
ConnectionStatus,
|
|
@@ -8,11 +8,14 @@ import type {
|
|
|
8
8
|
DependencyInfo,
|
|
9
9
|
} from "@preact/signals-devtools-adapter";
|
|
10
10
|
|
|
11
|
+
export type ThemeMode = "auto" | "light" | "dark";
|
|
12
|
+
|
|
11
13
|
export interface DevToolsContext {
|
|
12
14
|
adapter: DevToolsAdapter;
|
|
13
15
|
connectionStore: ReturnType<typeof createConnectionStore>;
|
|
14
16
|
updatesStore: ReturnType<typeof createUpdatesStore>;
|
|
15
17
|
settingsStore: ReturnType<typeof createSettingsStore>;
|
|
18
|
+
themeStore: ReturnType<typeof createThemeStore>;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
let currentContext: DevToolsContext | null = null;
|
|
@@ -65,8 +68,8 @@ export function createConnectionStore(adapter: DevToolsAdapter) {
|
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
export interface SignalUpdate {
|
|
68
|
-
type: "update" | "effect";
|
|
69
|
-
signalType: "signal" | "computed" | "effect";
|
|
71
|
+
type: "update" | "effect" | "component";
|
|
72
|
+
signalType: "signal" | "computed" | "effect" | "component";
|
|
70
73
|
signalName: string;
|
|
71
74
|
signalId?: string;
|
|
72
75
|
prevValue?: any;
|
|
@@ -325,16 +328,85 @@ export function createSettingsStore(adapter: DevToolsAdapter) {
|
|
|
325
328
|
};
|
|
326
329
|
}
|
|
327
330
|
|
|
331
|
+
const THEME_STORAGE_KEY = "signals-devtools-theme";
|
|
332
|
+
|
|
333
|
+
export function createThemeStore() {
|
|
334
|
+
const stored = (() => {
|
|
335
|
+
try {
|
|
336
|
+
const val = localStorage.getItem(THEME_STORAGE_KEY);
|
|
337
|
+
if (val === "light" || val === "dark" || val === "auto") return val;
|
|
338
|
+
} catch {
|
|
339
|
+
// localStorage unavailable
|
|
340
|
+
}
|
|
341
|
+
return "auto" as ThemeMode;
|
|
342
|
+
})();
|
|
343
|
+
|
|
344
|
+
const theme = signal<ThemeMode>(stored);
|
|
345
|
+
|
|
346
|
+
const mediaQuery =
|
|
347
|
+
typeof window !== "undefined"
|
|
348
|
+
? window.matchMedia("(prefers-color-scheme: dark)")
|
|
349
|
+
: null;
|
|
350
|
+
const systemIsDark = signal(mediaQuery?.matches ?? false);
|
|
351
|
+
|
|
352
|
+
if (mediaQuery) {
|
|
353
|
+
const handler = (e: MediaQueryListEvent) => {
|
|
354
|
+
systemIsDark.value = e.matches;
|
|
355
|
+
};
|
|
356
|
+
mediaQuery.addEventListener("change", handler);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const resolvedTheme = computed<"light" | "dark">(() =>
|
|
360
|
+
theme.value === "auto"
|
|
361
|
+
? systemIsDark.value
|
|
362
|
+
? "dark"
|
|
363
|
+
: "light"
|
|
364
|
+
: theme.value
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
// Apply data-theme attribute to the devtools container
|
|
368
|
+
effect(() => {
|
|
369
|
+
const resolved = resolvedTheme.value;
|
|
370
|
+
const el = document.querySelector(".signals-devtools");
|
|
371
|
+
if (el instanceof HTMLElement) {
|
|
372
|
+
el.dataset.theme = resolved;
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const toggleTheme = () => {
|
|
377
|
+
const order: ThemeMode[] = ["auto", "light", "dark"];
|
|
378
|
+
const idx = order.indexOf(theme.value);
|
|
379
|
+
theme.value = order[(idx + 1) % order.length];
|
|
380
|
+
try {
|
|
381
|
+
localStorage.setItem(THEME_STORAGE_KEY, theme.value);
|
|
382
|
+
} catch {
|
|
383
|
+
// localStorage unavailable
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
get theme() {
|
|
389
|
+
return theme.value;
|
|
390
|
+
},
|
|
391
|
+
get resolvedTheme() {
|
|
392
|
+
return resolvedTheme.value;
|
|
393
|
+
},
|
|
394
|
+
toggleTheme,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
328
398
|
export function initDevTools(adapter: DevToolsAdapter): DevToolsContext {
|
|
329
399
|
const settingsStore = createSettingsStore(adapter);
|
|
330
400
|
const updatesStore = createUpdatesStore(adapter, settingsStore);
|
|
331
401
|
const connectionStore = createConnectionStore(adapter);
|
|
402
|
+
const themeStore = createThemeStore();
|
|
332
403
|
|
|
333
404
|
currentContext = {
|
|
334
405
|
adapter,
|
|
335
406
|
connectionStore,
|
|
336
407
|
updatesStore,
|
|
337
408
|
settingsStore,
|
|
409
|
+
themeStore,
|
|
338
410
|
};
|
|
339
411
|
|
|
340
412
|
return currentContext;
|