@shadow-js/devtools 0.0.0-stage → 1.0.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/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +576 -2
- package/dist/dev/agent.d.ts +239 -0
- package/dist/dev/agent.js +626 -0
- package/dist/dev/agent.js.map +1 -0
- package/dist/dev/backend.d.ts +53 -0
- package/dist/dev/backend.js +53 -0
- package/dist/dev/backend.js.map +1 -0
- package/dist/dev/hook.d.ts +83 -0
- package/dist/dev/hook.js +136 -0
- package/dist/dev/hook.js.map +1 -0
- package/dist/dev/index.d.ts +17 -0
- package/dist/dev/index.js +17 -0
- package/dist/dev/inspection.d.ts +226 -0
- package/dist/dev/inspection.js +263 -0
- package/dist/dev/inspection.js.map +1 -0
- package/dist/dev/inspector.d.ts +331 -0
- package/dist/dev/inspector.js +429 -0
- package/dist/dev/inspector.js.map +1 -0
- package/dist/dev/model.d.ts +418 -0
- package/dist/dev/model.js +559 -0
- package/dist/dev/model.js.map +1 -0
- package/dist/dev/mount.d.ts +207 -0
- package/dist/dev/mount.js +256 -0
- package/dist/dev/mount.js.map +1 -0
- package/dist/dev/overlay.d.ts +102 -0
- package/dist/dev/overlay.js +176 -0
- package/dist/dev/overlay.js.map +1 -0
- package/dist/dev/preview.d.ts +74 -0
- package/dist/dev/preview.js +122 -0
- package/dist/dev/preview.js.map +1 -0
- package/dist/dev/protocol.d.ts +288 -0
- package/dist/dev/protocol.js +109 -0
- package/dist/dev/protocol.js.map +1 -0
- package/dist/dev/ring.d.ts +50 -0
- package/dist/dev/ring.js +94 -0
- package/dist/dev/ring.js.map +1 -0
- package/dist/dev/transport.d.ts +347 -0
- package/dist/dev/transport.js +394 -0
- package/dist/dev/transport.js.map +1 -0
- package/dist/dev/ui/details.js +294 -0
- package/dist/dev/ui/details.js.map +1 -0
- package/dist/dev/ui/inspection-state.d.ts +43 -0
- package/dist/dev/ui/inspection-state.js +62 -0
- package/dist/dev/ui/inspection-state.js.map +1 -0
- package/dist/dev/ui/issues.js +85 -0
- package/dist/dev/ui/issues.js.map +1 -0
- package/dist/dev/ui/panel.d.ts +55 -0
- package/dist/dev/ui/panel.js +160 -0
- package/dist/dev/ui/panel.js.map +1 -0
- package/dist/dev/ui/rows.d.ts +44 -0
- package/dist/dev/ui/rows.js +132 -0
- package/dist/dev/ui/rows.js.map +1 -0
- package/dist/dev/ui/state-tab.js +88 -0
- package/dist/dev/ui/state-tab.js.map +1 -0
- package/dist/dev/ui/state.d.ts +192 -0
- package/dist/dev/ui/state.js +147 -0
- package/dist/dev/ui/state.js.map +1 -0
- package/dist/dev/ui/styles.d.ts +31 -0
- package/dist/dev/ui/styles.js +113 -0
- package/dist/dev/ui/styles.js.map +1 -0
- package/dist/dev/ui/timeline.js +135 -0
- package/dist/dev/ui/timeline.js.map +1 -0
- package/dist/dev/ui/tree.js +186 -0
- package/dist/dev/ui/tree.js.map +1 -0
- package/dist/prod/agent.js +2 -0
- package/dist/prod/agent.js.map +1 -0
- package/dist/prod/backend.js +2 -0
- package/dist/prod/backend.js.map +1 -0
- package/dist/prod/hook.js +2 -0
- package/dist/prod/hook.js.map +1 -0
- package/dist/prod/index.js +1 -0
- package/dist/prod/inspection.js +2 -0
- package/dist/prod/inspection.js.map +1 -0
- package/dist/prod/inspector.js +2 -0
- package/dist/prod/inspector.js.map +1 -0
- package/dist/prod/model.js +2 -0
- package/dist/prod/model.js.map +1 -0
- package/dist/prod/mount.js +2 -0
- package/dist/prod/mount.js.map +1 -0
- package/dist/prod/overlay.js +11 -0
- package/dist/prod/overlay.js.map +1 -0
- package/dist/prod/preview.js +2 -0
- package/dist/prod/preview.js.map +1 -0
- package/dist/prod/protocol.js +2 -0
- package/dist/prod/protocol.js.map +1 -0
- package/dist/prod/ring.js +2 -0
- package/dist/prod/ring.js.map +1 -0
- package/dist/prod/transport.js +2 -0
- package/dist/prod/transport.js.map +1 -0
- package/dist/prod/ui/details.js +2 -0
- package/dist/prod/ui/details.js.map +1 -0
- package/dist/prod/ui/inspection-state.js +2 -0
- package/dist/prod/ui/inspection-state.js.map +1 -0
- package/dist/prod/ui/issues.js +2 -0
- package/dist/prod/ui/issues.js.map +1 -0
- package/dist/prod/ui/panel.js +2 -0
- package/dist/prod/ui/panel.js.map +1 -0
- package/dist/prod/ui/rows.js +2 -0
- package/dist/prod/ui/rows.js.map +1 -0
- package/dist/prod/ui/state-tab.js +2 -0
- package/dist/prod/ui/state-tab.js.map +1 -0
- package/dist/prod/ui/state.js +2 -0
- package/dist/prod/ui/state.js.map +1 -0
- package/dist/prod/ui/styles.js +82 -0
- package/dist/prod/ui/styles.js.map +1 -0
- package/dist/prod/ui/timeline.js +2 -0
- package/dist/prod/ui/timeline.js.map +1 -0
- package/dist/prod/ui/tree.js +2 -0
- package/dist/prod/ui/tree.js.map +1 -0
- package/package.json +69 -4
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { DevtoolsHook } from "./protocol.js";
|
|
2
|
+
import { Agent } from "./agent.js";
|
|
3
|
+
import { Backend } from "./backend.js";
|
|
4
|
+
import { Transport } from "./transport.js";
|
|
5
|
+
import { TabId } from "./ui/state.js";
|
|
6
|
+
//#region src/mount.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Options of {@link mountDevtools}. Every field is optional.
|
|
9
|
+
*
|
|
10
|
+
* @since 1.0.0
|
|
11
|
+
*/
|
|
12
|
+
export interface MountOptions {
|
|
13
|
+
/**
|
|
14
|
+
* The hook to listen to. When unset, the package uses the hook {@link installHook} returns.
|
|
15
|
+
*
|
|
16
|
+
* @default installHook()
|
|
17
|
+
*/
|
|
18
|
+
hook?: DevtoolsHook;
|
|
19
|
+
/**
|
|
20
|
+
* Use this agent instead of creating one. It must already be attached to the hook, or be attached by
|
|
21
|
+
* you: the package attaches only the agent it creates.
|
|
22
|
+
*
|
|
23
|
+
* @default createAgent({ writeSignal })
|
|
24
|
+
*/
|
|
25
|
+
agent?: Agent;
|
|
26
|
+
/**
|
|
27
|
+
* Where the overlay host element is appended.
|
|
28
|
+
*
|
|
29
|
+
* @default document.body
|
|
30
|
+
*/
|
|
31
|
+
container?: Element;
|
|
32
|
+
/**
|
|
33
|
+
* The document to use.
|
|
34
|
+
*
|
|
35
|
+
* @default globalThis.document
|
|
36
|
+
*/
|
|
37
|
+
document?: Document;
|
|
38
|
+
/**
|
|
39
|
+
* Start with the panel open. When false, only the launcher button shows.
|
|
40
|
+
*
|
|
41
|
+
* @default false
|
|
42
|
+
*/
|
|
43
|
+
open?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Keyboard shortcut that toggles the panel, such as `"Alt+Shift+D"`. Pass `false` to turn the shortcut
|
|
46
|
+
* off.
|
|
47
|
+
*
|
|
48
|
+
* @default "Alt+Shift+D"
|
|
49
|
+
*/
|
|
50
|
+
toggleKey?: string | false;
|
|
51
|
+
/**
|
|
52
|
+
* Panel position in pixels from the top-left corner of the window.
|
|
53
|
+
*
|
|
54
|
+
* @default { x: 16, y: 16 }
|
|
55
|
+
*/
|
|
56
|
+
position?: {
|
|
57
|
+
x: number;
|
|
58
|
+
y: number;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Flash components when they update. The panel's Flash button changes this at runtime.
|
|
62
|
+
*
|
|
63
|
+
* @default true
|
|
64
|
+
*/
|
|
65
|
+
flash?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* The tab shown first.
|
|
68
|
+
*
|
|
69
|
+
* @default "tree"
|
|
70
|
+
*/
|
|
71
|
+
tab?: TabId;
|
|
72
|
+
/**
|
|
73
|
+
* Also serve the agent over this transport, for a remote panel in an extension or another tab. See
|
|
74
|
+
* {@link serveAgent}.
|
|
75
|
+
*/
|
|
76
|
+
serve?: Transport;
|
|
77
|
+
/**
|
|
78
|
+
* Show the launcher button that opens the panel.
|
|
79
|
+
*
|
|
80
|
+
* @default true
|
|
81
|
+
*/
|
|
82
|
+
launcher?: boolean;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The handle `mountDevtools()` returns. Every member is a no-op in production builds, where `host`,
|
|
86
|
+
* `root`, `agent` and `backend` are `null`.
|
|
87
|
+
*
|
|
88
|
+
* @since 1.0.0
|
|
89
|
+
*/
|
|
90
|
+
export interface DevtoolsHandle {
|
|
91
|
+
/**
|
|
92
|
+
* The shadow host element the panel is appended to, or `null` when the tools are off.
|
|
93
|
+
*/
|
|
94
|
+
readonly host: HTMLElement | null;
|
|
95
|
+
/**
|
|
96
|
+
* The shadow root holding the panel, or `null` when the tools are off.
|
|
97
|
+
*/
|
|
98
|
+
readonly root: ShadowRoot | null;
|
|
99
|
+
/**
|
|
100
|
+
* The agent the panel reads from, or `null` when the tools are off.
|
|
101
|
+
*/
|
|
102
|
+
readonly agent: Agent | null;
|
|
103
|
+
/**
|
|
104
|
+
* The backend the panel reads from, or `null` when the tools are off.
|
|
105
|
+
*/
|
|
106
|
+
readonly backend: Backend | null;
|
|
107
|
+
/**
|
|
108
|
+
* Whether the panel is open. Each read returns the current value.
|
|
109
|
+
*/
|
|
110
|
+
readonly isOpen: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Opens the panel and applies the change now.
|
|
113
|
+
*/
|
|
114
|
+
open(): void;
|
|
115
|
+
/**
|
|
116
|
+
* Closes the panel and applies the change now.
|
|
117
|
+
*/
|
|
118
|
+
close(): void;
|
|
119
|
+
/**
|
|
120
|
+
* Opens the panel when it is closed, and closes it when it is open. Applies the change now.
|
|
121
|
+
*/
|
|
122
|
+
toggle(): void;
|
|
123
|
+
/**
|
|
124
|
+
* Moves the panel to `x` and `y` pixels from the top-left corner. The position is clamped to the
|
|
125
|
+
* window.
|
|
126
|
+
*/
|
|
127
|
+
moveTo(x: number, y: number): void;
|
|
128
|
+
/**
|
|
129
|
+
* Shows a tab and applies the change now.
|
|
130
|
+
*/
|
|
131
|
+
setTab(tab: TabId): void;
|
|
132
|
+
/**
|
|
133
|
+
* Selects a node in the tree.
|
|
134
|
+
*/
|
|
135
|
+
select(id: number): void;
|
|
136
|
+
/**
|
|
137
|
+
* Applies pending model changes to the panel now. The panel normally refreshes once per frame.
|
|
138
|
+
*/
|
|
139
|
+
flush(): void;
|
|
140
|
+
/**
|
|
141
|
+
* Removes the panel, the overlay and every listener, and stops serving the agent. When
|
|
142
|
+
* `mountDevtools()` created the agent, it is disposed too. Calling this again does nothing.
|
|
143
|
+
*/
|
|
144
|
+
dispose(): void;
|
|
145
|
+
}
|
|
146
|
+
/** A parsed keyboard shortcut. */
|
|
147
|
+
interface Shortcut {
|
|
148
|
+
alt: boolean;
|
|
149
|
+
ctrl: boolean;
|
|
150
|
+
shift: boolean;
|
|
151
|
+
meta: boolean;
|
|
152
|
+
key: string;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Parses `"Alt+Shift+D"` style shortcuts. Modifier names are case-insensitive: `alt` or `option`,
|
|
156
|
+
* `ctrl` or `control`, `shift`, and `meta`, `cmd` or `command`. The last part is the key, compared
|
|
157
|
+
* case-insensitively with `event.key`.
|
|
158
|
+
*
|
|
159
|
+
* @param spec The shortcut text.
|
|
160
|
+
* @returns The parsed shortcut, or `null` when it has no key.
|
|
161
|
+
* @example
|
|
162
|
+
* import { parseShortcut } from "@shadow-js/devtools";
|
|
163
|
+
* const shortcut = parseShortcut("Ctrl+Shift+K"); // { ctrl: true, shift: true, alt: false, meta: false, key: "k" }
|
|
164
|
+
* @since 1.0.0
|
|
165
|
+
*/
|
|
166
|
+
export declare function parseShortcut(spec: string): Shortcut | null;
|
|
167
|
+
/**
|
|
168
|
+
* True when a keyboard event is the shortcut. The modifiers must match exactly. The key matches
|
|
169
|
+
* `event.key`, or, for a single letter, `event.code` (such as `KeyD`), because Alt and Shift change the
|
|
170
|
+
* character on some keyboard layouts.
|
|
171
|
+
*
|
|
172
|
+
* @param event The event.
|
|
173
|
+
* @param shortcut From {@link parseShortcut}.
|
|
174
|
+
* @returns True when the event is the shortcut.
|
|
175
|
+
* @example
|
|
176
|
+
* import { matchesShortcut, parseShortcut } from "@shadow-js/devtools";
|
|
177
|
+
* const toggle = parseShortcut("Alt+Shift+D");
|
|
178
|
+
* document.addEventListener("keydown", (event) => {
|
|
179
|
+
* if (toggle !== null && matchesShortcut(event, toggle)) console.log("toggle the panel");
|
|
180
|
+
* });
|
|
181
|
+
* @since 1.0.0
|
|
182
|
+
*/
|
|
183
|
+
export declare function matchesShortcut(event: Pick<KeyboardEvent, "key" | "code" | "altKey" | "ctrlKey" | "shiftKey" | "metaKey">, shortcut: Shortcut): boolean;
|
|
184
|
+
/**
|
|
185
|
+
* Shows the devtools: a floating, draggable panel in a shadow root (so page styles cannot touch it)
|
|
186
|
+
* with the component tree, a flame timeline of flushes, a state viewer with live editing, and an
|
|
187
|
+
* issues list. Hovering a row highlights the component on the page; components flash when they
|
|
188
|
+
* update. Toggle with `Alt+Shift+D`, or with the `toggleKey` option.
|
|
189
|
+
*
|
|
190
|
+
* This is a development tool: the panel is for development builds and never ships. In production
|
|
191
|
+
* builds (`__DEV__` false), on the server, or when another protocol version owns the hook, this does
|
|
192
|
+
* nothing and returns a handle of no-ops. Call it before your application renders so no event is
|
|
193
|
+
* missed.
|
|
194
|
+
*
|
|
195
|
+
* @param options See {@link MountOptions}.
|
|
196
|
+
* @returns A handle to control or remove the panel.
|
|
197
|
+
* @example
|
|
198
|
+
* import { mountDevtools } from "@shadow-js/devtools";
|
|
199
|
+
* // Development builds only: production builds ignore this call.
|
|
200
|
+
* const devtools = mountDevtools({ open: true });
|
|
201
|
+
* devtools.toggle();
|
|
202
|
+
* @since 1.0.0
|
|
203
|
+
*/
|
|
204
|
+
export declare function mountDevtools(options?: MountOptions): DevtoolsHandle;
|
|
205
|
+
//#endregion
|
|
206
|
+
export {};
|
|
207
|
+
//# sourceMappingURL=mount.d.ts.map
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { installHook } from "./hook.js";
|
|
2
|
+
import { createAgent } from "./agent.js";
|
|
3
|
+
import { createLocalBackend } from "./backend.js";
|
|
4
|
+
import { createOverlay } from "./overlay.js";
|
|
5
|
+
import { serveAgent } from "./transport.js";
|
|
6
|
+
import { createPanelState } from "./ui/state.js";
|
|
7
|
+
import { Panel } from "./ui/panel.js";
|
|
8
|
+
import { LAUNCHER_CSS, PANEL_CSS } from "./ui/styles.js";
|
|
9
|
+
import { addEventListener, createComponent, effect, render, template } from "@shadow-js/web";
|
|
10
|
+
import { flushSync, useSignal } from "@shadow-js/core";
|
|
11
|
+
import { writeSignal } from "@shadow-js/core/extend";
|
|
12
|
+
//#region src/mount.tsx
|
|
13
|
+
const _tmpl$ = /*#__PURE__*/ template("<button type=\"button\" class=\"launcher\" aria-label=\"Open ShadowJS devtools\">S</button>");
|
|
14
|
+
/**
|
|
15
|
+
* Parses `"Alt+Shift+D"` style shortcuts. Modifier names are case-insensitive: `alt` or `option`,
|
|
16
|
+
* `ctrl` or `control`, `shift`, and `meta`, `cmd` or `command`. The last part is the key, compared
|
|
17
|
+
* case-insensitively with `event.key`.
|
|
18
|
+
*
|
|
19
|
+
* @param spec The shortcut text.
|
|
20
|
+
* @returns The parsed shortcut, or `null` when it has no key.
|
|
21
|
+
* @example
|
|
22
|
+
* import { parseShortcut } from "@shadow-js/devtools";
|
|
23
|
+
* const shortcut = parseShortcut("Ctrl+Shift+K"); // { ctrl: true, shift: true, alt: false, meta: false, key: "k" }
|
|
24
|
+
* @since 1.0.0
|
|
25
|
+
*/
|
|
26
|
+
function parseShortcut(spec) {
|
|
27
|
+
const parts = spec.split("+").map((part) => part.trim().toLowerCase()).filter((part) => part !== "");
|
|
28
|
+
const key = parts.pop();
|
|
29
|
+
if (key === void 0) return null;
|
|
30
|
+
const has = (...names) => parts.some((part) => names.includes(part));
|
|
31
|
+
return {
|
|
32
|
+
alt: has("alt", "option"),
|
|
33
|
+
ctrl: has("ctrl", "control"),
|
|
34
|
+
shift: has("shift"),
|
|
35
|
+
meta: has("meta", "cmd", "command"),
|
|
36
|
+
key
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* True when a keyboard event is the shortcut. The modifiers must match exactly. The key matches
|
|
41
|
+
* `event.key`, or, for a single letter, `event.code` (such as `KeyD`), because Alt and Shift change the
|
|
42
|
+
* character on some keyboard layouts.
|
|
43
|
+
*
|
|
44
|
+
* @param event The event.
|
|
45
|
+
* @param shortcut From {@link parseShortcut}.
|
|
46
|
+
* @returns True when the event is the shortcut.
|
|
47
|
+
* @example
|
|
48
|
+
* import { matchesShortcut, parseShortcut } from "@shadow-js/devtools";
|
|
49
|
+
* const toggle = parseShortcut("Alt+Shift+D");
|
|
50
|
+
* document.addEventListener("keydown", (event) => {
|
|
51
|
+
* if (toggle !== null && matchesShortcut(event, toggle)) console.log("toggle the panel");
|
|
52
|
+
* });
|
|
53
|
+
* @since 1.0.0
|
|
54
|
+
*/
|
|
55
|
+
function matchesShortcut(event, shortcut) {
|
|
56
|
+
if (event.altKey !== shortcut.alt || event.ctrlKey !== shortcut.ctrl || event.shiftKey !== shortcut.shift || event.metaKey !== shortcut.meta) return false;
|
|
57
|
+
if (event.key.toLowerCase() === shortcut.key) return true;
|
|
58
|
+
return shortcut.key.length === 1 && event.code.toLowerCase() === `key${shortcut.key}`;
|
|
59
|
+
}
|
|
60
|
+
function noop() {}
|
|
61
|
+
const OFF = {
|
|
62
|
+
host: null,
|
|
63
|
+
root: null,
|
|
64
|
+
agent: null,
|
|
65
|
+
backend: null,
|
|
66
|
+
isOpen: false,
|
|
67
|
+
open: noop,
|
|
68
|
+
close: noop,
|
|
69
|
+
toggle: noop,
|
|
70
|
+
moveTo: noop,
|
|
71
|
+
setTab: noop,
|
|
72
|
+
select: noop,
|
|
73
|
+
flush: noop,
|
|
74
|
+
dispose: noop
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Shows the devtools: a floating, draggable panel in a shadow root (so page styles cannot touch it)
|
|
78
|
+
* with the component tree, a flame timeline of flushes, a state viewer with live editing, and an
|
|
79
|
+
* issues list. Hovering a row highlights the component on the page; components flash when they
|
|
80
|
+
* update. Toggle with `Alt+Shift+D`, or with the `toggleKey` option.
|
|
81
|
+
*
|
|
82
|
+
* This is a development tool: the panel is for development builds and never ships. In production
|
|
83
|
+
* builds (`__DEV__` false), on the server, or when another protocol version owns the hook, this does
|
|
84
|
+
* nothing and returns a handle of no-ops. Call it before your application renders so no event is
|
|
85
|
+
* missed.
|
|
86
|
+
*
|
|
87
|
+
* @param options See {@link MountOptions}.
|
|
88
|
+
* @returns A handle to control or remove the panel.
|
|
89
|
+
* @example
|
|
90
|
+
* import { mountDevtools } from "@shadow-js/devtools";
|
|
91
|
+
* // Development builds only: production builds ignore this call.
|
|
92
|
+
* const devtools = mountDevtools({ open: true });
|
|
93
|
+
* devtools.toggle();
|
|
94
|
+
* @since 1.0.0
|
|
95
|
+
*/
|
|
96
|
+
function mountDevtools(options = {}) {
|
|
97
|
+
const doc = options.document ?? (typeof document === "undefined" ? void 0 : document);
|
|
98
|
+
if (doc === void 0) return OFF;
|
|
99
|
+
const hook = options.hook ?? installHook();
|
|
100
|
+
if (hook === void 0) return OFF;
|
|
101
|
+
const ownsAgent = options.agent === void 0;
|
|
102
|
+
const agent = options.agent ?? createAgent({ writeSignal });
|
|
103
|
+
const detach = ownsAgent ? agent.attach(hook) : noop;
|
|
104
|
+
const overlay = createOverlay(agent, {
|
|
105
|
+
document: doc,
|
|
106
|
+
flash: options.flash !== false
|
|
107
|
+
});
|
|
108
|
+
const backend = createLocalBackend(agent, { highlight: (id) => overlay.highlight(id) });
|
|
109
|
+
const stopServing = options.serve === void 0 ? noop : serveAgent(agent, options.serve, { highlight: (id) => overlay.highlight(id) });
|
|
110
|
+
const host = doc.createElement("div");
|
|
111
|
+
host.setAttribute("data-shadowjs-devtools", "");
|
|
112
|
+
const root = host.attachShadow({ mode: "open" });
|
|
113
|
+
const style = doc.createElement("style");
|
|
114
|
+
style.textContent = `${PANEL_CSS}${LAUNCHER_CSS}`;
|
|
115
|
+
root.appendChild(style);
|
|
116
|
+
(options.container ?? doc.body).appendChild(host);
|
|
117
|
+
let state;
|
|
118
|
+
let setOpen = noop;
|
|
119
|
+
let setPosition = noop;
|
|
120
|
+
let isOpen = options.open === true;
|
|
121
|
+
const start = options.position ?? {
|
|
122
|
+
x: 16,
|
|
123
|
+
y: 16
|
|
124
|
+
};
|
|
125
|
+
const unmount = agent.quiet(() => render(() => {
|
|
126
|
+
const [open, setOpenSignal] = useSignal(isOpen);
|
|
127
|
+
const [position, setPositionSignal] = useSignal({
|
|
128
|
+
x: start.x,
|
|
129
|
+
y: start.y
|
|
130
|
+
});
|
|
131
|
+
setOpen = (value) => {
|
|
132
|
+
isOpen = value;
|
|
133
|
+
setOpenSignal(value);
|
|
134
|
+
};
|
|
135
|
+
setPosition = (x, y) => {
|
|
136
|
+
const maxX = Math.max((doc.defaultView?.innerWidth ?? 1024) - 80, 0);
|
|
137
|
+
const maxY = Math.max((doc.defaultView?.innerHeight ?? 768) - 40, 0);
|
|
138
|
+
setPositionSignal({
|
|
139
|
+
x: Math.min(Math.max(x, 0), maxX),
|
|
140
|
+
y: Math.min(Math.max(y, 0), maxY)
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
state = createPanelState(backend, {
|
|
144
|
+
...options.tab === void 0 ? {} : { tab: options.tab },
|
|
145
|
+
flash: options.flash !== false,
|
|
146
|
+
onFlash: (on) => overlay.setFlash(on),
|
|
147
|
+
onPause: (paused) => {
|
|
148
|
+
if (paused) agent.pause();
|
|
149
|
+
else agent.resume();
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
return [createComponent(Panel, {
|
|
153
|
+
state,
|
|
154
|
+
get open() {
|
|
155
|
+
return open();
|
|
156
|
+
},
|
|
157
|
+
get x() {
|
|
158
|
+
return position().x;
|
|
159
|
+
},
|
|
160
|
+
get y() {
|
|
161
|
+
return position().y;
|
|
162
|
+
},
|
|
163
|
+
onMove: (x, y) => setPosition(x, y),
|
|
164
|
+
onClose: () => setOpen(false)
|
|
165
|
+
}, "Panel"), (() => {
|
|
166
|
+
const _el$ = _tmpl$();
|
|
167
|
+
effect(() => {
|
|
168
|
+
_el$.hidden = open() || options.launcher === false;
|
|
169
|
+
});
|
|
170
|
+
addEventListener(_el$, "click", () => setOpen(true));
|
|
171
|
+
return _el$;
|
|
172
|
+
})()];
|
|
173
|
+
}, root));
|
|
174
|
+
let refreshPending = false;
|
|
175
|
+
let frame;
|
|
176
|
+
const refresh = () => {
|
|
177
|
+
refreshPending = false;
|
|
178
|
+
frame = void 0;
|
|
179
|
+
state?.refresh();
|
|
180
|
+
};
|
|
181
|
+
const stopSubscription = backend.subscribe(() => {
|
|
182
|
+
if (refreshPending) return;
|
|
183
|
+
refreshPending = true;
|
|
184
|
+
if (typeof doc.defaultView?.requestAnimationFrame === "function") frame = doc.defaultView.requestAnimationFrame(refresh);
|
|
185
|
+
else queueMicrotask(refresh);
|
|
186
|
+
});
|
|
187
|
+
const shortcut = options.toggleKey === false ? null : parseShortcut(options.toggleKey ?? "Alt+Shift+D");
|
|
188
|
+
const onKey = (event) => {
|
|
189
|
+
if (shortcut === null || !matchesShortcut(event, shortcut)) return;
|
|
190
|
+
event.preventDefault();
|
|
191
|
+
setOpen(!isOpen);
|
|
192
|
+
flushSync();
|
|
193
|
+
};
|
|
194
|
+
doc.addEventListener("keydown", onKey, true);
|
|
195
|
+
const flush = () => {
|
|
196
|
+
agent.flush();
|
|
197
|
+
if (refreshPending) {
|
|
198
|
+
if (frame !== void 0) doc.defaultView?.cancelAnimationFrame(frame);
|
|
199
|
+
refresh();
|
|
200
|
+
}
|
|
201
|
+
flushSync();
|
|
202
|
+
};
|
|
203
|
+
let disposed = false;
|
|
204
|
+
return {
|
|
205
|
+
host,
|
|
206
|
+
root,
|
|
207
|
+
agent,
|
|
208
|
+
backend,
|
|
209
|
+
get isOpen() {
|
|
210
|
+
return isOpen;
|
|
211
|
+
},
|
|
212
|
+
open() {
|
|
213
|
+
setOpen(true);
|
|
214
|
+
flushSync();
|
|
215
|
+
},
|
|
216
|
+
close() {
|
|
217
|
+
setOpen(false);
|
|
218
|
+
flushSync();
|
|
219
|
+
},
|
|
220
|
+
toggle() {
|
|
221
|
+
setOpen(!isOpen);
|
|
222
|
+
flushSync();
|
|
223
|
+
},
|
|
224
|
+
moveTo(x, y) {
|
|
225
|
+
setPosition(x, y);
|
|
226
|
+
flushSync();
|
|
227
|
+
},
|
|
228
|
+
setTab(tab) {
|
|
229
|
+
state?.setTab(tab);
|
|
230
|
+
flushSync();
|
|
231
|
+
},
|
|
232
|
+
select(id) {
|
|
233
|
+
state?.select(id);
|
|
234
|
+
flushSync();
|
|
235
|
+
},
|
|
236
|
+
flush,
|
|
237
|
+
dispose() {
|
|
238
|
+
if (disposed) return;
|
|
239
|
+
disposed = true;
|
|
240
|
+
doc.removeEventListener("keydown", onKey, true);
|
|
241
|
+
stopSubscription();
|
|
242
|
+
if (frame !== void 0) doc.defaultView?.cancelAnimationFrame(frame);
|
|
243
|
+
stopServing();
|
|
244
|
+
unmount();
|
|
245
|
+
overlay.dispose();
|
|
246
|
+
backend.dispose();
|
|
247
|
+
host.remove();
|
|
248
|
+
detach();
|
|
249
|
+
if (ownsAgent) agent.dispose();
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
//#endregion
|
|
254
|
+
export { matchesShortcut, mountDevtools, parseShortcut };
|
|
255
|
+
|
|
256
|
+
//# sourceMappingURL=mount.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount.js","names":["_$createComponent"],"sources":["../../src/mount.tsx"],"sourcesContent":["import { flushSync, useSignal } from \"@shadow-js/core\";\nimport { writeSignal } from \"@shadow-js/core/extend\";\nimport { render } from \"@shadow-js/web\";\nimport { type Agent, createAgent } from \"./agent\";\nimport { type Backend, createLocalBackend } from \"./backend\";\nimport { installHook } from \"./hook\";\nimport { createOverlay } from \"./overlay\";\nimport type { DevtoolsHook } from \"./protocol\";\nimport { serveAgent, type Transport } from \"./transport\";\nimport { Panel } from \"./ui/panel\";\nimport { createPanelState, type PanelState, type TabId } from \"./ui/state\";\nimport { LAUNCHER_CSS, PANEL_CSS } from \"./ui/styles\";\n\n/**\n * Options of {@link mountDevtools}. Every field is optional.\n *\n * @since 1.0.0\n */\nexport interface MountOptions {\n /**\n * The hook to listen to. When unset, the package uses the hook {@link installHook} returns.\n *\n * @default installHook()\n */\n hook?: DevtoolsHook;\n /**\n * Use this agent instead of creating one. It must already be attached to the hook, or be attached by\n * you: the package attaches only the agent it creates.\n *\n * @default createAgent({ writeSignal })\n */\n agent?: Agent;\n /**\n * Where the overlay host element is appended.\n *\n * @default document.body\n */\n container?: Element;\n /**\n * The document to use.\n *\n * @default globalThis.document\n */\n document?: Document;\n /**\n * Start with the panel open. When false, only the launcher button shows.\n *\n * @default false\n */\n open?: boolean;\n /**\n * Keyboard shortcut that toggles the panel, such as `\"Alt+Shift+D\"`. Pass `false` to turn the shortcut\n * off.\n *\n * @default \"Alt+Shift+D\"\n */\n toggleKey?: string | false;\n /**\n * Panel position in pixels from the top-left corner of the window.\n *\n * @default { x: 16, y: 16 }\n */\n position?: { x: number; y: number };\n /**\n * Flash components when they update. The panel's Flash button changes this at runtime.\n *\n * @default true\n */\n flash?: boolean;\n /**\n * The tab shown first.\n *\n * @default \"tree\"\n */\n tab?: TabId;\n /**\n * Also serve the agent over this transport, for a remote panel in an extension or another tab. See\n * {@link serveAgent}.\n */\n serve?: Transport;\n /**\n * Show the launcher button that opens the panel.\n *\n * @default true\n */\n launcher?: boolean;\n}\n\n/**\n * The handle `mountDevtools()` returns. Every member is a no-op in production builds, where `host`,\n * `root`, `agent` and `backend` are `null`.\n *\n * @since 1.0.0\n */\nexport interface DevtoolsHandle {\n /**\n * The shadow host element the panel is appended to, or `null` when the tools are off.\n */\n readonly host: HTMLElement | null;\n /**\n * The shadow root holding the panel, or `null` when the tools are off.\n */\n readonly root: ShadowRoot | null;\n /**\n * The agent the panel reads from, or `null` when the tools are off.\n */\n readonly agent: Agent | null;\n /**\n * The backend the panel reads from, or `null` when the tools are off.\n */\n readonly backend: Backend | null;\n /**\n * Whether the panel is open. Each read returns the current value.\n */\n readonly isOpen: boolean;\n /**\n * Opens the panel and applies the change now.\n */\n open(): void;\n /**\n * Closes the panel and applies the change now.\n */\n close(): void;\n /**\n * Opens the panel when it is closed, and closes it when it is open. Applies the change now.\n */\n toggle(): void;\n /**\n * Moves the panel to `x` and `y` pixels from the top-left corner. The position is clamped to the\n * window.\n */\n moveTo(x: number, y: number): void;\n /**\n * Shows a tab and applies the change now.\n */\n setTab(tab: TabId): void;\n /**\n * Selects a node in the tree.\n */\n select(id: number): void;\n /**\n * Applies pending model changes to the panel now. The panel normally refreshes once per frame.\n */\n flush(): void;\n /**\n * Removes the panel, the overlay and every listener, and stops serving the agent. When\n * `mountDevtools()` created the agent, it is disposed too. Calling this again does nothing.\n */\n dispose(): void;\n}\n\n/** A parsed keyboard shortcut. */\ninterface Shortcut {\n alt: boolean;\n ctrl: boolean;\n shift: boolean;\n meta: boolean;\n key: string;\n}\n\n/**\n * Parses `\"Alt+Shift+D\"` style shortcuts. Modifier names are case-insensitive: `alt` or `option`,\n * `ctrl` or `control`, `shift`, and `meta`, `cmd` or `command`. The last part is the key, compared\n * case-insensitively with `event.key`.\n *\n * @param spec The shortcut text.\n * @returns The parsed shortcut, or `null` when it has no key.\n * @example\n * import { parseShortcut } from \"@shadow-js/devtools\";\n * const shortcut = parseShortcut(\"Ctrl+Shift+K\"); // { ctrl: true, shift: true, alt: false, meta: false, key: \"k\" }\n * @since 1.0.0\n */\nexport function parseShortcut(spec: string): Shortcut | null {\n const parts = spec\n .split(\"+\")\n .map((part) => part.trim().toLowerCase())\n .filter((part) => part !== \"\");\n const key = parts.pop();\n if (key === undefined) return null;\n const has = (...names: string[]): boolean => parts.some((part) => names.includes(part));\n return {\n alt: has(\"alt\", \"option\"),\n ctrl: has(\"ctrl\", \"control\"),\n shift: has(\"shift\"),\n meta: has(\"meta\", \"cmd\", \"command\"),\n key,\n };\n}\n\n/**\n * True when a keyboard event is the shortcut. The modifiers must match exactly. The key matches\n * `event.key`, or, for a single letter, `event.code` (such as `KeyD`), because Alt and Shift change the\n * character on some keyboard layouts.\n *\n * @param event The event.\n * @param shortcut From {@link parseShortcut}.\n * @returns True when the event is the shortcut.\n * @example\n * import { matchesShortcut, parseShortcut } from \"@shadow-js/devtools\";\n * const toggle = parseShortcut(\"Alt+Shift+D\");\n * document.addEventListener(\"keydown\", (event) => {\n * if (toggle !== null && matchesShortcut(event, toggle)) console.log(\"toggle the panel\");\n * });\n * @since 1.0.0\n */\nexport function matchesShortcut(\n event: Pick<KeyboardEvent, \"key\" | \"code\" | \"altKey\" | \"ctrlKey\" | \"shiftKey\" | \"metaKey\">,\n shortcut: Shortcut,\n): boolean {\n if (\n event.altKey !== shortcut.alt ||\n event.ctrlKey !== shortcut.ctrl ||\n event.shiftKey !== shortcut.shift ||\n event.metaKey !== shortcut.meta\n ) {\n return false;\n }\n const key = event.key.toLowerCase();\n if (key === shortcut.key) return true;\n return shortcut.key.length === 1 && event.code.toLowerCase() === `key${shortcut.key}`;\n}\n\nfunction noop(): void {\n // Production and server builds: the tools do nothing.\n}\n\nconst OFF: DevtoolsHandle = {\n host: null,\n root: null,\n agent: null,\n backend: null,\n isOpen: false,\n open: noop,\n close: noop,\n toggle: noop,\n moveTo: noop,\n setTab: noop,\n select: noop,\n flush: noop,\n dispose: noop,\n};\n\n/**\n * Shows the devtools: a floating, draggable panel in a shadow root (so page styles cannot touch it)\n * with the component tree, a flame timeline of flushes, a state viewer with live editing, and an\n * issues list. Hovering a row highlights the component on the page; components flash when they\n * update. Toggle with `Alt+Shift+D`, or with the `toggleKey` option.\n *\n * This is a development tool: the panel is for development builds and never ships. In production\n * builds (`__DEV__` false), on the server, or when another protocol version owns the hook, this does\n * nothing and returns a handle of no-ops. Call it before your application renders so no event is\n * missed.\n *\n * @param options See {@link MountOptions}.\n * @returns A handle to control or remove the panel.\n * @example\n * import { mountDevtools } from \"@shadow-js/devtools\";\n * // Development builds only: production builds ignore this call.\n * const devtools = mountDevtools({ open: true });\n * devtools.toggle();\n * @since 1.0.0\n */\nexport function mountDevtools(options: MountOptions = {}): DevtoolsHandle {\n if (!__DEV__) return OFF;\n const doc = options.document ?? (typeof document === \"undefined\" ? undefined : document);\n if (doc === undefined) return OFF;\n const hook = options.hook ?? installHook();\n if (hook === undefined) return OFF;\n\n const ownsAgent = options.agent === undefined;\n const agent = options.agent ?? createAgent({ writeSignal });\n const detach = ownsAgent ? agent.attach(hook) : noop;\n const overlay = createOverlay(agent, { document: doc, flash: options.flash !== false });\n const backend = createLocalBackend(agent, { highlight: (id) => overlay.highlight(id) });\n const stopServing =\n options.serve === undefined\n ? noop\n : serveAgent(agent, options.serve, { highlight: (id) => overlay.highlight(id) });\n\n const host = doc.createElement(\"div\");\n host.setAttribute(\"data-shadowjs-devtools\", \"\");\n const root = host.attachShadow({ mode: \"open\" });\n const style = doc.createElement(\"style\");\n style.textContent = `${PANEL_CSS}${LAUNCHER_CSS}`;\n root.appendChild(style);\n (options.container ?? doc.body).appendChild(host);\n\n let state: PanelState | undefined;\n let setOpen: (value: boolean) => void = noop;\n let setPosition: (x: number, y: number) => void = noop;\n let isOpen = options.open === true;\n const start = options.position ?? { x: 16, y: 16 };\n\n // Everything the panel creates is invisible to the agent: it must not inspect itself.\n const unmount = agent.quiet(() =>\n render(() => {\n const [open, setOpenSignal] = useSignal(isOpen);\n const [position, setPositionSignal] = useSignal({ x: start.x, y: start.y });\n setOpen = (value) => {\n isOpen = value;\n setOpenSignal(value);\n };\n setPosition = (x, y) => {\n const maxX = Math.max((doc.defaultView?.innerWidth ?? 1024) - 80, 0);\n const maxY = Math.max((doc.defaultView?.innerHeight ?? 768) - 40, 0);\n setPositionSignal({ x: Math.min(Math.max(x, 0), maxX), y: Math.min(Math.max(y, 0), maxY) });\n };\n state = createPanelState(backend, {\n ...(options.tab === undefined ? {} : { tab: options.tab }),\n flash: options.flash !== false,\n onFlash: (on) => overlay.setFlash(on),\n onPause: (paused) => {\n if (paused) agent.pause();\n else agent.resume();\n },\n });\n return (\n <>\n <Panel\n state={state}\n open={open()}\n x={position().x}\n y={position().y}\n onMove={(x, y) => setPosition(x, y)}\n onClose={() => setOpen(false)}\n />\n <button\n type=\"button\"\n class=\"launcher\"\n aria-label=\"Open ShadowJS devtools\"\n hidden={open() || options.launcher === false}\n on:click={() => setOpen(true)}\n >\n S\n </button>\n </>\n );\n }, root),\n );\n\n // The model changes far more often than anyone can read it: refresh the views once per frame.\n let refreshPending = false;\n let frame: number | undefined;\n const refresh = (): void => {\n refreshPending = false;\n frame = undefined;\n state?.refresh();\n };\n const stopSubscription = backend.subscribe(() => {\n if (refreshPending) return;\n refreshPending = true;\n if (typeof doc.defaultView?.requestAnimationFrame === \"function\") {\n frame = doc.defaultView.requestAnimationFrame(refresh);\n } else {\n queueMicrotask(refresh);\n }\n });\n\n const shortcut =\n options.toggleKey === false ? null : parseShortcut(options.toggleKey ?? \"Alt+Shift+D\");\n const onKey = (event: KeyboardEvent): void => {\n if (shortcut === null || !matchesShortcut(event, shortcut)) return;\n event.preventDefault();\n setOpen(!isOpen);\n flushSync();\n };\n doc.addEventListener(\"keydown\", onKey, true);\n\n const flush = (): void => {\n // Deliver the agent's pending events first: they are what schedules the refresh.\n agent.flush();\n if (refreshPending) {\n if (frame !== undefined) doc.defaultView?.cancelAnimationFrame(frame);\n refresh();\n }\n flushSync();\n };\n\n let disposed = false;\n return {\n host,\n root,\n agent,\n backend,\n get isOpen(): boolean {\n return isOpen;\n },\n open(): void {\n setOpen(true);\n flushSync();\n },\n close(): void {\n setOpen(false);\n flushSync();\n },\n toggle(): void {\n setOpen(!isOpen);\n flushSync();\n },\n moveTo(x, y): void {\n setPosition(x, y);\n flushSync();\n },\n setTab(tab): void {\n state?.setTab(tab);\n flushSync();\n },\n select(id): void {\n state?.select(id);\n flushSync();\n },\n flush,\n dispose(): void {\n if (disposed) return;\n disposed = true;\n doc.removeEventListener(\"keydown\", onKey, true);\n stopSubscription();\n if (frame !== undefined) doc.defaultView?.cancelAnimationFrame(frame);\n stopServing();\n unmount();\n overlay.dispose();\n backend.dispose();\n host.remove();\n detach();\n if (ownsAgent) agent.dispose();\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA4KA,SAAgB,cAAc,MAA+B;CAC3D,MAAM,QAAQ,KACX,MAAM,GAAG,CAAA,CACT,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,YAAY,CAAC,CAAA,CACvC,QAAQ,SAAS,SAAS,EAAE;CAC/B,MAAM,MAAM,MAAM,IAAI;CACtB,IAAI,QAAQ,KAAA,GAAW,OAAO;CAC9B,MAAM,OAAO,GAAG,UAA6B,MAAM,MAAM,SAAS,MAAM,SAAS,IAAI,CAAC;CACtF,OAAO;EACL,KAAK,IAAI,OAAO,QAAQ;EACxB,MAAM,IAAI,QAAQ,SAAS;EAC3B,OAAO,IAAI,OAAO;EAClB,MAAM,IAAI,QAAQ,OAAO,SAAS;EAClC;CACF;AACF;;;;;;;;;;;;;;;;;AAkBA,SAAgB,gBACd,OACA,UACS;CACT,IACE,MAAM,WAAW,SAAS,OAC1B,MAAM,YAAY,SAAS,QAC3B,MAAM,aAAa,SAAS,SAC5B,MAAM,YAAY,SAAS,MAE3B,OAAO;CAGT,IADY,MAAM,IAAI,YAClB,MAAQ,SAAS,KAAK,OAAO;CACjC,OAAO,SAAS,IAAI,WAAW,KAAK,MAAM,KAAK,YAAY,MAAM,MAAM,SAAS;AAClF;AAEA,SAAS,OAAa,CAEtB;AAEA,MAAM,MAAsB;CAC1B,MAAM;CACN,MAAM;CACN,OAAO;CACP,SAAS;CACT,QAAQ;CACR,MAAM;CACN,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,OAAO;CACP,SAAS;AACX;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,cAAc,UAAwB,CAAC,GAAmB;CAExE,MAAM,MAAM,QAAQ,aAAa,OAAO,aAAa,cAAc,KAAA,IAAY;CAC/E,IAAI,QAAQ,KAAA,GAAW,OAAO;CAC9B,MAAM,OAAO,QAAQ,QAAQ,YAAY;CACzC,IAAI,SAAS,KAAA,GAAW,OAAO;CAE/B,MAAM,YAAY,QAAQ,UAAU,KAAA;CACpC,MAAM,QAAQ,QAAQ,SAAS,YAAY,EAAE,YAAY,CAAC;CAC1D,MAAM,SAAS,YAAY,MAAM,OAAO,IAAI,IAAI;CAChD,MAAM,UAAU,cAAc,OAAO;EAAE,UAAU;EAAK,OAAO,QAAQ,UAAU;CAAM,CAAC;CACtF,MAAM,UAAU,mBAAmB,OAAO,EAAE,YAAY,OAAO,QAAQ,UAAU,EAAE,EAAE,CAAC;CACtF,MAAM,cACJ,QAAQ,UAAU,KAAA,IACd,OACA,WAAW,OAAO,QAAQ,OAAO,EAAE,YAAY,OAAO,QAAQ,UAAU,EAAE,EAAE,CAAC;CAEnF,MAAM,OAAO,IAAI,cAAc,KAAK;CACpC,KAAK,aAAa,0BAA0B,EAAE;CAC9C,MAAM,OAAO,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;CAC/C,MAAM,QAAQ,IAAI,cAAc,OAAO;CACvC,MAAM,cAAc,GAAG,YAAY;CACnC,KAAK,YAAY,KAAK;CACtB,CAAC,QAAQ,aAAa,IAAI,KAAA,CAAM,YAAY,IAAI;CAEhD,IAAI;CACJ,IAAI,UAAoC;CACxC,IAAI,cAA8C;CAClD,IAAI,SAAS,QAAQ,SAAS;CAC9B,MAAM,QAAQ,QAAQ,YAAY;EAAE,GAAG;EAAI,GAAG;CAAG;CAGjD,MAAM,UAAU,MAAM,YACpB,aAAa;EACX,MAAM,CAAC,MAAM,iBAAiB,UAAU,MAAM;EAC9C,MAAM,CAAC,UAAU,qBAAqB,UAAU;GAAE,GAAG,MAAM;GAAG,GAAG,MAAM;EAAE,CAAC;EAC1E,WAAW,UAAU;GACnB,SAAS;GACT,cAAc,KAAK;EACrB;EACA,eAAe,GAAG,MAAM;GACtB,MAAM,OAAO,KAAK,KAAK,IAAI,aAAa,cAAc,QAAQ,IAAI,CAAC;GACnE,MAAM,OAAO,KAAK,KAAK,IAAI,aAAa,eAAe,OAAO,IAAI,CAAC;GACnE,kBAAkB;IAAE,GAAG,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,GAAG,IAAI;IAAG,GAAG,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,GAAG,IAAI;GAAE,CAAC;EAC5F;EACA,QAAQ,iBAAiB,SAAS;GAChC,GAAI,QAAQ,QAAQ,KAAA,IAAY,CAAC,IAAI,EAAE,KAAK,QAAQ,IAAI;GACxD,OAAO,QAAQ,UAAU;GACzB,UAAU,OAAO,QAAQ,SAAS,EAAE;GACpC,UAAU,WAAW;IACnB,IAAI,QAAQ,MAAM,MAAM;SACnB,MAAM,OAAO;GACpB;EACF,CAAC;EACD,OACE,CAAAA,gBAAA,OAAA;GAAA;GAAA,IAAA,OAAA;IAAA,OAAA,KAAA;GAAA;GAAA,IAAA,IAAA;IAAA,OAAA,SAAA,CAAA,CAAA;GAAA;GAAA,IAAA,IAAA;IAAA,OAAA,SAAA,CAAA,CAAA;GAAA;GAAA,SAAA,GAAA,MAAA,YAAA,GAAA,CAAA;GAAA,eAAA,QAAA,KAAA;EAAA,GAAA,OAAA,UAAA;GAAA,MAAA,OAAA,OAAA;GAAA,aAAA;IAAA,KAAA,SAAA,KAAA,KAAA,QAAA,aAAA;GAAA,CAAA;GAAA,iBAAA,MAAA,eAAA,QAAA,IAAA,CAAA;GAAA,OAAA;EAAA,EAAA,CAAA,CAAA;CAoBJ,GAAG,IAAI,CACT;CAGA,IAAI,iBAAiB;CACrB,IAAI;CACJ,MAAM,gBAAsB;EAC1B,iBAAiB;EACjB,QAAQ,KAAA;EACR,OAAO,QAAQ;CACjB;CACA,MAAM,mBAAmB,QAAQ,gBAAgB;EAC/C,IAAI,gBAAgB;EACpB,iBAAiB;EACjB,IAAI,OAAO,IAAI,aAAa,0BAA0B,YACpD,QAAQ,IAAI,YAAY,sBAAsB,OAAO;OAErD,eAAe,OAAO;CAE1B,CAAC;CAED,MAAM,WACJ,QAAQ,cAAc,QAAQ,OAAO,cAAc,QAAQ,aAAa,aAAa;CACvF,MAAM,SAAS,UAA+B;EAC5C,IAAI,aAAa,QAAQ,CAAC,gBAAgB,OAAO,QAAQ,GAAG;EAC5D,MAAM,eAAe;EACrB,QAAQ,CAAC,MAAM;EACf,UAAU;CACZ;CACA,IAAI,iBAAiB,WAAW,OAAO,IAAI;CAE3C,MAAM,cAAoB;EAExB,MAAM,MAAM;EACZ,IAAI,gBAAgB;GAClB,IAAI,UAAU,KAAA,GAAW,IAAI,aAAa,qBAAqB,KAAK;GACpE,QAAQ;EACV;EACA,UAAU;CACZ;CAEA,IAAI,WAAW;CACf,OAAO;EACL;EACA;EACA;EACA;EACA,IAAI,SAAkB;GACpB,OAAO;EACT;EACA,OAAa;GACX,QAAQ,IAAI;GACZ,UAAU;EACZ;EACA,QAAc;GACZ,QAAQ,KAAK;GACb,UAAU;EACZ;EACA,SAAe;GACb,QAAQ,CAAC,MAAM;GACf,UAAU;EACZ;EACA,OAAO,GAAG,GAAS;GACjB,YAAY,GAAG,CAAC;GAChB,UAAU;EACZ;EACA,OAAO,KAAW;GAChB,OAAO,OAAO,GAAG;GACjB,UAAU;EACZ;EACA,OAAO,IAAU;GACf,OAAO,OAAO,EAAE;GAChB,UAAU;EACZ;EACA;EACA,UAAgB;GACd,IAAI,UAAU;GACd,WAAW;GACX,IAAI,oBAAoB,WAAW,OAAO,IAAI;GAC9C,iBAAiB;GACjB,IAAI,UAAU,KAAA,GAAW,IAAI,aAAa,qBAAqB,KAAK;GACpE,YAAY;GACZ,QAAQ;GACR,QAAQ,QAAQ;GAChB,QAAQ,QAAQ;GAChB,KAAK,OAAO;GACZ,OAAO;GACP,IAAI,WAAW,MAAM,QAAQ;EAC/B;CACF;AACF"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Agent } from "./agent.js";
|
|
2
|
+
//#region src/overlay.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* A rectangle in viewport coordinates, from {@link boxOf}.
|
|
5
|
+
*
|
|
6
|
+
* @since 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
export interface Box {
|
|
9
|
+
/** Distance from the left edge of the viewport, in pixels. */
|
|
10
|
+
left: number;
|
|
11
|
+
/** Distance from the top edge of the viewport, in pixels. */
|
|
12
|
+
top: number;
|
|
13
|
+
/** Width in pixels. */
|
|
14
|
+
width: number;
|
|
15
|
+
/** Height in pixels. */
|
|
16
|
+
height: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Options of {@link createOverlay}.
|
|
20
|
+
*
|
|
21
|
+
* @since 1.0.0
|
|
22
|
+
*/
|
|
23
|
+
export interface OverlayOptions {
|
|
24
|
+
/** The document to draw in. @default globalThis.document */
|
|
25
|
+
document?: Document;
|
|
26
|
+
/** Flash a component's box each time one of its effects or memos re-runs after its first run. @default true */
|
|
27
|
+
flash?: boolean;
|
|
28
|
+
/** Most flash boxes alive at once; older ones are removed first. @default 40 */
|
|
29
|
+
maxFlashes?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The in-page layer: the hover highlight and the update flashes. Created by {@link createOverlay}.
|
|
33
|
+
*
|
|
34
|
+
* @since 1.0.0
|
|
35
|
+
*/
|
|
36
|
+
export interface Overlay {
|
|
37
|
+
/** The shadow host element that holds the layer. It is appended to `document.body`. */
|
|
38
|
+
readonly host: HTMLElement;
|
|
39
|
+
/**
|
|
40
|
+
* Draws a labeled box around the component of this node (the node itself when it is a component),
|
|
41
|
+
* replacing the previous highlight. `null` removes the highlight; a node with nothing laid out draws
|
|
42
|
+
* nothing.
|
|
43
|
+
*/
|
|
44
|
+
highlight(id: number | null): void;
|
|
45
|
+
/**
|
|
46
|
+
* Flashes the rendered DOM of the component with this id once. Does nothing while flashing is off or
|
|
47
|
+
* when none of its DOM is laid out.
|
|
48
|
+
*/
|
|
49
|
+
flash(id: number): void;
|
|
50
|
+
/** Number of flash boxes currently in the page. */
|
|
51
|
+
readonly flashes: number;
|
|
52
|
+
/** Removes all flash boxes now (they also remove themselves when their animation ends). */
|
|
53
|
+
clearFlashes(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Turns the update flashes on or off. Turning them off does not remove the boxes already shown; use
|
|
56
|
+
* {@link Overlay.clearFlashes} for that.
|
|
57
|
+
*/
|
|
58
|
+
setFlash(on: boolean): void;
|
|
59
|
+
/**
|
|
60
|
+
* Stops listening to the agent and removes the layer, the flash boxes and the highlight. Do not use
|
|
61
|
+
* the overlay afterward.
|
|
62
|
+
*/
|
|
63
|
+
dispose(): void;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The union of the boxes of `nodes`: the smallest rectangle that contains all of them. Elements are
|
|
67
|
+
* measured with their bounding box, text nodes with a range. Nodes with no layout (zero width and
|
|
68
|
+
* height, or neither an element nor a text node) are skipped.
|
|
69
|
+
*
|
|
70
|
+
* @param nodes The DOM nodes to measure.
|
|
71
|
+
* @param doc The document, used to create ranges for text nodes.
|
|
72
|
+
* @returns The box in viewport coordinates, or `null` when none is laid out.
|
|
73
|
+
* @example
|
|
74
|
+
* import { boxOf } from "@shadow-js/devtools";
|
|
75
|
+
*
|
|
76
|
+
* const box = boxOf([document.body], document);
|
|
77
|
+
* @since 1.0.0
|
|
78
|
+
*/
|
|
79
|
+
export declare function boxOf(nodes: readonly Node[], doc: Document): Box | null;
|
|
80
|
+
/**
|
|
81
|
+
* Creates the in-page overlay: a shadow-root layer above the page that never takes pointer events.
|
|
82
|
+
* It draws the hover highlight and, when enabled, flashes a component's box each time one of its
|
|
83
|
+
* effects or memos re-runs after its first run. It reads positions from the DOM nodes the agent
|
|
84
|
+
* holds weakly. Dev only: `mountDevtools` creates it, and that call does nothing outside `__DEV__`
|
|
85
|
+
* builds, so this layer never ships.
|
|
86
|
+
*
|
|
87
|
+
* @param agent The agent that knows which DOM nodes a component rendered.
|
|
88
|
+
* @param options Document, flash switch and flash limit.
|
|
89
|
+
* @returns The overlay. Call `dispose` on it when it is no longer needed.
|
|
90
|
+
* @example
|
|
91
|
+
* import { componentStats, createAgent, createOverlay } from "@shadow-js/devtools";
|
|
92
|
+
*
|
|
93
|
+
* const agent = createAgent();
|
|
94
|
+
* const overlay = createOverlay(agent);
|
|
95
|
+
* const top = componentStats(agent.model)[0];
|
|
96
|
+
* if (top !== undefined) overlay.highlight(top.id);
|
|
97
|
+
* @see boxOf
|
|
98
|
+
* @since 1.0.0
|
|
99
|
+
*/
|
|
100
|
+
export declare function createOverlay(agent: Agent, options?: OverlayOptions): Overlay;
|
|
101
|
+
//#endregion
|
|
102
|
+
//# sourceMappingURL=overlay.d.ts.map
|