@preact/signals-devtools-ui 0.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/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@preact/signals-devtools-ui",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "description": "DevTools UI components for @preact/signals",
6
+ "keywords": [
7
+ "preact",
8
+ "signals",
9
+ "devtools",
10
+ "ui"
11
+ ],
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/preactjs/signals",
15
+ "directory": "packages/devtools-ui"
16
+ },
17
+ "bugs": "https://github.com/preactjs/signals/issues",
18
+ "homepage": "https://preactjs.com",
19
+ "funding": {
20
+ "type": "opencollective",
21
+ "url": "https://opencollective.com/preact"
22
+ },
23
+ "amdName": "preactSignalsDevtoolsUI",
24
+ "main": "dist/devtools-ui.js",
25
+ "module": "dist/devtools-ui.module.js",
26
+ "unpkg": "dist/devtools-ui.min.js",
27
+ "types": "dist/devtools-ui.d.ts",
28
+ "source": "src/index.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/devtools-ui.d.ts",
32
+ "browser": "./dist/devtools-ui.module.js",
33
+ "import": "./dist/devtools-ui.mjs",
34
+ "require": "./dist/devtools-ui.js"
35
+ },
36
+ "./styles": "./dist/styles.css"
37
+ },
38
+ "mangle": "../../mangle.json",
39
+ "files": [
40
+ "src",
41
+ "dist",
42
+ "CHANGELOG.md",
43
+ "LICENSE",
44
+ "README.md"
45
+ ],
46
+ "dependencies": {
47
+ "@preact/signals-devtools-adapter": "0.1.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^18.19.103",
51
+ "@vitest/browser": "^3.2.4",
52
+ "playwright": "^1.53.1",
53
+ "preact": "^10.26.9",
54
+ "typescript": "~5.8.3",
55
+ "vite": "^6.3.5",
56
+ "vitest": "^3.2.4",
57
+ "@preact/signals": "2.6.0"
58
+ },
59
+ "publishConfig": {
60
+ "access": "public",
61
+ "provenance": false
62
+ },
63
+ "scripts": {
64
+ "test": "vitest run"
65
+ }
66
+ }
@@ -0,0 +1,107 @@
1
+ import { render } from "preact";
2
+ import { useSignal } from "@preact/signals";
3
+ import type { DevToolsAdapter } from "@preact/signals-devtools-adapter";
4
+ import { EmptyState } from "./components/EmptyState";
5
+ import { Header } from "./components/Header";
6
+ import { SettingsPanel } from "./components/SettingsPanel";
7
+ import { GraphVisualization } from "./components/Graph";
8
+ import { UpdatesContainer } from "./components/UpdatesContainer";
9
+ import { initDevTools, destroyDevTools, getContext } from "./context";
10
+
11
+ export interface DevToolsPanelProps {
12
+ /** Hide the header (useful for embedded contexts) */
13
+ hideHeader?: boolean;
14
+ /** Initial tab to show */
15
+ initialTab?: "updates" | "graph";
16
+ }
17
+
18
+ export function DevToolsPanel({
19
+ hideHeader = false,
20
+ initialTab = "updates",
21
+ }: DevToolsPanelProps = {}) {
22
+ const { connectionStore } = getContext();
23
+ const activeTab = useSignal<"updates" | "graph">(initialTab);
24
+
25
+ return (
26
+ <div id="app" className="signals-devtools">
27
+ {!hideHeader && <Header />}
28
+
29
+ <SettingsPanel />
30
+
31
+ <main className="main-content">
32
+ <div className="tabs">
33
+ <button
34
+ className={`tab ${activeTab.value === "updates" ? "active" : ""}`}
35
+ onClick={() => (activeTab.value = "updates")}
36
+ >
37
+ Updates
38
+ </button>
39
+ <button
40
+ className={`tab ${activeTab.value === "graph" ? "active" : ""}`}
41
+ onClick={() => (activeTab.value = "graph")}
42
+ >
43
+ Dependency Graph
44
+ </button>
45
+ </div>
46
+ <div className="tab-content">
47
+ {!connectionStore.isConnected ? (
48
+ <EmptyState onRefresh={connectionStore.refreshConnection} />
49
+ ) : (
50
+ <>
51
+ {activeTab.value === "updates" && <UpdatesContainer />}
52
+ {activeTab.value === "graph" && <GraphVisualization />}
53
+ </>
54
+ )}
55
+ </div>
56
+ </main>
57
+ </div>
58
+ );
59
+ }
60
+
61
+ export interface MountOptions extends DevToolsPanelProps {
62
+ /** The adapter to use for communication */
63
+ adapter: DevToolsAdapter;
64
+ /** The container element to render into */
65
+ container: HTMLElement;
66
+ }
67
+
68
+ /**
69
+ * Mount the DevTools UI into a container element.
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * import { mount } from "@preact/signals-devtools-ui";
74
+ * import { createDirectAdapter } from "@preact/signals-devtools-adapter";
75
+ *
76
+ * const adapter = createDirectAdapter();
77
+ *
78
+ * const unmount = await mount({
79
+ * adapter,
80
+ * container: document.getElementById("devtools")!,
81
+ * });
82
+ *
83
+ * // Later, to cleanup:
84
+ * unmount();
85
+ * ```
86
+ */
87
+ export async function mount(options: MountOptions): Promise<() => void> {
88
+ const { adapter, container, ...panelProps } = options;
89
+
90
+ // Initialize context with adapter
91
+ initDevTools(adapter);
92
+
93
+ // Connect the adapter
94
+ await adapter.connect();
95
+
96
+ // Clear existing content
97
+ container.innerHTML = "";
98
+
99
+ // Render the panel
100
+ render(<DevToolsPanel {...panelProps} />, container);
101
+
102
+ // Return cleanup function
103
+ return () => {
104
+ render(null, container);
105
+ destroyDevTools();
106
+ };
107
+ }
@@ -0,0 +1,31 @@
1
+ import type { ComponentChildren } from "preact";
2
+
3
+ interface ButtonProps {
4
+ onClick: () => void;
5
+ className?: string;
6
+ disabled?: boolean;
7
+ children: ComponentChildren;
8
+ variant?: "primary" | "secondary";
9
+ active?: boolean;
10
+ }
11
+
12
+ export function Button({
13
+ onClick,
14
+ className = "",
15
+ disabled = false,
16
+ children,
17
+ variant = "secondary",
18
+ active = false,
19
+ }: ButtonProps) {
20
+ const baseClass = "btn";
21
+ const variantClass = variant === "primary" ? "btn-primary" : "btn-secondary";
22
+ const activeClass = active ? "active" : "";
23
+ const combinedClassName =
24
+ `${baseClass} ${variantClass} ${activeClass} ${className}`.trim();
25
+
26
+ return (
27
+ <button onClick={onClick} className={combinedClassName} disabled={disabled}>
28
+ {children}
29
+ </button>
30
+ );
31
+ }
@@ -0,0 +1,29 @@
1
+ import { Button } from "./Button";
2
+
3
+ interface EmptyStateProps {
4
+ onRefresh: () => void;
5
+ title?: string;
6
+ description?: string;
7
+ buttonText?: string;
8
+ }
9
+
10
+ export function EmptyState({
11
+ onRefresh,
12
+ title = "No Signals Detected",
13
+ description = "Make sure your application is using @preact/signals-debug package.",
14
+ buttonText = "Refresh Detection",
15
+ }: EmptyStateProps) {
16
+ return (
17
+ <div className="empty-state">
18
+ <div className="empty-state-content">
19
+ <h2>{title}</h2>
20
+ <p>{description}</p>
21
+ <div className="empty-state-actions">
22
+ <Button onClick={onRefresh} variant="primary">
23
+ {buttonText}
24
+ </Button>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ );
29
+ }