@fluixi/devtools 0.2.0-alpha.2 → 0.2.0-alpha.4
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 +80 -2
- package/dist/callsite.cjs +72 -0
- package/dist/callsite.d.ts +22 -0
- package/dist/callsite.d.ts.map +1 -0
- package/dist/callsite.js +82 -0
- package/dist/callsite.mjs +51 -0
- package/dist/change-view.cjs +134 -0
- package/dist/change-view.d.ts +10 -0
- package/dist/change-view.d.ts.map +1 -0
- package/dist/change-view.js +102 -0
- package/dist/change-view.mjs +111 -0
- package/dist/copy.cjs +63 -0
- package/dist/copy.d.ts +9 -0
- package/dist/copy.d.ts.map +1 -0
- package/dist/copy.js +52 -0
- package/dist/copy.mjs +42 -0
- package/dist/describe.cjs +55 -0
- package/dist/describe.d.ts +3 -0
- package/dist/describe.d.ts.map +1 -0
- package/dist/describe.js +47 -0
- package/dist/describe.mjs +34 -0
- package/dist/detail-panel.cjs +307 -0
- package/dist/detail-panel.d.ts +17 -0
- package/dist/detail-panel.d.ts.map +1 -0
- package/dist/detail-panel.js +217 -0
- package/dist/detail-panel.mjs +284 -0
- package/dist/diff.cjs +225 -0
- package/dist/diff.d.ts +31 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +132 -0
- package/dist/diff.mjs +202 -0
- package/dist/dom.cjs +204 -0
- package/dist/dom.d.ts +81 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +269 -0
- package/dist/dom.mjs +183 -0
- package/dist/export.cjs +221 -0
- package/dist/export.d.ts +55 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +90 -0
- package/dist/export.mjs +209 -0
- package/dist/float.cjs +232 -0
- package/dist/float.d.ts +54 -0
- package/dist/float.d.ts.map +1 -0
- package/dist/float.js +256 -0
- package/dist/float.mjs +211 -0
- package/dist/format.cjs +175 -0
- package/dist/format.d.ts +16 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +188 -0
- package/dist/format.mjs +154 -0
- package/dist/graph-view.cjs +893 -0
- package/dist/graph-view.d.ts +79 -2
- package/dist/graph-view.d.ts.map +1 -1
- package/dist/graph-view.js +866 -50
- package/dist/graph-view.mjs +870 -0
- package/dist/highlight.cjs +140 -0
- package/dist/highlight.d.ts +56 -0
- package/dist/highlight.d.ts.map +1 -0
- package/dist/highlight.js +154 -0
- package/dist/highlight.mjs +119 -0
- package/dist/history.cjs +95 -0
- package/dist/history.d.ts +59 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +85 -0
- package/dist/history.mjs +74 -0
- package/dist/hook.cjs +1562 -0
- package/dist/hook.d.ts +60 -0
- package/dist/hook.d.ts.map +1 -0
- package/dist/hook.js +142 -0
- package/dist/hook.mjs +1553 -0
- package/dist/index.cjs +4552 -0
- package/dist/index.d.ts +44 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -1
- package/dist/index.mjs +4544 -0
- package/dist/inspector.cjs +688 -0
- package/dist/inspector.d.ts +60 -0
- package/dist/inspector.d.ts.map +1 -0
- package/dist/inspector.js +415 -0
- package/dist/inspector.mjs +676 -0
- package/dist/instrument.cjs +497 -0
- package/dist/instrument.d.ts +147 -5
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.js +164 -44
- package/dist/instrument.mjs +487 -0
- package/dist/kind-filter.cjs +172 -0
- package/dist/kind-filter.d.ts +39 -0
- package/dist/kind-filter.d.ts.map +1 -0
- package/dist/kind-filter.js +151 -0
- package/dist/kind-filter.mjs +151 -0
- package/dist/menu.cjs +141 -0
- package/dist/menu.d.ts +37 -0
- package/dist/menu.d.ts.map +1 -0
- package/dist/menu.js +125 -0
- package/dist/menu.mjs +120 -0
- package/dist/observe.cjs +1352 -0
- package/dist/observe.d.ts +80 -0
- package/dist/observe.d.ts.map +1 -0
- package/dist/observe.js +893 -0
- package/dist/observe.mjs +1345 -0
- package/dist/palette.cjs +52 -0
- package/dist/palette.d.ts +11 -0
- package/dist/palette.d.ts.map +1 -0
- package/dist/palette.js +27 -0
- package/dist/palette.mjs +31 -0
- package/dist/path.cjs +45 -0
- package/dist/path.d.ts +19 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +49 -0
- package/dist/path.mjs +24 -0
- package/dist/plugin.cjs +87 -0
- package/dist/plugin.d.ts +121 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +74 -0
- package/dist/plugin.mjs +66 -0
- package/dist/plugins/context.cjs +98 -0
- package/dist/plugins/context.d.ts +10 -0
- package/dist/plugins/context.d.ts.map +1 -0
- package/dist/plugins/context.js +80 -0
- package/dist/plugins/context.mjs +75 -0
- package/dist/plugins/directives.cjs +98 -0
- package/dist/plugins/directives.d.ts +25 -0
- package/dist/plugins/directives.d.ts.map +1 -0
- package/dist/plugins/directives.js +78 -0
- package/dist/plugins/directives.mjs +75 -0
- package/dist/plugins/index.cjs +162 -0
- package/dist/plugins/index.d.ts +7 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +9 -0
- package/dist/plugins/index.mjs +140 -0
- package/dist/source.cjs +111 -0
- package/dist/source.d.ts +72 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +153 -0
- package/dist/source.mjs +90 -0
- package/dist/timeline.cjs +350 -0
- package/dist/timeline.d.ts +30 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +271 -0
- package/dist/timeline.mjs +327 -0
- package/dist/tree.cjs +574 -0
- package/dist/tree.d.ts +128 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +645 -0
- package/dist/tree.mjs +551 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/value-pane.cjs +223 -0
- package/dist/value-pane.d.ts +10 -0
- package/dist/value-pane.d.ts.map +1 -0
- package/dist/value-pane.js +61 -0
- package/dist/value-pane.mjs +200 -0
- package/dist/wire.cjs +109 -0
- package/dist/wire.d.ts +100 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +93 -0
- package/dist/wire.mjs +89 -0
- package/package.json +17 -5
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,4544 @@
|
|
|
1
|
+
/*! @fluixi/devtools v0.2.0-alpha.4 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
|
|
2
|
+
|
|
3
|
+
// src/instrument.ts
|
|
4
|
+
import {
|
|
5
|
+
createSignal,
|
|
6
|
+
createMemo,
|
|
7
|
+
createEffect,
|
|
8
|
+
createResource,
|
|
9
|
+
createRoot,
|
|
10
|
+
untrack,
|
|
11
|
+
onCleanup,
|
|
12
|
+
getOwner,
|
|
13
|
+
runWithOwner,
|
|
14
|
+
Signal
|
|
15
|
+
} from "@fluixi/reactive/signal";
|
|
16
|
+
import { createStore } from "@fluixi/reactive/store";
|
|
17
|
+
|
|
18
|
+
// src/source.ts
|
|
19
|
+
var MARKER = "__fx_source";
|
|
20
|
+
var COMPONENT_MARKER = "__fx_component";
|
|
21
|
+
var HOLE_MARKER = "__fx_hole";
|
|
22
|
+
var PROPS_MARKER = "__fx_props";
|
|
23
|
+
var MARKS = "__FLUIXI_DEVTOOLS_MARKS__";
|
|
24
|
+
var host = globalThis;
|
|
25
|
+
var shared = host[MARKS] ?? {
|
|
26
|
+
pending: null,
|
|
27
|
+
expiring: false,
|
|
28
|
+
components: /* @__PURE__ */ new Map()
|
|
29
|
+
};
|
|
30
|
+
host[MARKS] = shared;
|
|
31
|
+
function markReactiveSource(file, line, column, name) {
|
|
32
|
+
const location2 = locate(file, line, column);
|
|
33
|
+
if (shared.pending && location2.external && !shared.pending.location?.external) return;
|
|
34
|
+
shared.pending = { name, location: location2 };
|
|
35
|
+
shared.expiring = false;
|
|
36
|
+
}
|
|
37
|
+
function locate(file, line, column) {
|
|
38
|
+
const external = !!file && (/^([a-zA-Z]:)?[\\/]/.test(file) || file.includes("node_modules/"));
|
|
39
|
+
return external ? { file, line, column, external } : { file, line, column };
|
|
40
|
+
}
|
|
41
|
+
function markComponentSource(name, file, line, column) {
|
|
42
|
+
const seen = shared.components.get(name);
|
|
43
|
+
const next = locate(file, line, column);
|
|
44
|
+
if (seen === void 0) {
|
|
45
|
+
shared.components.set(name, next);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!seen || seen.file !== file || seen.line !== line) shared.components.set(name, null);
|
|
49
|
+
}
|
|
50
|
+
function componentSource(name) {
|
|
51
|
+
return shared.components.get(name) ?? void 0;
|
|
52
|
+
}
|
|
53
|
+
function takeReactiveSource() {
|
|
54
|
+
const mark = shared.pending;
|
|
55
|
+
if (mark) {
|
|
56
|
+
if (mark.name !== void 0) shared.pending = { location: mark.location };
|
|
57
|
+
if (!shared.expiring) {
|
|
58
|
+
shared.expiring = true;
|
|
59
|
+
queueMicrotask(() => {
|
|
60
|
+
shared.expiring = false;
|
|
61
|
+
shared.pending = null;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return mark;
|
|
66
|
+
}
|
|
67
|
+
var HOLES = /* @__PURE__ */ new WeakMap();
|
|
68
|
+
var HOLE_TAG = "__fx_at";
|
|
69
|
+
function markHole(accessor, line, column, name) {
|
|
70
|
+
if (accessor && (typeof accessor === "object" || typeof accessor === "function")) {
|
|
71
|
+
const at3 = name === void 0 ? { line, column } : { line, column, name };
|
|
72
|
+
HOLES.set(accessor, at3);
|
|
73
|
+
try {
|
|
74
|
+
Object.defineProperty(accessor, HOLE_TAG, { value: at3, configurable: true });
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
shared.pending = null;
|
|
79
|
+
return accessor;
|
|
80
|
+
}
|
|
81
|
+
var PROPS = /* @__PURE__ */ new WeakMap();
|
|
82
|
+
function markProps(props, at3) {
|
|
83
|
+
if (props && typeof props === "object") PROPS.set(props, at3);
|
|
84
|
+
return props;
|
|
85
|
+
}
|
|
86
|
+
function propsLocations(props) {
|
|
87
|
+
return props && typeof props === "object" ? PROPS.get(props) : void 0;
|
|
88
|
+
}
|
|
89
|
+
function holeLocation(accessor) {
|
|
90
|
+
if (!accessor || typeof accessor !== "object" && typeof accessor !== "function") return void 0;
|
|
91
|
+
return HOLES.get(accessor) ?? accessor[HOLE_TAG];
|
|
92
|
+
}
|
|
93
|
+
function installSourceMarker() {
|
|
94
|
+
if (typeof host[MARKER] !== "function") host[MARKER] = markReactiveSource;
|
|
95
|
+
if (typeof host[COMPONENT_MARKER] !== "function") host[COMPONENT_MARKER] = markComponentSource;
|
|
96
|
+
if (typeof host[HOLE_MARKER] !== "function") host[HOLE_MARKER] = markHole;
|
|
97
|
+
if (typeof host[PROPS_MARKER] !== "function") host[PROPS_MARKER] = markProps;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/format.ts
|
|
101
|
+
var MAX_DEPTH = 3;
|
|
102
|
+
var MAX_ENTRIES = 8;
|
|
103
|
+
function domName(v) {
|
|
104
|
+
if (typeof v?.nodeType !== "number" || typeof v.nodeName !== "string") return null;
|
|
105
|
+
return v.tagName ? `<${v.tagName.toLowerCase()}>` : `#${v.nodeName.toLowerCase()}`;
|
|
106
|
+
}
|
|
107
|
+
function fnName(v) {
|
|
108
|
+
return v.name ? `ƒ ${v.name}()` : "ƒ";
|
|
109
|
+
}
|
|
110
|
+
function className(v) {
|
|
111
|
+
const name = v.constructor?.name;
|
|
112
|
+
return name && name !== "Object" ? `${name} ` : "";
|
|
113
|
+
}
|
|
114
|
+
function primitive(v) {
|
|
115
|
+
if (v === null) return "null";
|
|
116
|
+
switch (typeof v) {
|
|
117
|
+
case "undefined":
|
|
118
|
+
return "undefined";
|
|
119
|
+
case "string":
|
|
120
|
+
return JSON.stringify(v);
|
|
121
|
+
case "number":
|
|
122
|
+
case "boolean":
|
|
123
|
+
return String(v);
|
|
124
|
+
case "bigint":
|
|
125
|
+
return `${v}n`;
|
|
126
|
+
case "symbol":
|
|
127
|
+
return v.toString();
|
|
128
|
+
case "function":
|
|
129
|
+
return fnName(v);
|
|
130
|
+
default:
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function walk(value, depth, seen) {
|
|
135
|
+
const simple = primitive(value);
|
|
136
|
+
if (simple !== null) return simple;
|
|
137
|
+
const object = value;
|
|
138
|
+
if (seen.has(object)) return "[circular]";
|
|
139
|
+
const dom = domName(object);
|
|
140
|
+
if (dom) return dom;
|
|
141
|
+
if (object instanceof Date) return Number.isNaN(object.getTime()) ? "Invalid Date" : object.toISOString();
|
|
142
|
+
if (object instanceof RegExp) return String(object);
|
|
143
|
+
if (object instanceof Error) return `${object.name}: ${object.message}`;
|
|
144
|
+
if (object instanceof Promise) return "Promise";
|
|
145
|
+
if (ArrayBuffer.isView(object)) {
|
|
146
|
+
return `${className(object).trim()}(${object.length ?? ""})`;
|
|
147
|
+
}
|
|
148
|
+
if (depth >= MAX_DEPTH) return Array.isArray(object) ? "[…]" : "{…}";
|
|
149
|
+
seen.add(object);
|
|
150
|
+
try {
|
|
151
|
+
if (Array.isArray(object)) {
|
|
152
|
+
const shown2 = object.slice(0, MAX_ENTRIES).map((item) => walk(item, depth + 1, seen));
|
|
153
|
+
if (object.length > MAX_ENTRIES) shown2.push("…");
|
|
154
|
+
return `[${shown2.join(", ")}]`;
|
|
155
|
+
}
|
|
156
|
+
if (object instanceof Map) {
|
|
157
|
+
const shown2 = [];
|
|
158
|
+
for (const [k, v] of object) {
|
|
159
|
+
if (shown2.length === MAX_ENTRIES) {
|
|
160
|
+
shown2.push("…");
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
shown2.push(`${walk(k, depth + 1, seen)} => ${walk(v, depth + 1, seen)}`);
|
|
164
|
+
}
|
|
165
|
+
return `Map(${object.size}) {${shown2.join(", ")}}`;
|
|
166
|
+
}
|
|
167
|
+
if (object instanceof Set) {
|
|
168
|
+
const shown2 = [];
|
|
169
|
+
for (const item of object) {
|
|
170
|
+
if (shown2.length === MAX_ENTRIES) {
|
|
171
|
+
shown2.push("…");
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
shown2.push(walk(item, depth + 1, seen));
|
|
175
|
+
}
|
|
176
|
+
return `Set(${object.size}) {${shown2.join(", ")}}`;
|
|
177
|
+
}
|
|
178
|
+
const keys = Object.keys(object);
|
|
179
|
+
const shown = keys.slice(0, MAX_ENTRIES).map((key) => {
|
|
180
|
+
let read;
|
|
181
|
+
try {
|
|
182
|
+
read = walk(object[key], depth + 1, seen);
|
|
183
|
+
} catch {
|
|
184
|
+
read = "[unreadable]";
|
|
185
|
+
}
|
|
186
|
+
return `${key}: ${read}`;
|
|
187
|
+
});
|
|
188
|
+
if (keys.length > MAX_ENTRIES) shown.push("…");
|
|
189
|
+
return `${className(object)}{${shown.join(", ")}}`;
|
|
190
|
+
} finally {
|
|
191
|
+
seen.delete(object);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function formatValue(value, limit = 240) {
|
|
195
|
+
let text;
|
|
196
|
+
try {
|
|
197
|
+
text = walk(value, 0, /* @__PURE__ */ new Set());
|
|
198
|
+
} catch {
|
|
199
|
+
text = "[unreadable]";
|
|
200
|
+
}
|
|
201
|
+
return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
|
|
202
|
+
}
|
|
203
|
+
function shorten(text, limit = 18) {
|
|
204
|
+
return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
|
|
205
|
+
}
|
|
206
|
+
function indentValue(text, pad = " ") {
|
|
207
|
+
const out = [];
|
|
208
|
+
let depth = 0;
|
|
209
|
+
let quote = null;
|
|
210
|
+
let line = "";
|
|
211
|
+
const flush = () => {
|
|
212
|
+
if (line.trim()) out.push(pad.repeat(depth) + line.trim());
|
|
213
|
+
line = "";
|
|
214
|
+
};
|
|
215
|
+
for (let i = 0; i < text.length; i++) {
|
|
216
|
+
const c = text[i];
|
|
217
|
+
if (quote) {
|
|
218
|
+
line += c;
|
|
219
|
+
if (c === quote && text[i - 1] !== "\\") quote = null;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (c === '"' || c === "'") {
|
|
223
|
+
quote = c;
|
|
224
|
+
line += c;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (c === "{" || c === "[") {
|
|
228
|
+
line += c;
|
|
229
|
+
flush();
|
|
230
|
+
depth++;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (c === "}" || c === "]") {
|
|
234
|
+
flush();
|
|
235
|
+
depth = Math.max(0, depth - 1);
|
|
236
|
+
line = c;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (c === ",") {
|
|
240
|
+
line += c;
|
|
241
|
+
flush();
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
line += c;
|
|
245
|
+
}
|
|
246
|
+
flush();
|
|
247
|
+
return out.join("\n");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/instrument.ts
|
|
251
|
+
var $NODE = /* @__PURE__ */ Symbol.for("signal-node");
|
|
252
|
+
var KIND_OF_BUILTIN = {
|
|
253
|
+
Show: "control",
|
|
254
|
+
For: "control",
|
|
255
|
+
Index: "control",
|
|
256
|
+
Switch: "control",
|
|
257
|
+
Match: "control",
|
|
258
|
+
Portal: "control",
|
|
259
|
+
Dynamic: "control",
|
|
260
|
+
ErrorBoundary: "control",
|
|
261
|
+
Suspense: "control",
|
|
262
|
+
SuspenseList: "control",
|
|
263
|
+
Await: "control",
|
|
264
|
+
Router: "router",
|
|
265
|
+
Outlet: "router",
|
|
266
|
+
Redirect: "router",
|
|
267
|
+
Link: "router"
|
|
268
|
+
};
|
|
269
|
+
var CORE_STATE = { 0: "clean", 1: "check", 2: "dirty" };
|
|
270
|
+
function nodeVersion(n) {
|
|
271
|
+
return n.core && typeof n.core.version === "number" ? n.core.version : n.runs;
|
|
272
|
+
}
|
|
273
|
+
function nodeState(n) {
|
|
274
|
+
return n.core && typeof n.core.state === "number" ? CORE_STATE[n.core.state] : void 0;
|
|
275
|
+
}
|
|
276
|
+
function createReactor() {
|
|
277
|
+
installSourceMarker();
|
|
278
|
+
const graph = /* @__PURE__ */ new Map();
|
|
279
|
+
const runsAtLastSample = /* @__PURE__ */ new Map();
|
|
280
|
+
const unnamed = /* @__PURE__ */ new Set();
|
|
281
|
+
const consumerStack = [];
|
|
282
|
+
let seq = 0;
|
|
283
|
+
let dispose = () => {
|
|
284
|
+
};
|
|
285
|
+
let rootOwner = null;
|
|
286
|
+
const claims = (node, value) => {
|
|
287
|
+
for (const dep of node.deps) {
|
|
288
|
+
const other = graph.get(dep);
|
|
289
|
+
if (other?.read && other.read() === value) return false;
|
|
290
|
+
}
|
|
291
|
+
return true;
|
|
292
|
+
};
|
|
293
|
+
const forget = (id) => {
|
|
294
|
+
const owner = getOwner() ?? rootOwner;
|
|
295
|
+
if (owner) {
|
|
296
|
+
runWithOwner(
|
|
297
|
+
owner,
|
|
298
|
+
() => onCleanup(() => {
|
|
299
|
+
graph.delete(id);
|
|
300
|
+
runsAtLastSample.delete(id);
|
|
301
|
+
unnamed.delete(id);
|
|
302
|
+
})
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
const recordRead = (sourceId) => {
|
|
307
|
+
const c = consumerStack[consumerStack.length - 1];
|
|
308
|
+
if (c != null) graph.get(c)?.deps.add(sourceId);
|
|
309
|
+
};
|
|
310
|
+
const recordWrite = (targetId) => {
|
|
311
|
+
const c = consumerStack[consumerStack.length - 1];
|
|
312
|
+
if (c != null && c !== targetId) graph.get(c)?.writes.add(targetId);
|
|
313
|
+
};
|
|
314
|
+
const base = (id, named, kind, fallback2) => {
|
|
315
|
+
const mark = takeReactiveSource();
|
|
316
|
+
if (!named && !mark?.name) unnamed.add(id);
|
|
317
|
+
return {
|
|
318
|
+
id,
|
|
319
|
+
label: named ?? mark?.name ?? fallback2,
|
|
320
|
+
kind,
|
|
321
|
+
deps: /* @__PURE__ */ new Set(),
|
|
322
|
+
writes: /* @__PURE__ */ new Set(),
|
|
323
|
+
runs: 0,
|
|
324
|
+
lastPulse: 0,
|
|
325
|
+
...mark?.location ? { source: mark.location } : {}
|
|
326
|
+
};
|
|
327
|
+
};
|
|
328
|
+
const trackedCreateSignal = (value, options) => {
|
|
329
|
+
const id = seq++;
|
|
330
|
+
const [get, set] = createSignal(value, options);
|
|
331
|
+
const node = base(id, options?.name, "state", `s${id}`);
|
|
332
|
+
node.read = () => untrack(get);
|
|
333
|
+
node.detail = formatValue(value);
|
|
334
|
+
node.value = shorten(node.detail);
|
|
335
|
+
node.core = get[$NODE] ?? null;
|
|
336
|
+
graph.set(id, node);
|
|
337
|
+
forget(id);
|
|
338
|
+
const read = () => {
|
|
339
|
+
recordRead(id);
|
|
340
|
+
return get();
|
|
341
|
+
};
|
|
342
|
+
const write = (v) => {
|
|
343
|
+
recordWrite(id);
|
|
344
|
+
return set(typeof v === "function" ? v(untrack(get)) : v);
|
|
345
|
+
};
|
|
346
|
+
return [read, write];
|
|
347
|
+
};
|
|
348
|
+
const trackedCreateMemo = (fn, value, options) => {
|
|
349
|
+
const id = seq++;
|
|
350
|
+
const node = base(id, options?.name, "memo", `m${id}`);
|
|
351
|
+
graph.set(id, node);
|
|
352
|
+
forget(id);
|
|
353
|
+
const m = createMemo(() => {
|
|
354
|
+
node.core ??= Signal.subtle.currentComputed();
|
|
355
|
+
node.deps.clear();
|
|
356
|
+
node.writes.clear();
|
|
357
|
+
node.runs++;
|
|
358
|
+
consumerStack.push(id);
|
|
359
|
+
try {
|
|
360
|
+
return fn();
|
|
361
|
+
} finally {
|
|
362
|
+
consumerStack.pop();
|
|
363
|
+
}
|
|
364
|
+
}, value, options);
|
|
365
|
+
node.read = () => untrack(m);
|
|
366
|
+
return () => {
|
|
367
|
+
recordRead(id);
|
|
368
|
+
return m();
|
|
369
|
+
};
|
|
370
|
+
};
|
|
371
|
+
const trackedCreateEffect = (fn, options) => {
|
|
372
|
+
const id = seq++;
|
|
373
|
+
const node = base(id, options?.name, "effect", `e${id}`);
|
|
374
|
+
graph.set(id, node);
|
|
375
|
+
forget(id);
|
|
376
|
+
createEffect(() => {
|
|
377
|
+
node.core ??= Signal.subtle.currentComputed();
|
|
378
|
+
node.deps.clear();
|
|
379
|
+
node.writes.clear();
|
|
380
|
+
node.runs++;
|
|
381
|
+
node.lastPulse = performance.now();
|
|
382
|
+
consumerStack.push(id);
|
|
383
|
+
try {
|
|
384
|
+
fn();
|
|
385
|
+
} finally {
|
|
386
|
+
consumerStack.pop();
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
};
|
|
390
|
+
const trackedCreateStore = (initial, options) => {
|
|
391
|
+
const id = seq++;
|
|
392
|
+
const [proxy, setter] = createStore(initial, { immutable: true, ...options });
|
|
393
|
+
const node = base(id, options?.name, "store", `store${id}`);
|
|
394
|
+
node.read = () => {
|
|
395
|
+
try {
|
|
396
|
+
return untrack(() => JSON.stringify(proxy));
|
|
397
|
+
} catch {
|
|
398
|
+
return "{…}";
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
node.value = "{…}";
|
|
402
|
+
graph.set(id, node);
|
|
403
|
+
forget(id);
|
|
404
|
+
const recording = new Proxy(proxy, {
|
|
405
|
+
get(t, p, r) {
|
|
406
|
+
recordRead(id);
|
|
407
|
+
return Reflect.get(t, p, r);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
const write = (...args) => {
|
|
411
|
+
recordWrite(id);
|
|
412
|
+
return setter(...args);
|
|
413
|
+
};
|
|
414
|
+
return [recording, write];
|
|
415
|
+
};
|
|
416
|
+
const trackedCreateResource = (...args) => {
|
|
417
|
+
const id = seq++;
|
|
418
|
+
const last = args[args.length - 1];
|
|
419
|
+
const name = last && typeof last === "object" && !Array.isArray(last) && typeof last !== "function" ? last.name : void 0;
|
|
420
|
+
const node = base(id, name, "resource", `res${id}`);
|
|
421
|
+
const [data, actions] = createResource(...args);
|
|
422
|
+
node.read = () => {
|
|
423
|
+
try {
|
|
424
|
+
return untrack(() => data.loading ? "⏳ loading" : JSON.stringify(data.latest));
|
|
425
|
+
} catch {
|
|
426
|
+
return "?";
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
node.value = "⏳ loading";
|
|
430
|
+
graph.set(id, node);
|
|
431
|
+
forget(id);
|
|
432
|
+
const accessor = (() => {
|
|
433
|
+
recordRead(id);
|
|
434
|
+
return data();
|
|
435
|
+
});
|
|
436
|
+
for (const k of ["loading", "error", "latest", "state"]) {
|
|
437
|
+
Object.defineProperty(accessor, k, { get: () => data[k] });
|
|
438
|
+
}
|
|
439
|
+
return [accessor, actions];
|
|
440
|
+
};
|
|
441
|
+
const trackedSignal = (value, options) => {
|
|
442
|
+
const [read, write] = trackedCreateSignal(value, options);
|
|
443
|
+
Object.defineProperty(read, "set", { value: write });
|
|
444
|
+
return read;
|
|
445
|
+
};
|
|
446
|
+
const trackedMemo = trackedCreateMemo;
|
|
447
|
+
const trackedEffect = trackedCreateEffect;
|
|
448
|
+
const trackedStore = (initial, options) => {
|
|
449
|
+
const [state, set] = trackedCreateStore(initial, options);
|
|
450
|
+
return new Proxy(state, {
|
|
451
|
+
get: (target, key) => key === "set" ? set : target[key],
|
|
452
|
+
has: (target, key) => key === "set" || Reflect.has(target, key)
|
|
453
|
+
});
|
|
454
|
+
};
|
|
455
|
+
const trackedResource = (...args) => {
|
|
456
|
+
const [accessor, controls] = trackedCreateResource(...args);
|
|
457
|
+
return Object.assign(accessor, controls);
|
|
458
|
+
};
|
|
459
|
+
const api = {
|
|
460
|
+
createSignal: trackedCreateSignal,
|
|
461
|
+
createMemo: trackedCreateMemo,
|
|
462
|
+
createEffect: trackedCreateEffect,
|
|
463
|
+
createStore: trackedCreateStore,
|
|
464
|
+
createResource: trackedCreateResource,
|
|
465
|
+
signal: trackedSignal,
|
|
466
|
+
memo: trackedMemo,
|
|
467
|
+
store: trackedStore,
|
|
468
|
+
effect: trackedEffect,
|
|
469
|
+
resource: trackedResource
|
|
470
|
+
};
|
|
471
|
+
return {
|
|
472
|
+
graph,
|
|
473
|
+
run(setup) {
|
|
474
|
+
dispose();
|
|
475
|
+
graph.clear();
|
|
476
|
+
runsAtLastSample.clear();
|
|
477
|
+
unnamed.clear();
|
|
478
|
+
seq = 0;
|
|
479
|
+
consumerStack.length = 0;
|
|
480
|
+
createRoot((d) => {
|
|
481
|
+
dispose = d;
|
|
482
|
+
rootOwner = getOwner();
|
|
483
|
+
setup(api);
|
|
484
|
+
});
|
|
485
|
+
},
|
|
486
|
+
sample() {
|
|
487
|
+
const changed = [];
|
|
488
|
+
const now = Date.now();
|
|
489
|
+
for (const node of graph.values()) {
|
|
490
|
+
if (!node.read) {
|
|
491
|
+
const seen = runsAtLastSample.get(node.id);
|
|
492
|
+
if (seen !== void 0 && node.runs > seen) {
|
|
493
|
+
node.lastPulse = now;
|
|
494
|
+
changed.push(node.id);
|
|
495
|
+
}
|
|
496
|
+
runsAtLastSample.set(node.id, node.runs);
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
const raw = node.read();
|
|
500
|
+
const stamped = raw?.$name;
|
|
501
|
+
if (node.kind === "memo" && typeof stamped === "string" && stamped && claims(node, raw)) {
|
|
502
|
+
node.kind = KIND_OF_BUILTIN[stamped] ?? "component";
|
|
503
|
+
node.declaredAt = componentSource(stamped);
|
|
504
|
+
node.source ??= node.declaredAt;
|
|
505
|
+
if (unnamed.has(node.id)) {
|
|
506
|
+
node.label = stamped;
|
|
507
|
+
unnamed.delete(node.id);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const detail2 = formatValue(raw);
|
|
511
|
+
const next = shorten(detail2);
|
|
512
|
+
if (next !== node.value || detail2 !== node.detail) {
|
|
513
|
+
node.detail = detail2;
|
|
514
|
+
node.value = next;
|
|
515
|
+
node.runs++;
|
|
516
|
+
node.lastPulse = now;
|
|
517
|
+
changed.push(node.id);
|
|
518
|
+
}
|
|
519
|
+
runsAtLastSample.set(node.id, node.runs);
|
|
520
|
+
}
|
|
521
|
+
return changed;
|
|
522
|
+
},
|
|
523
|
+
dispose() {
|
|
524
|
+
dispose();
|
|
525
|
+
dispose = () => {
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/observe.ts
|
|
532
|
+
import {
|
|
533
|
+
observeReactiveNodes,
|
|
534
|
+
untrack as untrack2
|
|
535
|
+
} from "@fluixi/reactive/signal";
|
|
536
|
+
|
|
537
|
+
// src/callsite.ts
|
|
538
|
+
var EXTENSION = ["chrome-extension://", "moz-extension://", "safari-web-extension://", "extension://"];
|
|
539
|
+
var INTERNAL = [
|
|
540
|
+
"/packages/reactive/",
|
|
541
|
+
"/packages/dom/",
|
|
542
|
+
"/packages/core/",
|
|
543
|
+
"/packages/devtools/",
|
|
544
|
+
"/@fluixi/",
|
|
545
|
+
"node_modules",
|
|
546
|
+
"fluixi-devtools",
|
|
547
|
+
...EXTENSION
|
|
548
|
+
];
|
|
549
|
+
var REPORTING = [
|
|
550
|
+
"/packages/reactive/src/lib/signal/graph/",
|
|
551
|
+
"/packages/devtools/",
|
|
552
|
+
"fluixi-devtools",
|
|
553
|
+
// Not a fallback either: naming the agent as where a node came from is worse than nothing.
|
|
554
|
+
...EXTENSION
|
|
555
|
+
];
|
|
556
|
+
var FRAME = /(?:at\s+(?:[^(]*\()?)([^()\s]+):(\d+):(\d+)\)?$/;
|
|
557
|
+
var isInternal = (frame) => INTERNAL.some((part) => frame.includes(part));
|
|
558
|
+
function locate2(frame, origin) {
|
|
559
|
+
const match = FRAME.exec(frame);
|
|
560
|
+
if (!match) return void 0;
|
|
561
|
+
let file = match[1];
|
|
562
|
+
if (origin && file.startsWith(origin)) file = file.slice(origin.length);
|
|
563
|
+
file = file.replace(/^\/@fs\//, "/").replace(/\?.*$/, "").replace(/^\//, "");
|
|
564
|
+
return file ? { file, line: Number(match[2]), column: Number(match[3]) } : void 0;
|
|
565
|
+
}
|
|
566
|
+
function callSite(stack, origin) {
|
|
567
|
+
if (!stack) return void 0;
|
|
568
|
+
let fallback2;
|
|
569
|
+
for (const line of stack.split("\n")) {
|
|
570
|
+
const frame = line.trim();
|
|
571
|
+
if (!frame.startsWith("at ")) continue;
|
|
572
|
+
if (!isInternal(frame)) {
|
|
573
|
+
const location2 = locate2(frame, origin);
|
|
574
|
+
if (location2) return { location: location2, external: false };
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (!fallback2 && !REPORTING.some((part) => frame.includes(part))) fallback2 = locate2(frame, origin);
|
|
578
|
+
}
|
|
579
|
+
return fallback2 ? { location: { ...fallback2, external: true }, external: true } : void 0;
|
|
580
|
+
}
|
|
581
|
+
function currentCallSite(origin) {
|
|
582
|
+
return callSite(new Error().stack, origin);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// src/history.ts
|
|
586
|
+
var OFF = {
|
|
587
|
+
wrote() {
|
|
588
|
+
},
|
|
589
|
+
ran() {
|
|
590
|
+
},
|
|
591
|
+
created() {
|
|
592
|
+
},
|
|
593
|
+
disposed() {
|
|
594
|
+
},
|
|
595
|
+
take: () => [],
|
|
596
|
+
dropped: 0,
|
|
597
|
+
clear() {
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
function createHistory(limit) {
|
|
601
|
+
if (limit <= 0) return OFF;
|
|
602
|
+
let events = [];
|
|
603
|
+
let seq = 0;
|
|
604
|
+
let commit = 0;
|
|
605
|
+
let dropped = 0;
|
|
606
|
+
const push = (event) => {
|
|
607
|
+
events.push({ ...event, seq: seq++, t: Date.now(), commit });
|
|
608
|
+
if (events.length > limit) {
|
|
609
|
+
dropped += events.length - limit;
|
|
610
|
+
events = events.slice(-limit);
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
return {
|
|
614
|
+
wrote(id, by, detail2) {
|
|
615
|
+
if (by === void 0) commit++;
|
|
616
|
+
push({ kind: "write", id, by, ...detail2 });
|
|
617
|
+
},
|
|
618
|
+
ran(id, by) {
|
|
619
|
+
push({ kind: "ran", id, by });
|
|
620
|
+
},
|
|
621
|
+
created(id, by, label) {
|
|
622
|
+
push({ kind: "created", id, by, label });
|
|
623
|
+
},
|
|
624
|
+
disposed(id, label) {
|
|
625
|
+
push({ kind: "disposed", id, label });
|
|
626
|
+
},
|
|
627
|
+
take() {
|
|
628
|
+
const taken = events;
|
|
629
|
+
events = [];
|
|
630
|
+
return taken;
|
|
631
|
+
},
|
|
632
|
+
get dropped() {
|
|
633
|
+
return dropped;
|
|
634
|
+
},
|
|
635
|
+
clear() {
|
|
636
|
+
events = [];
|
|
637
|
+
dropped = 0;
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
function toCommits(events) {
|
|
642
|
+
const byNumber = /* @__PURE__ */ new Map();
|
|
643
|
+
for (const event of events) {
|
|
644
|
+
const bucket = byNumber.get(event.commit);
|
|
645
|
+
if (bucket) bucket.push(event);
|
|
646
|
+
else byNumber.set(event.commit, [event]);
|
|
647
|
+
}
|
|
648
|
+
return [...byNumber.entries()].sort((a, b) => a[0] - b[0]).map(([commit, group]) => ({
|
|
649
|
+
commit,
|
|
650
|
+
t: group[0].t,
|
|
651
|
+
events: group,
|
|
652
|
+
cause: group.find((e) => e.kind === "write" && e.by === void 0)
|
|
653
|
+
}));
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// src/diff.ts
|
|
657
|
+
var MAX_ENTRIES2 = 24;
|
|
658
|
+
var ENTRY_VALUE = 120;
|
|
659
|
+
var ENTRY_JSON = 4e3;
|
|
660
|
+
var show = (value) => formatValue(value, ENTRY_VALUE);
|
|
661
|
+
function json(value) {
|
|
662
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
663
|
+
try {
|
|
664
|
+
const text = JSON.stringify(value);
|
|
665
|
+
return text && text.length <= ENTRY_JSON ? text : void 0;
|
|
666
|
+
} catch {
|
|
667
|
+
return void 0;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
var side = (value, key) => {
|
|
671
|
+
const text = json(value);
|
|
672
|
+
return { [key]: show(value), ...text ? { [`${key}Json`]: text } : {} };
|
|
673
|
+
};
|
|
674
|
+
var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
675
|
+
function same(a, b) {
|
|
676
|
+
if (Object.is(a, b)) return true;
|
|
677
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
678
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
679
|
+
const ka = Object.keys(a);
|
|
680
|
+
const kb = Object.keys(b);
|
|
681
|
+
if (ka.length !== kb.length) return false;
|
|
682
|
+
return ka.every((k) => Object.is(a[k], b[k]));
|
|
683
|
+
}
|
|
684
|
+
function diffArray(before, after) {
|
|
685
|
+
let head = 0;
|
|
686
|
+
while (head < before.length && head < after.length && same(before[head], after[head])) head++;
|
|
687
|
+
let tail = 0;
|
|
688
|
+
while (tail < before.length - head && tail < after.length - head && same(before[before.length - 1 - tail], after[after.length - 1 - tail])) {
|
|
689
|
+
tail++;
|
|
690
|
+
}
|
|
691
|
+
const entries = [];
|
|
692
|
+
let skipped = 0;
|
|
693
|
+
const push = (entry) => {
|
|
694
|
+
if (entries.length < MAX_ENTRIES2) entries.push(entry);
|
|
695
|
+
else skipped++;
|
|
696
|
+
};
|
|
697
|
+
const removedCount = before.length - head - tail;
|
|
698
|
+
const addedCount = after.length - head - tail;
|
|
699
|
+
const overlap = Math.min(removedCount, addedCount);
|
|
700
|
+
for (let i = 0; i < overlap; i++) {
|
|
701
|
+
push({
|
|
702
|
+
key: String(head + i),
|
|
703
|
+
op: "changed",
|
|
704
|
+
...side(before[head + i], "from"),
|
|
705
|
+
...side(after[head + i], "to")
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
for (let i = overlap; i < addedCount; i++) {
|
|
709
|
+
push({ key: String(head + i), op: "added", ...side(after[head + i], "to") });
|
|
710
|
+
}
|
|
711
|
+
for (let i = overlap; i < removedCount; i++) {
|
|
712
|
+
push({ key: String(head + i), op: "removed", ...side(before[head + i], "from") });
|
|
713
|
+
}
|
|
714
|
+
return {
|
|
715
|
+
shape: "array",
|
|
716
|
+
entries,
|
|
717
|
+
...skipped ? { more: skipped } : {},
|
|
718
|
+
...before.length !== after.length ? { length: { from: before.length, to: after.length } } : {}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
function diffObject(before, after) {
|
|
722
|
+
const entries = [];
|
|
723
|
+
let skipped = 0;
|
|
724
|
+
const push = (entry) => {
|
|
725
|
+
if (entries.length < MAX_ENTRIES2) entries.push(entry);
|
|
726
|
+
else skipped++;
|
|
727
|
+
};
|
|
728
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])) {
|
|
729
|
+
const had = key in before;
|
|
730
|
+
const has = key in after;
|
|
731
|
+
if (had && has) {
|
|
732
|
+
if (!same(before[key], after[key])) {
|
|
733
|
+
push({ key, op: "changed", ...side(before[key], "from"), ...side(after[key], "to") });
|
|
734
|
+
}
|
|
735
|
+
} else if (has) push({ key, op: "added", ...side(after[key], "to") });
|
|
736
|
+
else push({ key, op: "removed", ...side(before[key], "from") });
|
|
737
|
+
}
|
|
738
|
+
return { shape: "object", entries, ...skipped ? { more: skipped } : {} };
|
|
739
|
+
}
|
|
740
|
+
function diffValues(before, after) {
|
|
741
|
+
if (Array.isArray(before) && Array.isArray(after)) {
|
|
742
|
+
const change2 = diffArray(before, after);
|
|
743
|
+
return change2.entries?.length || change2.length ? change2 : void 0;
|
|
744
|
+
}
|
|
745
|
+
if (isPlainObject(before) && isPlainObject(after)) {
|
|
746
|
+
const change2 = diffObject(before, after);
|
|
747
|
+
return change2.entries?.length ? change2 : void 0;
|
|
748
|
+
}
|
|
749
|
+
return void 0;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// src/describe.ts
|
|
753
|
+
var MAX_CLASSES = 2;
|
|
754
|
+
function textOwner(node) {
|
|
755
|
+
const parent = node.parentElement;
|
|
756
|
+
return parent ? `text in ${describeElement(parent)}` : "text";
|
|
757
|
+
}
|
|
758
|
+
function describeElement(el2) {
|
|
759
|
+
const tag = el2.tagName.toLowerCase();
|
|
760
|
+
const id = el2.id ? `#${el2.id}` : "";
|
|
761
|
+
const raw = typeof el2.className === "string" ? el2.className : el2.getAttribute("class") ?? "";
|
|
762
|
+
const classes = raw.split(/\s+/).filter(Boolean).slice(0, MAX_CLASSES).map((c) => `.${c}`).join("");
|
|
763
|
+
return `${tag}${id}${classes}`;
|
|
764
|
+
}
|
|
765
|
+
function describeNode(target) {
|
|
766
|
+
const node = target;
|
|
767
|
+
if (!node || typeof node.nodeType !== "number") return "detached";
|
|
768
|
+
switch (node.nodeType) {
|
|
769
|
+
case 1:
|
|
770
|
+
return describeElement(node);
|
|
771
|
+
case 3:
|
|
772
|
+
return textOwner(node);
|
|
773
|
+
case 8: {
|
|
774
|
+
const parent = node.parentElement;
|
|
775
|
+
return parent ? `slot in ${describeElement(parent)}` : "slot";
|
|
776
|
+
}
|
|
777
|
+
case 11:
|
|
778
|
+
return "fragment";
|
|
779
|
+
default:
|
|
780
|
+
return node.nodeName?.toLowerCase() ?? "node";
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// src/dom.ts
|
|
785
|
+
var LOC_TAG = "__fx_loc";
|
|
786
|
+
function locationOf(element) {
|
|
787
|
+
return element[LOC_TAG];
|
|
788
|
+
}
|
|
789
|
+
var DOM_BUDGET = 200;
|
|
790
|
+
function describeElement2(element) {
|
|
791
|
+
let out = element.tagName.toLowerCase();
|
|
792
|
+
if (element.id) out += `#${element.id}`;
|
|
793
|
+
const classes = element.classList;
|
|
794
|
+
if (classes.length > 0) {
|
|
795
|
+
out += `.${classes[0]}`;
|
|
796
|
+
if (classes.length > 1) out += `.${classes[1]}`;
|
|
797
|
+
}
|
|
798
|
+
return out;
|
|
799
|
+
}
|
|
800
|
+
function describeAll(graph, roots, writtenBy, kinds, extraFor) {
|
|
801
|
+
const owners = /* @__PURE__ */ new Map();
|
|
802
|
+
const writers = /* @__PURE__ */ new Map();
|
|
803
|
+
const rendered = /* @__PURE__ */ new Map();
|
|
804
|
+
for (const node of graph.values()) {
|
|
805
|
+
if (kinds.has(node.kind)) {
|
|
806
|
+
const found = roots(node.id);
|
|
807
|
+
rendered.set(node.id, found);
|
|
808
|
+
for (const element of found) if (!owners.has(element)) owners.set(element, node.id);
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
for (const element of writtenBy(node.id)) {
|
|
812
|
+
const held = writers.get(element);
|
|
813
|
+
if (held) held.push(node.id);
|
|
814
|
+
else writers.set(element, [node.id]);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
const { claimed, containers } = claimOrphans(graph, rendered, writtenBy, kinds, owners);
|
|
818
|
+
const claims = /* @__PURE__ */ new Map();
|
|
819
|
+
for (const [element, id] of claimed) {
|
|
820
|
+
const held = claims.get(id);
|
|
821
|
+
if (held) held.push(element);
|
|
822
|
+
else claims.set(id, [element]);
|
|
823
|
+
}
|
|
824
|
+
const landing = landings(graph, rendered, writtenBy, kinds, claimed, containers);
|
|
825
|
+
const out = /* @__PURE__ */ new Map();
|
|
826
|
+
for (const node of graph.values()) {
|
|
827
|
+
if (!kinds.has(node.kind)) continue;
|
|
828
|
+
const budget = { left: DOM_BUDGET };
|
|
829
|
+
const walk2 = (element) => {
|
|
830
|
+
const owner = owners.get(element) ?? claimed.get(element);
|
|
831
|
+
const where = locationOf(element);
|
|
832
|
+
if (owner !== void 0 && owner !== node.id) {
|
|
833
|
+
return { label: describeElement2(element), children: [], owner, element, ...where ? { at: where } : {} };
|
|
834
|
+
}
|
|
835
|
+
if (containers.has(element)) {
|
|
836
|
+
return { label: describeElement2(element), children: [], container: true, element, ...where ? { at: where } : {} };
|
|
837
|
+
}
|
|
838
|
+
const children = [];
|
|
839
|
+
const kids = element.children;
|
|
840
|
+
for (let i = 0, n = kids.length; i < n && budget.left > 0; i++) {
|
|
841
|
+
budget.left -= 1;
|
|
842
|
+
children.push(walk2(kids[i]));
|
|
843
|
+
}
|
|
844
|
+
const writes = writers.get(element);
|
|
845
|
+
const extra = extraFor?.(element);
|
|
846
|
+
const inside = landing.get(element)?.filter((id) => within(graph, id, node.id));
|
|
847
|
+
return {
|
|
848
|
+
label: describeElement2(element),
|
|
849
|
+
children,
|
|
850
|
+
element,
|
|
851
|
+
...where ? { at: where } : {},
|
|
852
|
+
...writes?.length ? { writes } : {},
|
|
853
|
+
...inside?.length ? { inside } : {},
|
|
854
|
+
...extra ? { extra } : {}
|
|
855
|
+
};
|
|
856
|
+
};
|
|
857
|
+
out.set(node.id, [...rendered.get(node.id) ?? [], ...claims.get(node.id) ?? []].map(walk2));
|
|
858
|
+
}
|
|
859
|
+
return out;
|
|
860
|
+
}
|
|
861
|
+
function within(graph, id, ancestor) {
|
|
862
|
+
const seen = /* @__PURE__ */ new Set();
|
|
863
|
+
for (let at3 = graph.get(id)?.createdIn; at3 !== void 0 && !seen.has(at3); at3 = graph.get(at3)?.createdIn) {
|
|
864
|
+
if (at3 === ancestor) return true;
|
|
865
|
+
seen.add(at3);
|
|
866
|
+
}
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
869
|
+
function landings(graph, rendered, writtenBy, kinds, claimed, containers) {
|
|
870
|
+
const claimers = new Set(claimed.values());
|
|
871
|
+
const landing = /* @__PURE__ */ new Map();
|
|
872
|
+
for (const node of graph.values()) {
|
|
873
|
+
if (!kinds.has(node.kind) || (rendered.get(node.id) ?? []).length) continue;
|
|
874
|
+
if (claimers.has(node.id)) continue;
|
|
875
|
+
let shallowest;
|
|
876
|
+
let best = Infinity;
|
|
877
|
+
for (const element of writtenBy(node.id)) {
|
|
878
|
+
if (containers.has(element)) continue;
|
|
879
|
+
const depth = depthInDocument(element);
|
|
880
|
+
if (depth < best) {
|
|
881
|
+
best = depth;
|
|
882
|
+
shallowest = element;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
if (!shallowest) continue;
|
|
886
|
+
const held = landing.get(shallowest);
|
|
887
|
+
if (held) held.push(node.id);
|
|
888
|
+
else landing.set(shallowest, [node.id]);
|
|
889
|
+
}
|
|
890
|
+
for (const [element, ids] of landing) {
|
|
891
|
+
landing.set(element, ids.filter((id) => !ids.some((other) => other !== id && within(graph, id, other))));
|
|
892
|
+
}
|
|
893
|
+
return landing;
|
|
894
|
+
}
|
|
895
|
+
function tagDepths(graph, kinds) {
|
|
896
|
+
const depth = /* @__PURE__ */ new Map();
|
|
897
|
+
const of = (id, seen) => {
|
|
898
|
+
const held = depth.get(id);
|
|
899
|
+
if (held !== void 0) return held;
|
|
900
|
+
if (seen.has(id)) return 0;
|
|
901
|
+
seen.add(id);
|
|
902
|
+
const parent = graph.get(id)?.createdIn;
|
|
903
|
+
const value = parent === void 0 ? 0 : of(parent, seen) + 1;
|
|
904
|
+
depth.set(id, value);
|
|
905
|
+
return value;
|
|
906
|
+
};
|
|
907
|
+
for (const node of graph.values()) if (kinds.has(node.kind)) of(node.id, /* @__PURE__ */ new Set());
|
|
908
|
+
return depth;
|
|
909
|
+
}
|
|
910
|
+
function depthInDocument(element) {
|
|
911
|
+
let steps = 0;
|
|
912
|
+
for (let at3 = element.parentElement; at3; at3 = at3.parentElement) steps++;
|
|
913
|
+
return steps;
|
|
914
|
+
}
|
|
915
|
+
function claimOrphans(graph, rendered, writtenBy, kinds, owners) {
|
|
916
|
+
const depth = tagDepths(graph, kinds);
|
|
917
|
+
const wraps = (element) => {
|
|
918
|
+
for (const owned of owners.keys()) if (element !== owned && element.contains(owned)) return true;
|
|
919
|
+
return false;
|
|
920
|
+
};
|
|
921
|
+
const writing = /* @__PURE__ */ new Map();
|
|
922
|
+
for (const node of graph.values()) {
|
|
923
|
+
if (!kinds.has(node.kind) || (rendered.get(node.id) ?? []).length) continue;
|
|
924
|
+
for (const element of writtenBy(node.id)) {
|
|
925
|
+
if (owners.has(element)) continue;
|
|
926
|
+
const held = writing.get(element);
|
|
927
|
+
if (held) held.push(node.id);
|
|
928
|
+
else writing.set(element, [node.id]);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
const claimed = /* @__PURE__ */ new Map();
|
|
932
|
+
const containers = /* @__PURE__ */ new Set();
|
|
933
|
+
const covered = (element) => {
|
|
934
|
+
for (let at3 = element.parentElement; at3; at3 = at3.parentElement) {
|
|
935
|
+
if (owners.has(at3) || claimed.has(at3)) return true;
|
|
936
|
+
}
|
|
937
|
+
return false;
|
|
938
|
+
};
|
|
939
|
+
const order = [...writing.keys()].sort((a, b) => depthInDocument(a) - depthInDocument(b));
|
|
940
|
+
for (const element of order) {
|
|
941
|
+
if (covered(element)) continue;
|
|
942
|
+
const ids = writing.get(element);
|
|
943
|
+
const container = wraps(element);
|
|
944
|
+
const pick = ids.reduce((best, id) => {
|
|
945
|
+
const a = depth.get(id) ?? 0;
|
|
946
|
+
const b = depth.get(best) ?? 0;
|
|
947
|
+
return container ? a < b ? id : best : a > b ? id : best;
|
|
948
|
+
});
|
|
949
|
+
claimed.set(element, pick);
|
|
950
|
+
if (container) containers.add(element);
|
|
951
|
+
}
|
|
952
|
+
return { claimed, containers };
|
|
953
|
+
}
|
|
954
|
+
function withoutElements(nodes) {
|
|
955
|
+
return nodes.map(({ element, children, ...rest }) => ({
|
|
956
|
+
...rest,
|
|
957
|
+
children: withoutElements(children)
|
|
958
|
+
}));
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// src/plugin.ts
|
|
962
|
+
var plugins = [];
|
|
963
|
+
function registerDevtoolsPlugin(plugin) {
|
|
964
|
+
const at3 = plugins.findIndex((p) => p.name === plugin.name);
|
|
965
|
+
if (at3 === -1) plugins.push(plugin);
|
|
966
|
+
else plugins[at3] = plugin;
|
|
967
|
+
}
|
|
968
|
+
function devtoolsPlugins() {
|
|
969
|
+
return plugins;
|
|
970
|
+
}
|
|
971
|
+
function annotationOf(node, name) {
|
|
972
|
+
return node.extra?.[name];
|
|
973
|
+
}
|
|
974
|
+
function pluginRows(node, graph) {
|
|
975
|
+
const out = [];
|
|
976
|
+
for (const plugin of plugins) {
|
|
977
|
+
if (!plugin.rows) continue;
|
|
978
|
+
try {
|
|
979
|
+
out.push(...plugin.rows(node, graph));
|
|
980
|
+
} catch {
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
return out;
|
|
984
|
+
}
|
|
985
|
+
function pluginElementRows(extra) {
|
|
986
|
+
if (!extra) return [];
|
|
987
|
+
const out = [];
|
|
988
|
+
for (const plugin of plugins) {
|
|
989
|
+
if (!plugin.elementRows) continue;
|
|
990
|
+
try {
|
|
991
|
+
out.push(...plugin.elementRows(extra));
|
|
992
|
+
} catch {
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return out;
|
|
996
|
+
}
|
|
997
|
+
function pluginElementBadge(extra) {
|
|
998
|
+
if (!extra) return void 0;
|
|
999
|
+
for (const plugin of plugins) {
|
|
1000
|
+
try {
|
|
1001
|
+
const badge = plugin.elementBadge?.(extra);
|
|
1002
|
+
if (badge) return badge;
|
|
1003
|
+
} catch {
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return void 0;
|
|
1007
|
+
}
|
|
1008
|
+
function pluginBadge(node) {
|
|
1009
|
+
for (const plugin of plugins) {
|
|
1010
|
+
try {
|
|
1011
|
+
const badge = plugin.badge?.(node);
|
|
1012
|
+
if (badge) return badge;
|
|
1013
|
+
} catch {
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return void 0;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/palette.ts
|
|
1020
|
+
var KIND_COLOR = {
|
|
1021
|
+
state: "#34d399",
|
|
1022
|
+
memo: "#a78bfa",
|
|
1023
|
+
effect: "#fbbf24",
|
|
1024
|
+
store: "#38bdf8",
|
|
1025
|
+
resource: "#fb7185",
|
|
1026
|
+
component: "#f0abfc",
|
|
1027
|
+
provider: "#fb923c",
|
|
1028
|
+
control: "#22d3ee",
|
|
1029
|
+
router: "#818cf8",
|
|
1030
|
+
handler: "#f472b6",
|
|
1031
|
+
context: "#a3e635",
|
|
1032
|
+
directive: "#5eead4",
|
|
1033
|
+
binding: "#5eead4",
|
|
1034
|
+
setter: "#fb7185",
|
|
1035
|
+
group: "#8a90a2",
|
|
1036
|
+
element: "#8a90a2"
|
|
1037
|
+
};
|
|
1038
|
+
var kindVar = (kind) => `--fx-kind-${kind}`;
|
|
1039
|
+
var kindColor = (kind) => `var(${kindVar(kind)}, ${KIND_COLOR[kind]})`;
|
|
1040
|
+
var KINDS = Object.keys(KIND_COLOR);
|
|
1041
|
+
function kindRules(selector, property) {
|
|
1042
|
+
return KINDS.map((kind) => `${selector(kind)} { ${property}: ${kindColor(kind)}; }`).join("\n");
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// src/tree.ts
|
|
1046
|
+
var TREE_KINDS = /* @__PURE__ */ new Set([
|
|
1047
|
+
"component",
|
|
1048
|
+
"control",
|
|
1049
|
+
"router",
|
|
1050
|
+
"handler",
|
|
1051
|
+
// A component a plugin recognised as providing a context. Still a tag the author wrote,
|
|
1052
|
+
// so leaving it out took it and everything it rendered out of the tree.
|
|
1053
|
+
"provider"
|
|
1054
|
+
]);
|
|
1055
|
+
function inPageOrder(nodes) {
|
|
1056
|
+
const slots = [];
|
|
1057
|
+
const placed = [];
|
|
1058
|
+
nodes.forEach((node, at3) => {
|
|
1059
|
+
if (node.order !== void 0) {
|
|
1060
|
+
slots.push(at3);
|
|
1061
|
+
placed.push(node);
|
|
1062
|
+
}
|
|
1063
|
+
});
|
|
1064
|
+
if (placed.length < 2) return nodes;
|
|
1065
|
+
placed.sort((a, b) => a.order - b.order);
|
|
1066
|
+
const out = nodes.slice();
|
|
1067
|
+
slots.forEach((slot, i) => out[slot] = placed[i]);
|
|
1068
|
+
return out;
|
|
1069
|
+
}
|
|
1070
|
+
var badges = /* @__PURE__ */ new WeakMap();
|
|
1071
|
+
function badgeOf(node) {
|
|
1072
|
+
if (!badges.has(node)) badges.set(node, pluginBadge(node));
|
|
1073
|
+
return badges.get(node);
|
|
1074
|
+
}
|
|
1075
|
+
function componentTree(graph, options = {}) {
|
|
1076
|
+
const wanted = (node) => TREE_KINDS.has(node.kind) || options.values === true && node.authored !== false;
|
|
1077
|
+
const tagged = inPageOrder([...graph.values()].filter(wanted).sort((a, b) => a.id - b.id));
|
|
1078
|
+
const made = /* @__PURE__ */ new Map();
|
|
1079
|
+
for (const node of tagged) {
|
|
1080
|
+
made.set(node.id, {
|
|
1081
|
+
id: node.id,
|
|
1082
|
+
label: node.label,
|
|
1083
|
+
kind: node.kind,
|
|
1084
|
+
children: [],
|
|
1085
|
+
...TREE_KINDS.has(node.kind) ? {} : { value: true },
|
|
1086
|
+
...node.lazy ? { lazy: true } : {},
|
|
1087
|
+
...badgeOf(node) ? { badge: badgeOf(node) } : {}
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
const owner = (node) => {
|
|
1091
|
+
const seen = /* @__PURE__ */ new Set([node.id]);
|
|
1092
|
+
let at3 = node.createdIn;
|
|
1093
|
+
while (at3 !== void 0 && !seen.has(at3)) {
|
|
1094
|
+
seen.add(at3);
|
|
1095
|
+
const found = made.get(at3);
|
|
1096
|
+
if (found) return found;
|
|
1097
|
+
at3 = graph.get(at3)?.createdIn;
|
|
1098
|
+
}
|
|
1099
|
+
return void 0;
|
|
1100
|
+
};
|
|
1101
|
+
const roots = [];
|
|
1102
|
+
for (const node of tagged) {
|
|
1103
|
+
const parent = owner(node);
|
|
1104
|
+
if (parent) parent.children.push(made.get(node.id));
|
|
1105
|
+
else roots.push(made.get(node.id));
|
|
1106
|
+
}
|
|
1107
|
+
const term = options.filter?.trim().toLowerCase();
|
|
1108
|
+
return term ? keep(roots, term) : roots;
|
|
1109
|
+
}
|
|
1110
|
+
function keep(nodes, term) {
|
|
1111
|
+
const out = [];
|
|
1112
|
+
for (const node of nodes) {
|
|
1113
|
+
const children = keep(node.children, term);
|
|
1114
|
+
const hit = node.label.toLowerCase().includes(term);
|
|
1115
|
+
if (hit || children.length) out.push({ ...node, children: hit ? node.children : children });
|
|
1116
|
+
}
|
|
1117
|
+
return out;
|
|
1118
|
+
}
|
|
1119
|
+
var escape = (text) => text.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
1120
|
+
function createComponentTree(host2, options = {}) {
|
|
1121
|
+
const empty = options.empty ?? "nothing rendered yet";
|
|
1122
|
+
const shut = /* @__PURE__ */ new Set();
|
|
1123
|
+
let signature = "";
|
|
1124
|
+
const defaulted = /* @__PURE__ */ new Set();
|
|
1125
|
+
let marked = null;
|
|
1126
|
+
let markedKey = null;
|
|
1127
|
+
let values = options.values === true;
|
|
1128
|
+
let filter = "";
|
|
1129
|
+
const doc = host2.ownerDocument;
|
|
1130
|
+
const bar = doc.createElement("label");
|
|
1131
|
+
bar.className = "fx-tree-bar";
|
|
1132
|
+
const box = doc.createElement("input");
|
|
1133
|
+
box.type = "checkbox";
|
|
1134
|
+
box.checked = values;
|
|
1135
|
+
bar.append(box, doc.createTextNode("values"));
|
|
1136
|
+
bar.title = "List the signals and memos each tag declares";
|
|
1137
|
+
const search = doc.createElement("input");
|
|
1138
|
+
search.type = "search";
|
|
1139
|
+
search.className = "fx-tree-search";
|
|
1140
|
+
search.placeholder = "filter by name";
|
|
1141
|
+
search.addEventListener("input", () => {
|
|
1142
|
+
filter = search.value;
|
|
1143
|
+
signature = "";
|
|
1144
|
+
});
|
|
1145
|
+
const list = doc.createElement("div");
|
|
1146
|
+
list.className = "fx-tree-list";
|
|
1147
|
+
host2.replaceChildren(bar, search, list);
|
|
1148
|
+
let last = null;
|
|
1149
|
+
box.addEventListener("change", () => {
|
|
1150
|
+
values = box.checked;
|
|
1151
|
+
signature = "";
|
|
1152
|
+
if (last) api.render(last.graph, last.selected);
|
|
1153
|
+
});
|
|
1154
|
+
const row = (node, depth) => {
|
|
1155
|
+
const has = node.children.length > 0;
|
|
1156
|
+
const open = has && !shut.has(node.key);
|
|
1157
|
+
const twist = has ? open ? "▾" : "▸" : node.prop ? "" : "·";
|
|
1158
|
+
const tag = node.props ? `<span class="fx-tree-props-label">props</span><span class="fx-tree-props-count">${node.children.length}</span>` : node.prop ? `<span class="fx-tree-prop-name">${escape(node.prop.name)}</span>${node.prop.value === void 0 ? "" : `<span class="fx-tree-prop-value">${escape(shorten(node.prop.value, 44))}</span>`}` : node.value ? escape(node.label) : has ? `<${escape(node.label)}>` : `<${escape(node.label)} />`;
|
|
1159
|
+
const count = has && !open && !node.props ? `<span class="fx-tree-count">${node.children.length}</span>` : "";
|
|
1160
|
+
const chunk = node.lazy ? '<span class="fx-tree-lazy" title="loaded as its own chunk">lazy</span>' : "";
|
|
1161
|
+
const mark = node.badge ? `<span class="fx-tree-plugin" title="${escape(node.badge)}">${escape(node.badge)}</span>` : "";
|
|
1162
|
+
return `<div class="fx-tree-row n-${node.kind}${node.value ? " is-value" : ""}${node.prop ? " is-prop" : ""}${node.props ? " is-props" : ""}" data-key="${escape(node.key)}"${node.id !== void 0 ? ` data-id="${node.id}"` : ""} style="--depth:${depth}"><button type="button" class="fx-tree-twist" data-twist="${escape(node.key)}"${has ? "" : ' tabindex="-1"'}>${twist}</button><span class="fx-tree-tag"${node.prop?.value ? ` title="${escape(node.prop.name)}=${escape(node.prop.value)}"` : ""}>${tag}</span>${mark}${chunk}${count}</div>` + (open ? node.children.map((child) => row(child, depth + 1)).join("") : "");
|
|
1163
|
+
};
|
|
1164
|
+
const pickOf = (rowEl) => {
|
|
1165
|
+
const found = shown.get(rowEl.dataset.key ?? "");
|
|
1166
|
+
if (!found || found.kind !== "element") return void 0;
|
|
1167
|
+
return {
|
|
1168
|
+
key: found.key,
|
|
1169
|
+
label: found.label,
|
|
1170
|
+
...found.owner !== void 0 ? { owner: found.owner } : {},
|
|
1171
|
+
...found.writes?.length ? { writes: found.writes } : {},
|
|
1172
|
+
...found.at ? { at: found.at } : {},
|
|
1173
|
+
...found.extra ? { extra: found.extra } : {},
|
|
1174
|
+
...found.element ? { element: found.element } : {}
|
|
1175
|
+
};
|
|
1176
|
+
};
|
|
1177
|
+
const onClick = (event) => {
|
|
1178
|
+
const target = event.target;
|
|
1179
|
+
const twist = target?.closest?.("[data-twist]");
|
|
1180
|
+
if (twist) {
|
|
1181
|
+
const key = twist.dataset.twist ?? "";
|
|
1182
|
+
if (shut.has(key)) shut.delete(key);
|
|
1183
|
+
else shut.add(key);
|
|
1184
|
+
signature = "";
|
|
1185
|
+
event.stopPropagation();
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
const rowEl = target?.closest?.(".fx-tree-row");
|
|
1189
|
+
if (!rowEl) return;
|
|
1190
|
+
if (rowEl.dataset.id) {
|
|
1191
|
+
markedKey = null;
|
|
1192
|
+
options.onSelect?.(Number(rowEl.dataset.id));
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
const held = shown.get(rowEl.dataset.key ?? "");
|
|
1196
|
+
if (held?.props) {
|
|
1197
|
+
if (shut.has(held.key)) shut.delete(held.key);
|
|
1198
|
+
else shut.add(held.key);
|
|
1199
|
+
signature = "";
|
|
1200
|
+
if (last) api.render(last.graph, last.selected);
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
if (held?.prop) {
|
|
1204
|
+
if (held.owner === void 0) return;
|
|
1205
|
+
markedKey = held.key;
|
|
1206
|
+
paint();
|
|
1207
|
+
options.onSelect?.(held.owner);
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
const pick = pickOf(rowEl);
|
|
1211
|
+
if (!pick) return;
|
|
1212
|
+
markedKey = pick.key;
|
|
1213
|
+
paint();
|
|
1214
|
+
options.onPick?.(pick);
|
|
1215
|
+
};
|
|
1216
|
+
const onDouble = (event) => {
|
|
1217
|
+
const rowEl = event.target?.closest?.(".fx-tree-row");
|
|
1218
|
+
if (!rowEl) return;
|
|
1219
|
+
if (rowEl.dataset.id) {
|
|
1220
|
+
options.onReveal?.(Number(rowEl.dataset.id));
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
const pick = pickOf(rowEl);
|
|
1224
|
+
if (pick) options.onRevealPick?.(pick);
|
|
1225
|
+
};
|
|
1226
|
+
let shown = /* @__PURE__ */ new Map();
|
|
1227
|
+
const onOver = (event) => {
|
|
1228
|
+
const rowEl = event.target?.closest?.(".fx-tree-row");
|
|
1229
|
+
const found = rowEl ? shown.get(rowEl.dataset.key ?? "") : void 0;
|
|
1230
|
+
if (!found) {
|
|
1231
|
+
options.onHover?.(null);
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
options.onHover?.(found.id ?? null, found.element, found.id === void 0 ? found.key : void 0);
|
|
1235
|
+
};
|
|
1236
|
+
const onOut = () => options.onHover?.(null);
|
|
1237
|
+
host2.addEventListener("click", onClick);
|
|
1238
|
+
host2.addEventListener("dblclick", onDouble);
|
|
1239
|
+
if (options.onHover) {
|
|
1240
|
+
host2.addEventListener("pointermove", onOver);
|
|
1241
|
+
host2.addEventListener("pointerleave", onOut);
|
|
1242
|
+
}
|
|
1243
|
+
const paint = () => {
|
|
1244
|
+
for (const el2 of list.querySelectorAll(".fx-tree-row")) {
|
|
1245
|
+
const row2 = el2;
|
|
1246
|
+
const id = row2.dataset.id;
|
|
1247
|
+
const on = id !== void 0 ? Number(id) === marked : row2.dataset.key === markedKey;
|
|
1248
|
+
el2.classList.toggle("on", on);
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
function rowsFor(nodes, graph) {
|
|
1252
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1253
|
+
const build2 = (node) => {
|
|
1254
|
+
const written = values ? graph.get(node.id)?.props ?? [] : [];
|
|
1255
|
+
const listed = written.map((p, i) => ({
|
|
1256
|
+
key: `${node.id}=${i}`,
|
|
1257
|
+
label: p.name,
|
|
1258
|
+
kind: node.kind,
|
|
1259
|
+
children: [],
|
|
1260
|
+
prop: { name: p.name, ...p.value === void 0 ? {} : { value: p.value } },
|
|
1261
|
+
owner: node.id,
|
|
1262
|
+
...p.at?.line === void 0 ? {} : { at: { file: p.at.file ?? "", line: p.at.line, column: p.at.column ?? 0 } }
|
|
1263
|
+
}));
|
|
1264
|
+
const group = `${node.id}=props`;
|
|
1265
|
+
if (listed.length && !defaulted.has(group)) {
|
|
1266
|
+
defaulted.add(group);
|
|
1267
|
+
shut.add(group);
|
|
1268
|
+
}
|
|
1269
|
+
const props = listed.length ? [{ key: group, label: "props", kind: node.kind, children: listed, props: true, owner: node.id }] : [];
|
|
1270
|
+
const row2 = {
|
|
1271
|
+
key: String(node.id),
|
|
1272
|
+
label: node.label,
|
|
1273
|
+
kind: node.kind,
|
|
1274
|
+
id: node.id,
|
|
1275
|
+
children: [...props, ...node.children.map(build2)],
|
|
1276
|
+
...node.value ? { value: true } : {},
|
|
1277
|
+
...node.lazy ? { lazy: true } : {},
|
|
1278
|
+
...node.badge ? { badge: node.badge } : {}
|
|
1279
|
+
};
|
|
1280
|
+
byId.set(node.id, row2);
|
|
1281
|
+
return row2;
|
|
1282
|
+
};
|
|
1283
|
+
const roots = nodes.map(build2);
|
|
1284
|
+
const domFor = options.domFor;
|
|
1285
|
+
if (!domFor) return roots;
|
|
1286
|
+
const placed = /* @__PURE__ */ new Set();
|
|
1287
|
+
const done = /* @__PURE__ */ new Set();
|
|
1288
|
+
const uses = /* @__PURE__ */ new Map();
|
|
1289
|
+
const tally = (nodes2) => {
|
|
1290
|
+
for (const node of nodes2) {
|
|
1291
|
+
if (node.owner !== void 0) uses.set(node.owner, (uses.get(node.owner) ?? 0) + 1);
|
|
1292
|
+
tally(node.children);
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
for (const row2 of byId.values()) if (row2.id !== void 0) tally(domFor(row2.id));
|
|
1296
|
+
const readerOf = (id, skip) => {
|
|
1297
|
+
const drawn = (from) => {
|
|
1298
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1299
|
+
let at3 = from;
|
|
1300
|
+
while (at3 !== void 0 && !seen.has(at3)) {
|
|
1301
|
+
seen.add(at3);
|
|
1302
|
+
const row2 = byId.get(at3);
|
|
1303
|
+
if (row2 && !row2.value && row2 !== skip) return row2;
|
|
1304
|
+
at3 = graph.get(at3)?.createdIn;
|
|
1305
|
+
}
|
|
1306
|
+
return void 0;
|
|
1307
|
+
};
|
|
1308
|
+
let best;
|
|
1309
|
+
let deepest = -1;
|
|
1310
|
+
for (const [nodeId, node] of graph) {
|
|
1311
|
+
if (nodeId === id || !node.deps.has(id)) continue;
|
|
1312
|
+
const row2 = drawn(nodeId);
|
|
1313
|
+
if (!row2 || row2.id === void 0) continue;
|
|
1314
|
+
const depth = graph.get(row2.id)?.depth ?? 0;
|
|
1315
|
+
if (depth >= deepest) {
|
|
1316
|
+
deepest = depth;
|
|
1317
|
+
best = row2;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
return best;
|
|
1321
|
+
};
|
|
1322
|
+
const again = (row2, key) => ({
|
|
1323
|
+
...row2,
|
|
1324
|
+
key,
|
|
1325
|
+
children: row2.children.map((child, i) => again(child, `${key}/${i}`))
|
|
1326
|
+
});
|
|
1327
|
+
const expand = (row2) => {
|
|
1328
|
+
if (row2.value || row2.id === void 0) return row2;
|
|
1329
|
+
if (done.has(row2)) return row2;
|
|
1330
|
+
done.add(row2);
|
|
1331
|
+
const dom = domFor(row2.id);
|
|
1332
|
+
if (!dom.length) {
|
|
1333
|
+
row2.children = row2.children.map(expand);
|
|
1334
|
+
return row2;
|
|
1335
|
+
}
|
|
1336
|
+
const walk2 = (node, key) => {
|
|
1337
|
+
const owner = node.owner === void 0 ? void 0 : byId.get(node.owner);
|
|
1338
|
+
if (owner && owner !== row2) {
|
|
1339
|
+
if (done.has(owner)) {
|
|
1340
|
+
const count = uses.get(node.owner) ?? 0;
|
|
1341
|
+
return count > 1 ? again(owner, key) : void 0;
|
|
1342
|
+
}
|
|
1343
|
+
placed.add(owner);
|
|
1344
|
+
return expand(owner);
|
|
1345
|
+
}
|
|
1346
|
+
const kids = [];
|
|
1347
|
+
for (const id of node.writes ?? []) {
|
|
1348
|
+
const value = byId.get(id);
|
|
1349
|
+
if (!value?.value || placed.has(value)) continue;
|
|
1350
|
+
placed.add(value);
|
|
1351
|
+
kids.push(value);
|
|
1352
|
+
}
|
|
1353
|
+
for (const id of node.inside ?? []) {
|
|
1354
|
+
const tag = byId.get(id);
|
|
1355
|
+
if (!tag || tag === row2 || placed.has(tag) || done.has(tag)) continue;
|
|
1356
|
+
placed.add(tag);
|
|
1357
|
+
kids.push(expand(tag));
|
|
1358
|
+
}
|
|
1359
|
+
node.children.forEach((child, i) => {
|
|
1360
|
+
const kid = walk2(child, `${key}/${i}`);
|
|
1361
|
+
if (kid) kids.push(kid);
|
|
1362
|
+
});
|
|
1363
|
+
return {
|
|
1364
|
+
key,
|
|
1365
|
+
label: node.label,
|
|
1366
|
+
kind: "element",
|
|
1367
|
+
children: kids,
|
|
1368
|
+
owner: row2.id,
|
|
1369
|
+
...node.writes?.length ? { writes: node.writes } : {},
|
|
1370
|
+
...node.at ? { at: node.at } : {},
|
|
1371
|
+
...node.extra ? { extra: node.extra } : {},
|
|
1372
|
+
...pluginElementBadge(node.extra) ? { badge: pluginElementBadge(node.extra) } : {},
|
|
1373
|
+
...node.element ? { element: node.element } : {}
|
|
1374
|
+
};
|
|
1375
|
+
};
|
|
1376
|
+
const mount = dom.findIndex((node) => node.container);
|
|
1377
|
+
if (mount !== -1) {
|
|
1378
|
+
const node = dom[mount];
|
|
1379
|
+
row2.children = row2.children.map(expand);
|
|
1380
|
+
return {
|
|
1381
|
+
key: `${row2.id}:${mount}`,
|
|
1382
|
+
label: node.label,
|
|
1383
|
+
kind: "element",
|
|
1384
|
+
children: [row2],
|
|
1385
|
+
owner: row2.id,
|
|
1386
|
+
...node.at ? { at: node.at } : {},
|
|
1387
|
+
...node.element ? { element: node.element } : {}
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
const rendered = dom.map((node, i) => walk2(node, `${row2.id}:${i}`)).filter((node) => node !== void 0);
|
|
1391
|
+
const rest = row2.children.filter((child) => !placed.has(child)).map(expand);
|
|
1392
|
+
const lifted = [];
|
|
1393
|
+
const spare = [];
|
|
1394
|
+
const adopt = /* @__PURE__ */ new Map();
|
|
1395
|
+
for (const child of rest) {
|
|
1396
|
+
const reader = child.value && child.id !== void 0 ? readerOf(child.id, row2) : void 0;
|
|
1397
|
+
if (reader) {
|
|
1398
|
+
const held = adopt.get(reader);
|
|
1399
|
+
if (held) held.push(child);
|
|
1400
|
+
else adopt.set(reader, [child]);
|
|
1401
|
+
placed.add(child);
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
(child.value || child.prop || child.props ? lifted : spare).push(child);
|
|
1405
|
+
}
|
|
1406
|
+
for (const [reader, values2] of adopt) reader.children.unshift(...values2);
|
|
1407
|
+
row2.children = [...lifted, ...rendered, ...spare];
|
|
1408
|
+
return row2;
|
|
1409
|
+
};
|
|
1410
|
+
const top = roots.map((root) => placed.has(root) ? root : expand(root));
|
|
1411
|
+
return top.filter((_, i) => !placed.has(roots[i]));
|
|
1412
|
+
}
|
|
1413
|
+
const api = {
|
|
1414
|
+
render(graph, selected = null) {
|
|
1415
|
+
last = { graph, selected };
|
|
1416
|
+
const roots = rowsFor(componentTree(graph, { values, filter }), graph);
|
|
1417
|
+
const next = `${values}|${filter}|${[...shut].sort().join(",")}|${roots.map(function label(n) {
|
|
1418
|
+
return `${n.key}:${n.label}:${n.children.map(label).join("+")}`;
|
|
1419
|
+
}).join(",")}`;
|
|
1420
|
+
if (next !== signature) {
|
|
1421
|
+
signature = next;
|
|
1422
|
+
shown = /* @__PURE__ */ new Map();
|
|
1423
|
+
const index = (list2) => {
|
|
1424
|
+
for (const node of list2) {
|
|
1425
|
+
shown.set(node.key, node);
|
|
1426
|
+
index(node.children);
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
index(roots);
|
|
1430
|
+
list.innerHTML = roots.length ? roots.map((node) => row(node, 0)).join("") : `<span class="fx-tree-empty">${escape(filter ? `nothing named ${filter}` : empty)}</span>`;
|
|
1431
|
+
}
|
|
1432
|
+
if (selected !== marked) {
|
|
1433
|
+
marked = selected;
|
|
1434
|
+
paint();
|
|
1435
|
+
}
|
|
1436
|
+
},
|
|
1437
|
+
clear() {
|
|
1438
|
+
signature = "";
|
|
1439
|
+
marked = null;
|
|
1440
|
+
shut.clear();
|
|
1441
|
+
list.innerHTML = `<span class="fx-tree-empty">${escape(empty)}</span>`;
|
|
1442
|
+
},
|
|
1443
|
+
destroy() {
|
|
1444
|
+
host2.removeEventListener("click", onClick);
|
|
1445
|
+
host2.removeEventListener("dblclick", onDouble);
|
|
1446
|
+
host2.removeEventListener("pointermove", onOver);
|
|
1447
|
+
host2.removeEventListener("pointerleave", onOut);
|
|
1448
|
+
host2.innerHTML = "";
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1451
|
+
return api;
|
|
1452
|
+
}
|
|
1453
|
+
var TREE_CSS = `
|
|
1454
|
+
.fx-tree-row {
|
|
1455
|
+
display: flex; align-items: center; gap: 4px;
|
|
1456
|
+
padding: 1px 6px 1px calc(6px + var(--depth, 0) * 12px);
|
|
1457
|
+
font-family: ui-monospace, monospace; font-size: 11.5px;
|
|
1458
|
+
color: var(--fx-label, #e7e9ee); cursor: pointer; white-space: nowrap;
|
|
1459
|
+
}
|
|
1460
|
+
.fx-tree-row:hover { background: var(--fx-hover, #1b1e26); }
|
|
1461
|
+
.fx-tree-row.on { background: var(--fx-sel, #232838); }
|
|
1462
|
+
.fx-tree-twist {
|
|
1463
|
+
width: 12px; padding: 0; border: 0; background: none; cursor: pointer;
|
|
1464
|
+
color: var(--fx-muted, #8a90a2); font-size: 9px; line-height: 1;
|
|
1465
|
+
}
|
|
1466
|
+
.fx-tree-plugin {
|
|
1467
|
+
font-size: 9px; color: var(--fx-accent, #5eead4);
|
|
1468
|
+
border: 1px solid var(--fx-line, #262a35); border-radius: 6px; padding: 0 4px;
|
|
1469
|
+
max-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
1470
|
+
}
|
|
1471
|
+
.fx-tree-lazy {
|
|
1472
|
+
font-size: 9px; letter-spacing: .04em; color: var(--fx-router, #7dd3fc);
|
|
1473
|
+
border: 1px solid var(--fx-line, #262a35); border-radius: 6px; padding: 0 4px;
|
|
1474
|
+
}
|
|
1475
|
+
.fx-tree-count {
|
|
1476
|
+
font-size: 9.5px; color: var(--fx-muted, #8a90a2);
|
|
1477
|
+
border: 1px solid var(--fx-line, #262a35); border-radius: 6px; padding: 0 4px;
|
|
1478
|
+
}
|
|
1479
|
+
${kindRules((k) => `.fx-tree-row.n-${k} .fx-tree-tag`, "color")}
|
|
1480
|
+
.fx-tree-bar {
|
|
1481
|
+
display: flex; align-items: center; gap: 5px; padding: 2px 8px 5px;
|
|
1482
|
+
font-size: 10.5px; color: var(--fx-muted, #8a90a2); cursor: pointer; user-select: none;
|
|
1483
|
+
}
|
|
1484
|
+
.fx-tree-bar:hover { color: var(--fx-label, #e7e9ee); }
|
|
1485
|
+
.fx-tree-bar input { width: 11px; height: 11px; margin: 0; accent-color: var(--fx-accent, #5eead4); }
|
|
1486
|
+
.fx-tree-row.n-element:hover .fx-tree-tag { color: var(--fx-label, #e7e9ee); }
|
|
1487
|
+
/* A declared value is listed inside the tag that made it, so it reads quieter than a tag. */
|
|
1488
|
+
.fx-tree-row.is-value .fx-tree-tag { font-style: italic; opacity: .85; }
|
|
1489
|
+
.fx-tree-search {
|
|
1490
|
+
width: calc(100% - 16px); margin: 0 8px 4px; padding: 2px 6px;
|
|
1491
|
+
font: inherit; font-size: 10.5px;
|
|
1492
|
+
color: var(--fx-label, #e7e9ee); background: var(--fx-bg, #0f1115);
|
|
1493
|
+
border: 1px solid var(--fx-line, #262a35); border-radius: 4px;
|
|
1494
|
+
}
|
|
1495
|
+
.fx-tree-search:focus { outline: none; border-color: var(--fx-accent, #5eead4); }
|
|
1496
|
+
.fx-tree-empty { display: block; padding: 8px; font-size: 11px; color: var(--fx-muted, #8a90a2); }
|
|
1497
|
+
/* A prop reads as what was written on the tag, so it is quieter than the tag itself. */
|
|
1498
|
+
/* The row that folds a tag's props away. Dashed, because it stands for something written
|
|
1499
|
+
rather than something rendered. */
|
|
1500
|
+
.fx-tree-row.is-props .fx-tree-tag {
|
|
1501
|
+
display: inline-flex; align-items: center; gap: 5px;
|
|
1502
|
+
padding: 0 5px; border: 1px dashed var(--fx-line, #262a35); border-radius: 3px;
|
|
1503
|
+
color: var(--fx-muted, #8a90a2); font-size: 10px; letter-spacing: .04em; text-transform: uppercase;
|
|
1504
|
+
}
|
|
1505
|
+
.fx-tree-row.is-props:hover .fx-tree-tag { border-color: var(--fx-accent, #5eead4); color: var(--fx-label, #e7e9ee); }
|
|
1506
|
+
.fx-tree-props-count { opacity: .7; }
|
|
1507
|
+
|
|
1508
|
+
/* A prop is what was written on the tag above it, so it reads as an aside: the name in a
|
|
1509
|
+
quiet chip, the value beside it, and no marker pretending it opens. */
|
|
1510
|
+
.fx-tree-row.is-prop .fx-tree-tag { display: inline-flex; align-items: baseline; gap: 5px; min-width: 0; }
|
|
1511
|
+
.fx-tree-prop-name {
|
|
1512
|
+
flex: none; padding: 0 4px; border-radius: 3px;
|
|
1513
|
+
background: var(--fx-chip, rgba(138, 144, 162, .14));
|
|
1514
|
+
color: var(--fx-muted, #8a90a2); font-size: 10px; letter-spacing: .02em;
|
|
1515
|
+
}
|
|
1516
|
+
.fx-tree-row.is-prop:hover .fx-tree-prop-name { color: var(--fx-label, #e7e9ee); }
|
|
1517
|
+
.fx-tree-prop-value {
|
|
1518
|
+
min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
1519
|
+
color: var(--fx-label, #e7e9ee); opacity: .75;
|
|
1520
|
+
}
|
|
1521
|
+
`;
|
|
1522
|
+
|
|
1523
|
+
// src/observe.ts
|
|
1524
|
+
function stampDocumentOrder(graph, rendered) {
|
|
1525
|
+
const roots = [];
|
|
1526
|
+
for (const node of graph.values()) {
|
|
1527
|
+
if (!TREE_KINDS.has(node.kind)) continue;
|
|
1528
|
+
const element = rendered(node.id)[0];
|
|
1529
|
+
if (element?.isConnected) roots.push({ id: node.id, element });
|
|
1530
|
+
}
|
|
1531
|
+
roots.sort((a, b) => a.element.compareDocumentPosition(b.element) & 4 ? -1 : 1);
|
|
1532
|
+
for (const node of graph.values()) node.order = void 0;
|
|
1533
|
+
roots.forEach(({ id }, at3) => {
|
|
1534
|
+
const node = graph.get(id);
|
|
1535
|
+
if (node) node.order = at3;
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
var REMOVED_LOG = 500;
|
|
1539
|
+
var KINDS2 = {
|
|
1540
|
+
signal: "state",
|
|
1541
|
+
memo: "memo",
|
|
1542
|
+
effect: "effect",
|
|
1543
|
+
store: "store",
|
|
1544
|
+
resource: "resource"
|
|
1545
|
+
};
|
|
1546
|
+
var HISTORY_VALUE = 80;
|
|
1547
|
+
var SET_SITES = 8;
|
|
1548
|
+
var PREFIX = {
|
|
1549
|
+
state: "s",
|
|
1550
|
+
memo: "m",
|
|
1551
|
+
effect: "e",
|
|
1552
|
+
store: "st",
|
|
1553
|
+
resource: "r",
|
|
1554
|
+
component: "c",
|
|
1555
|
+
// Only the static graph makes these: at runtime control flow is a named memo, and a route
|
|
1556
|
+
// handler never reaches a browser.
|
|
1557
|
+
control: "x",
|
|
1558
|
+
router: "rt",
|
|
1559
|
+
handler: "h",
|
|
1560
|
+
// Never produced here: these are cards a view draws, not nodes anything creates.
|
|
1561
|
+
group: "g",
|
|
1562
|
+
binding: "b",
|
|
1563
|
+
setter: "w",
|
|
1564
|
+
// A plugin's, and it names them itself.
|
|
1565
|
+
directive: "d",
|
|
1566
|
+
context: "ctx",
|
|
1567
|
+
provider: "p"
|
|
1568
|
+
};
|
|
1569
|
+
function readProps(props) {
|
|
1570
|
+
const at3 = propsLocations(props);
|
|
1571
|
+
const out = [];
|
|
1572
|
+
for (const name of Object.keys(props)) {
|
|
1573
|
+
if (name === "children") continue;
|
|
1574
|
+
const where = at3?.[name];
|
|
1575
|
+
const report = {
|
|
1576
|
+
name,
|
|
1577
|
+
...where ? { at: { line: where[0], column: where[1] } } : {}
|
|
1578
|
+
};
|
|
1579
|
+
try {
|
|
1580
|
+
report.value = formatValue(untrack2(() => props[name]), 200);
|
|
1581
|
+
} catch {
|
|
1582
|
+
}
|
|
1583
|
+
out.push(report);
|
|
1584
|
+
}
|
|
1585
|
+
return out.length ? out : void 0;
|
|
1586
|
+
}
|
|
1587
|
+
function observeGraph(options = {}) {
|
|
1588
|
+
installSourceMarker();
|
|
1589
|
+
const origin = options.origin ?? (typeof location === "object" ? location.origin : void 0);
|
|
1590
|
+
const graph = /* @__PURE__ */ new Map();
|
|
1591
|
+
const claims = (node, value) => {
|
|
1592
|
+
for (const dep of node.deps) {
|
|
1593
|
+
const other = graph.get(dep);
|
|
1594
|
+
if (other?.read && other.read() === value) return false;
|
|
1595
|
+
}
|
|
1596
|
+
return true;
|
|
1597
|
+
};
|
|
1598
|
+
const ids = /* @__PURE__ */ new WeakMap();
|
|
1599
|
+
const through = /* @__PURE__ */ new WeakMap();
|
|
1600
|
+
const internals = /* @__PURE__ */ new Set();
|
|
1601
|
+
const targets = /* @__PURE__ */ new WeakMap();
|
|
1602
|
+
const handed = /* @__PURE__ */ new Map();
|
|
1603
|
+
const bindingSites = /* @__PURE__ */ new WeakMap();
|
|
1604
|
+
const boundTo = /* @__PURE__ */ new WeakMap();
|
|
1605
|
+
const drivenBy = /* @__PURE__ */ new WeakMap();
|
|
1606
|
+
let reaches2 = /* @__PURE__ */ new Map();
|
|
1607
|
+
let described = null;
|
|
1608
|
+
const rendered = /* @__PURE__ */ new Map();
|
|
1609
|
+
const elementsIn = (value) => {
|
|
1610
|
+
if (value instanceof Element) return [value];
|
|
1611
|
+
if (value instanceof DocumentFragment) return [...value.children];
|
|
1612
|
+
if (Array.isArray(value)) return value.flatMap(elementsIn);
|
|
1613
|
+
return [];
|
|
1614
|
+
};
|
|
1615
|
+
const cores = /* @__PURE__ */ new Map();
|
|
1616
|
+
const setters = /* @__PURE__ */ new Map();
|
|
1617
|
+
const runsAtLastSample = /* @__PURE__ */ new Map();
|
|
1618
|
+
const unnamed = /* @__PURE__ */ new Set();
|
|
1619
|
+
const recording = (options.history ?? 200) > 0;
|
|
1620
|
+
const wantSites = options.bindingSources ?? true;
|
|
1621
|
+
const mapped = (location2) => {
|
|
1622
|
+
if (!options.mapSource) return location2;
|
|
1623
|
+
try {
|
|
1624
|
+
const found = options.mapSource(location2);
|
|
1625
|
+
return found ? { ...location2, ...found, compiled: { line: location2.line, column: location2.column } } : location2;
|
|
1626
|
+
} catch {
|
|
1627
|
+
return location2;
|
|
1628
|
+
}
|
|
1629
|
+
};
|
|
1630
|
+
const history = createHistory(options.history ?? 200);
|
|
1631
|
+
let seq = 0;
|
|
1632
|
+
const raws = /* @__PURE__ */ new Map();
|
|
1633
|
+
const readRaw = (node) => {
|
|
1634
|
+
if (!node.read) return void 0;
|
|
1635
|
+
try {
|
|
1636
|
+
return untrack2(() => node.read());
|
|
1637
|
+
} catch {
|
|
1638
|
+
return void 0;
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
const removed = [];
|
|
1642
|
+
let revision = 0;
|
|
1643
|
+
const revs = /* @__PURE__ */ new Map();
|
|
1644
|
+
let drained = 0;
|
|
1645
|
+
const onDirective = [];
|
|
1646
|
+
const onContext = [];
|
|
1647
|
+
const retyped = /* @__PURE__ */ new Map();
|
|
1648
|
+
const contextSites = /* @__PURE__ */ new Map();
|
|
1649
|
+
const elementExtra = /* @__PURE__ */ new WeakMap();
|
|
1650
|
+
const ancestor = (parents) => {
|
|
1651
|
+
if (!parents) return void 0;
|
|
1652
|
+
for (let i = parents.length - 1; i >= 0; i--) {
|
|
1653
|
+
const key = parents[i];
|
|
1654
|
+
const found = ids.get(key) ?? through.get(key);
|
|
1655
|
+
if (found !== void 0) return found;
|
|
1656
|
+
}
|
|
1657
|
+
return void 0;
|
|
1658
|
+
};
|
|
1659
|
+
const stop = (options.observe ?? observeReactiveNodes)({
|
|
1660
|
+
created(report) {
|
|
1661
|
+
const core = report.node;
|
|
1662
|
+
if (!core || typeof core !== "object") return;
|
|
1663
|
+
if (report.internal) {
|
|
1664
|
+
const owner2 = ancestor(report.parents);
|
|
1665
|
+
if (owner2 !== void 0) through.set(core, owner2);
|
|
1666
|
+
internals.add(core);
|
|
1667
|
+
if (wantSites) {
|
|
1668
|
+
const site2 = currentCallSite(origin);
|
|
1669
|
+
if (site2) bindingSites.set(core, mapped(site2.location));
|
|
1670
|
+
}
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
const id = seq++;
|
|
1674
|
+
const handle = report.handle;
|
|
1675
|
+
const mark = takeReactiveSource();
|
|
1676
|
+
const base = KINDS2[report.kind];
|
|
1677
|
+
const marked = base === "memo" && mark?.name ? KIND_OF_BUILTIN[mark.name] : void 0;
|
|
1678
|
+
const kind = (report.name ? KIND_OF_BUILTIN[report.name] : void 0) ?? marked ?? base;
|
|
1679
|
+
const named = report.name ?? mark?.name;
|
|
1680
|
+
if (!named) unnamed.add(id);
|
|
1681
|
+
const site = mark?.location ? void 0 : currentCallSite(origin);
|
|
1682
|
+
const node = {
|
|
1683
|
+
id,
|
|
1684
|
+
label: named ?? `${PREFIX[kind]}${id}`,
|
|
1685
|
+
kind,
|
|
1686
|
+
// Only a compiler mark means the author wrote this and gave it that name. A name
|
|
1687
|
+
// the runtime supplies, such as `Show.when`, `mapArray` or `row index`, describes framework
|
|
1688
|
+
// machinery, and so does no name at all.
|
|
1689
|
+
authored: mark?.name != null,
|
|
1690
|
+
deps: /* @__PURE__ */ new Set(),
|
|
1691
|
+
// A write edge needs to know which consumer was running when a setter fired, and
|
|
1692
|
+
// nothing observed records that. Reads carry the graph; writes do not.
|
|
1693
|
+
writes: /* @__PURE__ */ new Set(),
|
|
1694
|
+
runs: 0,
|
|
1695
|
+
lastPulse: 0,
|
|
1696
|
+
core,
|
|
1697
|
+
// The application's line is where it was asked for; a package's is where it was made,
|
|
1698
|
+
// and for a node made while that package loaded it is the only thing there is.
|
|
1699
|
+
...mark?.location ? { source: mark.location } : {},
|
|
1700
|
+
// Mapped, unlike `mark`: these two came off a stack, so they are positions in the
|
|
1701
|
+
// compiled file until a host translates them.
|
|
1702
|
+
...site && !site.external ? { usedAt: mapped(site.location) } : {},
|
|
1703
|
+
...site && site.external && !mark?.location ? { source: mapped(site.location) } : {}
|
|
1704
|
+
};
|
|
1705
|
+
const owner = ancestor(report.parents);
|
|
1706
|
+
if (owner !== void 0) node.createdIn = owner;
|
|
1707
|
+
const originId = report.origin ? ids.get(report.origin) : void 0;
|
|
1708
|
+
const from = originId !== void 0 ? graph.get(originId) : void 0;
|
|
1709
|
+
if (from) {
|
|
1710
|
+
node.createdIn = originId;
|
|
1711
|
+
if (from.source) node.source = from.source;
|
|
1712
|
+
}
|
|
1713
|
+
if (kind !== "effect" && typeof handle === "function") {
|
|
1714
|
+
const read = handle;
|
|
1715
|
+
node.read = () => untrack2(read);
|
|
1716
|
+
}
|
|
1717
|
+
ids.set(core, id);
|
|
1718
|
+
if (handle && (typeof handle === "object" || typeof handle === "function")) {
|
|
1719
|
+
ids.set(handle, id);
|
|
1720
|
+
}
|
|
1721
|
+
cores.set(id, core);
|
|
1722
|
+
if (typeof report.set === "function") setters.set(id, report.set);
|
|
1723
|
+
graph.set(id, node);
|
|
1724
|
+
history.created(id, node.createdIn, node.label);
|
|
1725
|
+
},
|
|
1726
|
+
ran(target) {
|
|
1727
|
+
const id = ids.get(target);
|
|
1728
|
+
if (id !== void 0) {
|
|
1729
|
+
const node = graph.get(id);
|
|
1730
|
+
if (node) node.runs++;
|
|
1731
|
+
history.ran(id);
|
|
1732
|
+
}
|
|
1733
|
+
},
|
|
1734
|
+
wrote(target, by) {
|
|
1735
|
+
const writer = by ? ids.get(by) : void 0;
|
|
1736
|
+
const written = ids.get(target);
|
|
1737
|
+
if (written === void 0) return;
|
|
1738
|
+
if (wantSites && writer === void 0) {
|
|
1739
|
+
const node = graph.get(written);
|
|
1740
|
+
const site = currentCallSite(origin);
|
|
1741
|
+
if (node && site) {
|
|
1742
|
+
const seen = node.setAt ??= [];
|
|
1743
|
+
const at3 = mapped(site.location);
|
|
1744
|
+
if (!seen.some((s) => s.file === at3.file && s.line === at3.line && s.column === at3.column)) {
|
|
1745
|
+
if (seen.length >= SET_SITES) seen.shift();
|
|
1746
|
+
seen.push(at3);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
if (recording) {
|
|
1751
|
+
const node = graph.get(written);
|
|
1752
|
+
const after = node ? readRaw(node) : void 0;
|
|
1753
|
+
const before = raws.get(written);
|
|
1754
|
+
if (node) raws.set(written, after);
|
|
1755
|
+
const shown = node?.detail ?? node?.value;
|
|
1756
|
+
history.wrote(written, writer, {
|
|
1757
|
+
from: shown === void 0 ? void 0 : shorten(shown, HISTORY_VALUE),
|
|
1758
|
+
to: node ? shorten(formatValue(after, HISTORY_VALUE * 2), HISTORY_VALUE) : void 0,
|
|
1759
|
+
// Which row was added is the fact a reader is after.
|
|
1760
|
+
change: diffValues(before, after),
|
|
1761
|
+
label: node?.label
|
|
1762
|
+
});
|
|
1763
|
+
}
|
|
1764
|
+
if (writer === void 0 || writer === written) return;
|
|
1765
|
+
graph.get(writer)?.writes.add(written);
|
|
1766
|
+
},
|
|
1767
|
+
directive(node, target, kind, name, accessor) {
|
|
1768
|
+
if (!onDirective.length) return;
|
|
1769
|
+
const core = node;
|
|
1770
|
+
const owner = core && typeof core === "object" ? ids.get(core) : void 0;
|
|
1771
|
+
const at3 = holeLocation(accessor);
|
|
1772
|
+
const report = {
|
|
1773
|
+
owner: owner !== void 0 ? through.get(core) ?? owner : void 0,
|
|
1774
|
+
kind,
|
|
1775
|
+
name,
|
|
1776
|
+
element: describeNode(target),
|
|
1777
|
+
target,
|
|
1778
|
+
...at3 ? { at: at3 } : {}
|
|
1779
|
+
};
|
|
1780
|
+
for (const fn of onDirective) {
|
|
1781
|
+
try {
|
|
1782
|
+
fn(report);
|
|
1783
|
+
} catch {
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
},
|
|
1787
|
+
contextCreated(id) {
|
|
1788
|
+
if (!onContext.length || contextSites.has(id)) return;
|
|
1789
|
+
const site = currentCallSite(origin);
|
|
1790
|
+
if (site) contextSites.set(id, mapped(site.location));
|
|
1791
|
+
},
|
|
1792
|
+
context(node, id, kind, value) {
|
|
1793
|
+
if (!onContext.length) return;
|
|
1794
|
+
const core = node;
|
|
1795
|
+
const owner = core && typeof core === "object" ? ids.get(core) : void 0;
|
|
1796
|
+
const report = {
|
|
1797
|
+
owner: owner !== void 0 ? through.get(core) ?? owner : void 0,
|
|
1798
|
+
id,
|
|
1799
|
+
name: id.description || "context",
|
|
1800
|
+
...owner !== void 0 ? { ownerLabel: graph.get(owner)?.label } : {},
|
|
1801
|
+
// Where the context itself was written. The provider's line is a fallback, and it
|
|
1802
|
+
// reads as a lie when two providers share one context.
|
|
1803
|
+
...contextSites.get(id) ?? graph.get(owner ?? -1)?.source ? { at: contextSites.get(id) ?? graph.get(owner ?? -1)?.source } : {},
|
|
1804
|
+
kind,
|
|
1805
|
+
...value === void 0 ? {} : { value: formatValue(value) }
|
|
1806
|
+
};
|
|
1807
|
+
for (const fn of onContext) {
|
|
1808
|
+
try {
|
|
1809
|
+
fn(report);
|
|
1810
|
+
} catch {
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
},
|
|
1814
|
+
props(node, props) {
|
|
1815
|
+
const core = node;
|
|
1816
|
+
if (!core || typeof core !== "object" || !props) return;
|
|
1817
|
+
const id = ids.get(core);
|
|
1818
|
+
if (id !== void 0) handed.set(id, props);
|
|
1819
|
+
},
|
|
1820
|
+
bound(node, target, kind, name, accessor) {
|
|
1821
|
+
const core = node;
|
|
1822
|
+
if (!core || typeof core !== "object") return;
|
|
1823
|
+
const hole = holeLocation(accessor);
|
|
1824
|
+
const site = hole ? { ...hole, file: bindingSites.get(core)?.file } : bindingSites.get(core);
|
|
1825
|
+
const box = target instanceof Element ? target : target?.parentElement ?? void 0;
|
|
1826
|
+
if (box) boundTo.set(core, new WeakRef(box));
|
|
1827
|
+
targets.set(core, {
|
|
1828
|
+
kind,
|
|
1829
|
+
element: describeNode(target),
|
|
1830
|
+
...name ? { name } : {},
|
|
1831
|
+
...site ? { source: site } : {}
|
|
1832
|
+
});
|
|
1833
|
+
},
|
|
1834
|
+
moved(target, origin2, name) {
|
|
1835
|
+
const node = graph.get(ids.get(target) ?? -1);
|
|
1836
|
+
if (!node) return;
|
|
1837
|
+
const owner = ids.get(origin2);
|
|
1838
|
+
if (owner !== void 0 && graph.has(owner)) node.createdIn = owner;
|
|
1839
|
+
if (name) {
|
|
1840
|
+
node.label = name;
|
|
1841
|
+
unnamed.delete(node.id);
|
|
1842
|
+
}
|
|
1843
|
+
},
|
|
1844
|
+
disposed(target) {
|
|
1845
|
+
internals.delete(target);
|
|
1846
|
+
const id = ids.get(target);
|
|
1847
|
+
if (id === void 0) return;
|
|
1848
|
+
removed.push({ id, rev: ++revision });
|
|
1849
|
+
revs.delete(id);
|
|
1850
|
+
if (removed.length > REMOVED_LOG) removed.splice(0, removed.length - REMOVED_LOG);
|
|
1851
|
+
history.disposed(id, graph.get(id)?.label);
|
|
1852
|
+
graph.delete(id);
|
|
1853
|
+
handed.delete(id);
|
|
1854
|
+
raws.delete(id);
|
|
1855
|
+
cores.delete(id);
|
|
1856
|
+
rendered.delete(id);
|
|
1857
|
+
setters.delete(id);
|
|
1858
|
+
runsAtLastSample.delete(id);
|
|
1859
|
+
unnamed.delete(id);
|
|
1860
|
+
}
|
|
1861
|
+
});
|
|
1862
|
+
const edges = (id, node) => {
|
|
1863
|
+
const sources = cores.get(id)?.sources;
|
|
1864
|
+
node.deps.clear();
|
|
1865
|
+
if (!sources) return;
|
|
1866
|
+
for (const source of sources) {
|
|
1867
|
+
const dep = ids.get(source);
|
|
1868
|
+
if (dep !== void 0 && dep !== id) node.deps.add(dep);
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1871
|
+
const api = {
|
|
1872
|
+
graph,
|
|
1873
|
+
sample() {
|
|
1874
|
+
described = null;
|
|
1875
|
+
const changed = [];
|
|
1876
|
+
const moved = (id) => {
|
|
1877
|
+
changed.push(id);
|
|
1878
|
+
revs.set(id, ++revision);
|
|
1879
|
+
};
|
|
1880
|
+
const now = Date.now();
|
|
1881
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
1882
|
+
const drives = /* @__PURE__ */ new Map();
|
|
1883
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1884
|
+
for (const internal of internals) {
|
|
1885
|
+
const target = targets.get(internal);
|
|
1886
|
+
const element = boundTo.get(internal);
|
|
1887
|
+
for (const source of internal.sources ?? []) {
|
|
1888
|
+
const id = ids.get(source);
|
|
1889
|
+
if (id === void 0) continue;
|
|
1890
|
+
bindings.set(id, (bindings.get(id) ?? 0) + 1);
|
|
1891
|
+
if (element) {
|
|
1892
|
+
const found = element.deref();
|
|
1893
|
+
if (found && !drivenBy.has(found)) drivenBy.set(found, id);
|
|
1894
|
+
const held = seen.get(id);
|
|
1895
|
+
if (held) held.push(element);
|
|
1896
|
+
else seen.set(id, [element]);
|
|
1897
|
+
}
|
|
1898
|
+
if (!target) continue;
|
|
1899
|
+
const list = drives.get(id);
|
|
1900
|
+
if (list) list.push(target);
|
|
1901
|
+
else drives.set(id, [target]);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
reaches2 = seen;
|
|
1905
|
+
for (const node of graph.values()) {
|
|
1906
|
+
const count = bindings.get(node.id) ?? 0;
|
|
1907
|
+
if (node.bindings !== count) node.bindings = count || void 0;
|
|
1908
|
+
const list = drives.get(node.id);
|
|
1909
|
+
node.targets = list?.length ? list : void 0;
|
|
1910
|
+
}
|
|
1911
|
+
for (const node of graph.values()) {
|
|
1912
|
+
if (!node.read) {
|
|
1913
|
+
edges(node.id, node);
|
|
1914
|
+
const seen2 = runsAtLastSample.get(node.id);
|
|
1915
|
+
if (seen2 === void 0 || node.runs > seen2) {
|
|
1916
|
+
node.lastPulse = now;
|
|
1917
|
+
moved(node.id);
|
|
1918
|
+
}
|
|
1919
|
+
runsAtLastSample.set(node.id, node.runs);
|
|
1920
|
+
continue;
|
|
1921
|
+
}
|
|
1922
|
+
const raw = node.read();
|
|
1923
|
+
if (recording) raws.set(node.id, raw);
|
|
1924
|
+
edges(node.id, node);
|
|
1925
|
+
const stamped = raw?.$name;
|
|
1926
|
+
if (node.kind === "memo" && typeof stamped === "string" && stamped && claims(node, raw)) {
|
|
1927
|
+
node.kind = KIND_OF_BUILTIN[stamped] ?? "component";
|
|
1928
|
+
node.declaredAt = componentSource(stamped);
|
|
1929
|
+
node.source ??= node.declaredAt;
|
|
1930
|
+
if (raw.$lazy) node.lazy = true;
|
|
1931
|
+
if (unnamed.has(node.id)) {
|
|
1932
|
+
node.label = stamped;
|
|
1933
|
+
unnamed.delete(node.id);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
if (TREE_KINDS.has(node.kind)) {
|
|
1937
|
+
const found = elementsIn(raw);
|
|
1938
|
+
if (found.length) rendered.set(node.id, found.map((el2) => new WeakRef(el2)));
|
|
1939
|
+
const given = handed.get(node.id);
|
|
1940
|
+
if (given) node.props = readProps(given);
|
|
1941
|
+
}
|
|
1942
|
+
const detail2 = formatValue(raw);
|
|
1943
|
+
const next = shorten(detail2);
|
|
1944
|
+
if (next !== node.value || detail2 !== node.detail) {
|
|
1945
|
+
node.detail = detail2;
|
|
1946
|
+
node.value = next;
|
|
1947
|
+
if (node.kind === "state") node.runs++;
|
|
1948
|
+
node.lastPulse = now;
|
|
1949
|
+
moved(node.id);
|
|
1950
|
+
}
|
|
1951
|
+
runsAtLastSample.set(node.id, node.runs);
|
|
1952
|
+
}
|
|
1953
|
+
for (const [id, kind] of retyped) {
|
|
1954
|
+
const node = graph.get(id);
|
|
1955
|
+
if (node?.kind === "component" && TREE_KINDS.has(kind)) node.kind = kind;
|
|
1956
|
+
}
|
|
1957
|
+
const unnumbered = () => {
|
|
1958
|
+
for (const node of graph.values()) {
|
|
1959
|
+
if (TREE_KINDS.has(node.kind) && node.order === void 0) return true;
|
|
1960
|
+
}
|
|
1961
|
+
return false;
|
|
1962
|
+
};
|
|
1963
|
+
if (changed.length || unnumbered()) stampDocumentOrder(graph, (of) => api.renderedFor(of));
|
|
1964
|
+
return changed;
|
|
1965
|
+
},
|
|
1966
|
+
get revision() {
|
|
1967
|
+
return revision;
|
|
1968
|
+
},
|
|
1969
|
+
takeRemoved() {
|
|
1970
|
+
const out = removed.filter((entry) => entry.rev > drained).map((entry) => entry.id);
|
|
1971
|
+
drained = revision;
|
|
1972
|
+
return out;
|
|
1973
|
+
},
|
|
1974
|
+
changedSince(rev) {
|
|
1975
|
+
const out = [];
|
|
1976
|
+
for (const [id, at3] of revs) if (at3 > rev) out.push(id);
|
|
1977
|
+
return out;
|
|
1978
|
+
},
|
|
1979
|
+
removedSince(rev) {
|
|
1980
|
+
return removed.filter((entry) => entry.rev > rev).map((entry) => entry.id);
|
|
1981
|
+
},
|
|
1982
|
+
outrun(rev) {
|
|
1983
|
+
return removed.length >= REMOVED_LOG && rev < removed[0].rev;
|
|
1984
|
+
},
|
|
1985
|
+
elementsFor(id) {
|
|
1986
|
+
const live = [];
|
|
1987
|
+
const collect = (from, where = reaches2) => {
|
|
1988
|
+
for (const ref of where.get(from) ?? []) {
|
|
1989
|
+
const found = ref.deref();
|
|
1990
|
+
if (found?.isConnected && !live.includes(found)) live.push(found);
|
|
1991
|
+
}
|
|
1992
|
+
};
|
|
1993
|
+
collect(id, rendered);
|
|
1994
|
+
if (live.length) return live;
|
|
1995
|
+
collect(id);
|
|
1996
|
+
if (!live.length) {
|
|
1997
|
+
const inside = (node) => {
|
|
1998
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1999
|
+
let at3 = node.createdIn;
|
|
2000
|
+
while (at3 !== void 0 && !seen.has(at3)) {
|
|
2001
|
+
if (at3 === id) return true;
|
|
2002
|
+
seen.add(at3);
|
|
2003
|
+
at3 = graph.get(at3)?.createdIn;
|
|
2004
|
+
}
|
|
2005
|
+
return false;
|
|
2006
|
+
};
|
|
2007
|
+
for (const node of graph.values()) if (inside(node)) collect(node.id);
|
|
2008
|
+
}
|
|
2009
|
+
return live;
|
|
2010
|
+
},
|
|
2011
|
+
setValue(id, next) {
|
|
2012
|
+
const node = graph.get(id);
|
|
2013
|
+
const write = setters.get(id);
|
|
2014
|
+
if (!node || node.kind !== "state" || !write) return false;
|
|
2015
|
+
write(next);
|
|
2016
|
+
return true;
|
|
2017
|
+
},
|
|
2018
|
+
renderedFor(id) {
|
|
2019
|
+
const live = [];
|
|
2020
|
+
for (const ref of rendered.get(id) ?? []) {
|
|
2021
|
+
const found = ref.deref();
|
|
2022
|
+
if (found?.isConnected && !live.includes(found)) live.push(found);
|
|
2023
|
+
}
|
|
2024
|
+
return live;
|
|
2025
|
+
},
|
|
2026
|
+
domFor(id) {
|
|
2027
|
+
described ??= describeAll(
|
|
2028
|
+
graph,
|
|
2029
|
+
(of) => api.renderedFor(of),
|
|
2030
|
+
(of) => api.elementsFor(of),
|
|
2031
|
+
TREE_KINDS,
|
|
2032
|
+
(element) => elementExtra.get(element)
|
|
2033
|
+
);
|
|
2034
|
+
return described.get(id) ?? [];
|
|
2035
|
+
},
|
|
2036
|
+
nodeAt(element) {
|
|
2037
|
+
const depth = tagDepths(graph, TREE_KINDS);
|
|
2038
|
+
const rendering = /* @__PURE__ */ new Map();
|
|
2039
|
+
for (const node of graph.values()) {
|
|
2040
|
+
if (!TREE_KINDS.has(node.kind)) continue;
|
|
2041
|
+
for (const found of api.renderedFor(node.id)) {
|
|
2042
|
+
const held = rendering.get(found);
|
|
2043
|
+
if (held === void 0 || (depth.get(node.id) ?? 0) > (depth.get(held) ?? 0)) {
|
|
2044
|
+
rendering.set(found, node.id);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
for (let at3 = element; at3; at3 = at3.parentElement) {
|
|
2049
|
+
const owner = rendering.get(at3);
|
|
2050
|
+
if (owner !== void 0 && graph.has(owner)) return owner;
|
|
2051
|
+
}
|
|
2052
|
+
for (let at3 = element; at3; at3 = at3.parentElement) {
|
|
2053
|
+
const id = drivenBy.get(at3);
|
|
2054
|
+
if (id !== void 0 && graph.has(id)) return id;
|
|
2055
|
+
}
|
|
2056
|
+
return void 0;
|
|
2057
|
+
},
|
|
2058
|
+
takeEvents() {
|
|
2059
|
+
return history.take();
|
|
2060
|
+
},
|
|
2061
|
+
detach() {
|
|
2062
|
+
stop();
|
|
2063
|
+
internals.clear();
|
|
2064
|
+
graph.clear();
|
|
2065
|
+
cores.clear();
|
|
2066
|
+
rendered.clear();
|
|
2067
|
+
setters.clear();
|
|
2068
|
+
runsAtLastSample.clear();
|
|
2069
|
+
unnamed.clear();
|
|
2070
|
+
raws.clear();
|
|
2071
|
+
history.clear();
|
|
2072
|
+
}
|
|
2073
|
+
};
|
|
2074
|
+
for (const plugin of devtoolsPlugins()) {
|
|
2075
|
+
if (!plugin.collect) continue;
|
|
2076
|
+
const context = {
|
|
2077
|
+
onDirective: (fn) => onDirective.push(fn),
|
|
2078
|
+
onContext: (fn) => onContext.push(fn),
|
|
2079
|
+
annotate: (id, data) => {
|
|
2080
|
+
const node = graph.get(id);
|
|
2081
|
+
if (node) (node.extra ??= {})[plugin.name] = data;
|
|
2082
|
+
},
|
|
2083
|
+
annotation: (id) => graph.get(id)?.extra?.[plugin.name],
|
|
2084
|
+
// Remembered, not applied here. A component is still a memo when its context is
|
|
2085
|
+
// provided, and the promotion that gives it its name only runs while it is one.
|
|
2086
|
+
retype: (id, kind) => retyped.set(id, kind),
|
|
2087
|
+
annotateElement: (target, data) => {
|
|
2088
|
+
if (!target || typeof target !== "object") return;
|
|
2089
|
+
const held = elementExtra.get(target) ?? {};
|
|
2090
|
+
held[plugin.name] = data;
|
|
2091
|
+
elementExtra.set(target, held);
|
|
2092
|
+
},
|
|
2093
|
+
elementAnnotation: (target) => target && typeof target === "object" ? elementExtra.get(target)?.[plugin.name] : void 0,
|
|
2094
|
+
add: (spec) => {
|
|
2095
|
+
const id = seq++;
|
|
2096
|
+
graph.set(id, {
|
|
2097
|
+
id,
|
|
2098
|
+
label: spec.label,
|
|
2099
|
+
kind: spec.kind,
|
|
2100
|
+
deps: new Set(spec.deps ?? []),
|
|
2101
|
+
writes: /* @__PURE__ */ new Set(),
|
|
2102
|
+
runs: 0,
|
|
2103
|
+
lastPulse: 0,
|
|
2104
|
+
authored: spec.authored ?? false,
|
|
2105
|
+
...spec.createdIn !== void 0 ? { createdIn: spec.createdIn } : {},
|
|
2106
|
+
...spec.value !== void 0 ? { value: spec.value } : {},
|
|
2107
|
+
...spec.detail !== void 0 ? { detail: spec.detail } : {},
|
|
2108
|
+
...spec.source ? { source: spec.source } : {}
|
|
2109
|
+
});
|
|
2110
|
+
return id;
|
|
2111
|
+
}
|
|
2112
|
+
};
|
|
2113
|
+
try {
|
|
2114
|
+
plugin.collect(context);
|
|
2115
|
+
} catch {
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
return api;
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
// src/highlight.ts
|
|
2122
|
+
var LAYER = "fx-highlight-layer";
|
|
2123
|
+
var overlay = null;
|
|
2124
|
+
function build(doc) {
|
|
2125
|
+
const layer = doc.createElement("div");
|
|
2126
|
+
layer.id = LAYER;
|
|
2127
|
+
layer.style.cssText = "position:fixed;inset:0;z-index:2147483646;pointer-events:none;contain:strict;";
|
|
2128
|
+
const label = doc.createElement("div");
|
|
2129
|
+
label.style.cssText = "position:absolute;padding:1px 5px;border-radius:3px;background:#5eead4;color:#07131a;font:11px/1.5 ui-monospace,monospace;white-space:nowrap;";
|
|
2130
|
+
layer.append(label);
|
|
2131
|
+
doc.body.appendChild(layer);
|
|
2132
|
+
return { layer, boxes: [], label };
|
|
2133
|
+
}
|
|
2134
|
+
function boxIn(overlay2, index) {
|
|
2135
|
+
const held = overlay2.boxes[index];
|
|
2136
|
+
if (held) return held;
|
|
2137
|
+
const box = overlay2.layer.ownerDocument.createElement("div");
|
|
2138
|
+
box.style.cssText = "position:absolute;border:1px solid #5eead4;background:rgba(94,234,212,.16);border-radius:2px;transition:none;";
|
|
2139
|
+
overlay2.layer.append(box);
|
|
2140
|
+
overlay2.boxes.push(box);
|
|
2141
|
+
return box;
|
|
2142
|
+
}
|
|
2143
|
+
function showHighlight(target, text) {
|
|
2144
|
+
const elements = (Array.isArray(target) ? target : [target]).filter(
|
|
2145
|
+
(el2) => el2?.isConnected
|
|
2146
|
+
);
|
|
2147
|
+
const first = elements[0];
|
|
2148
|
+
const doc = first?.ownerDocument;
|
|
2149
|
+
if (!doc?.body) {
|
|
2150
|
+
hideHighlight();
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
if (!overlay || overlay.layer.ownerDocument !== doc || !overlay.layer.isConnected) {
|
|
2154
|
+
overlay = build(doc);
|
|
2155
|
+
}
|
|
2156
|
+
const { label, layer } = overlay;
|
|
2157
|
+
layer.style.display = "";
|
|
2158
|
+
let top = Infinity;
|
|
2159
|
+
let left = Infinity;
|
|
2160
|
+
let bottom = 0;
|
|
2161
|
+
elements.forEach((element, index) => {
|
|
2162
|
+
const rect = element.getBoundingClientRect();
|
|
2163
|
+
const box = boxIn(overlay, index);
|
|
2164
|
+
box.style.display = "";
|
|
2165
|
+
box.style.left = `${rect.left}px`;
|
|
2166
|
+
box.style.top = `${rect.top}px`;
|
|
2167
|
+
box.style.width = `${rect.width}px`;
|
|
2168
|
+
box.style.height = `${rect.height}px`;
|
|
2169
|
+
top = Math.min(top, rect.top);
|
|
2170
|
+
left = Math.min(left, rect.left);
|
|
2171
|
+
bottom = Math.max(bottom, rect.bottom);
|
|
2172
|
+
});
|
|
2173
|
+
for (let i = elements.length; i < overlay.boxes.length; i++) {
|
|
2174
|
+
overlay.boxes[i].style.display = "none";
|
|
2175
|
+
}
|
|
2176
|
+
if (!text) {
|
|
2177
|
+
label.style.display = "none";
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
label.style.display = "";
|
|
2181
|
+
label.textContent = text;
|
|
2182
|
+
label.style.left = `${Math.max(2, left)}px`;
|
|
2183
|
+
label.style.top = top >= 20 ? `${top - 18}px` : `${bottom + 4}px`;
|
|
2184
|
+
}
|
|
2185
|
+
function hideHighlight() {
|
|
2186
|
+
if (overlay) overlay.layer.style.display = "none";
|
|
2187
|
+
}
|
|
2188
|
+
function removeHighlight() {
|
|
2189
|
+
overlay?.layer.remove();
|
|
2190
|
+
overlay = null;
|
|
2191
|
+
}
|
|
2192
|
+
function startPicking(options) {
|
|
2193
|
+
const doc = options.document ?? globalThis.document;
|
|
2194
|
+
if (!doc?.body) return () => {
|
|
2195
|
+
};
|
|
2196
|
+
const was = doc.body.style.cursor;
|
|
2197
|
+
doc.body.style.cursor = "crosshair";
|
|
2198
|
+
const under = (event) => {
|
|
2199
|
+
const target = event.target;
|
|
2200
|
+
return target?.closest?.(`#${LAYER}`) ? null : target;
|
|
2201
|
+
};
|
|
2202
|
+
const move = (event) => {
|
|
2203
|
+
const element = under(event);
|
|
2204
|
+
const id = options.nodeAt(element);
|
|
2205
|
+
if (element && id !== void 0) showHighlight(element, options.describe?.(id, element));
|
|
2206
|
+
else hideHighlight();
|
|
2207
|
+
};
|
|
2208
|
+
const click = (event) => {
|
|
2209
|
+
event.preventDefault();
|
|
2210
|
+
event.stopPropagation();
|
|
2211
|
+
const target = under(event);
|
|
2212
|
+
const id = options.nodeAt(target);
|
|
2213
|
+
if (!options.sticky) stop();
|
|
2214
|
+
if (id !== void 0) options.onPick(id, target);
|
|
2215
|
+
else if (!options.sticky) options.onCancel?.();
|
|
2216
|
+
};
|
|
2217
|
+
const key = (event) => {
|
|
2218
|
+
if (event.key !== "Escape") return;
|
|
2219
|
+
stop();
|
|
2220
|
+
options.onCancel?.();
|
|
2221
|
+
};
|
|
2222
|
+
const stop = () => {
|
|
2223
|
+
doc.body.style.cursor = was;
|
|
2224
|
+
doc.removeEventListener("pointermove", move, true);
|
|
2225
|
+
doc.removeEventListener("click", click, true);
|
|
2226
|
+
doc.removeEventListener("keydown", key, true);
|
|
2227
|
+
hideHighlight();
|
|
2228
|
+
};
|
|
2229
|
+
doc.addEventListener("pointermove", move, true);
|
|
2230
|
+
doc.addEventListener("click", click, true);
|
|
2231
|
+
doc.addEventListener("keydown", key, true);
|
|
2232
|
+
return stop;
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
// src/wire.ts
|
|
2236
|
+
var STATE_NAME = { 0: "clean", 1: "check", 2: "dirty" };
|
|
2237
|
+
var STATE_CODE = { clean: 0, check: 1, dirty: 2 };
|
|
2238
|
+
function serializeGraph(graph, now = Date.now(), options = {}) {
|
|
2239
|
+
const nodes = [];
|
|
2240
|
+
const wanted = options.ids ? new Set(options.ids) : null;
|
|
2241
|
+
for (const node of graph.values()) {
|
|
2242
|
+
if (wanted && !wanted.has(node.id)) continue;
|
|
2243
|
+
const state = typeof node.core?.state === "number" ? STATE_NAME[node.core.state] : void 0;
|
|
2244
|
+
nodes.push({
|
|
2245
|
+
id: node.id,
|
|
2246
|
+
label: node.label,
|
|
2247
|
+
kind: node.kind,
|
|
2248
|
+
authored: node.authored,
|
|
2249
|
+
deps: [...node.deps],
|
|
2250
|
+
writes: [...node.writes],
|
|
2251
|
+
value: node.value,
|
|
2252
|
+
detail: node.detail,
|
|
2253
|
+
runs: node.runs,
|
|
2254
|
+
lastPulse: node.lastPulse,
|
|
2255
|
+
order: node.order,
|
|
2256
|
+
version: node.core?.version,
|
|
2257
|
+
state,
|
|
2258
|
+
loading: node.loading,
|
|
2259
|
+
lazy: node.lazy,
|
|
2260
|
+
props: node.props,
|
|
2261
|
+
extra: node.extra,
|
|
2262
|
+
source: node.source,
|
|
2263
|
+
declaredAt: node.declaredAt,
|
|
2264
|
+
usedAt: node.usedAt,
|
|
2265
|
+
createdIn: node.createdIn,
|
|
2266
|
+
bindings: node.bindings,
|
|
2267
|
+
targets: node.targets,
|
|
2268
|
+
setAt: node.setAt,
|
|
2269
|
+
// Falls back to what the node already carries, so a relay that re-serialises a
|
|
2270
|
+
// snapshot it received does not drop the description.
|
|
2271
|
+
dom: options.dom?.(node.id) ?? node.dom
|
|
2272
|
+
});
|
|
2273
|
+
}
|
|
2274
|
+
const removed = options.removed ? [...options.removed] : void 0;
|
|
2275
|
+
const events = options.events?.length ? [...options.events] : void 0;
|
|
2276
|
+
const cursor = options.cursor;
|
|
2277
|
+
return wanted ? { t: now, nodes, partial: true, removed, events, cursor } : { t: now, nodes, events, cursor };
|
|
2278
|
+
}
|
|
2279
|
+
function snapshotToRenderMap(snapshot) {
|
|
2280
|
+
const map = /* @__PURE__ */ new Map();
|
|
2281
|
+
for (const node of snapshot.nodes) {
|
|
2282
|
+
map.set(node.id, {
|
|
2283
|
+
id: node.id,
|
|
2284
|
+
label: node.label,
|
|
2285
|
+
kind: node.kind,
|
|
2286
|
+
authored: node.authored,
|
|
2287
|
+
deps: new Set(node.deps),
|
|
2288
|
+
writes: new Set(node.writes),
|
|
2289
|
+
value: node.value,
|
|
2290
|
+
detail: node.detail,
|
|
2291
|
+
runs: node.runs,
|
|
2292
|
+
lastPulse: node.lastPulse,
|
|
2293
|
+
order: node.order,
|
|
2294
|
+
core: { version: node.version, state: node.state ? STATE_CODE[node.state] : void 0 },
|
|
2295
|
+
loading: node.loading,
|
|
2296
|
+
lazy: node.lazy,
|
|
2297
|
+
props: node.props,
|
|
2298
|
+
extra: node.extra,
|
|
2299
|
+
source: node.source,
|
|
2300
|
+
declaredAt: node.declaredAt,
|
|
2301
|
+
usedAt: node.usedAt,
|
|
2302
|
+
createdIn: node.createdIn,
|
|
2303
|
+
bindings: node.bindings,
|
|
2304
|
+
targets: node.targets,
|
|
2305
|
+
setAt: node.setAt,
|
|
2306
|
+
dom: node.dom
|
|
2307
|
+
});
|
|
2308
|
+
}
|
|
2309
|
+
return map;
|
|
2310
|
+
}
|
|
2311
|
+
function applySnapshot(graph, snapshot) {
|
|
2312
|
+
if (!snapshot.partial) return snapshotToRenderMap(snapshot);
|
|
2313
|
+
for (const [id, node] of snapshotToRenderMap(snapshot)) graph.set(id, node);
|
|
2314
|
+
for (const id of snapshot.removed ?? []) graph.delete(id);
|
|
2315
|
+
return graph;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
// src/hook.ts
|
|
2319
|
+
var HISTORY = 500;
|
|
2320
|
+
var HOOK = "__FLUIXI_DEVTOOLS_HOOK__";
|
|
2321
|
+
function installDevtoolsHook() {
|
|
2322
|
+
const host2 = globalThis;
|
|
2323
|
+
const existing = host2[HOOK];
|
|
2324
|
+
if (existing) {
|
|
2325
|
+
existing.copies = (existing.copies ?? 1) + 1;
|
|
2326
|
+
return existing;
|
|
2327
|
+
}
|
|
2328
|
+
let observed = null;
|
|
2329
|
+
let owned = false;
|
|
2330
|
+
let version = null;
|
|
2331
|
+
const history = [];
|
|
2332
|
+
const hook = {
|
|
2333
|
+
copies: 1,
|
|
2334
|
+
inject(runtime) {
|
|
2335
|
+
version = runtime.version;
|
|
2336
|
+
if (!observed) {
|
|
2337
|
+
observed = (runtime.watch ?? observeGraph)({ observe: runtime.observeReactiveNodes });
|
|
2338
|
+
owned = runtime.watch !== void 0;
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
if (owned || !runtime.watch) return;
|
|
2342
|
+
observed.detach();
|
|
2343
|
+
observed = runtime.watch({ observe: runtime.observeReactiveNodes });
|
|
2344
|
+
owned = true;
|
|
2345
|
+
},
|
|
2346
|
+
get observed() {
|
|
2347
|
+
return observed;
|
|
2348
|
+
},
|
|
2349
|
+
get version() {
|
|
2350
|
+
return version;
|
|
2351
|
+
},
|
|
2352
|
+
highlight(target) {
|
|
2353
|
+
if (!observed || target === null) {
|
|
2354
|
+
hideHighlight();
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2357
|
+
if (typeof target === "number") {
|
|
2358
|
+
const found = observed.elementsFor(target);
|
|
2359
|
+
if (found.length) showHighlight(found, observed.graph.get(target)?.label);
|
|
2360
|
+
else hideHighlight();
|
|
2361
|
+
return;
|
|
2362
|
+
}
|
|
2363
|
+
const [head, ...steps] = target.split("/");
|
|
2364
|
+
const [tag, root] = (head ?? "").split(":");
|
|
2365
|
+
let node = observed.domFor(Number(tag))[Number(root)];
|
|
2366
|
+
for (const step of steps) node = node?.children[Number(step)];
|
|
2367
|
+
const element = node?.element;
|
|
2368
|
+
if (element) showHighlight(element, element.tagName.toLowerCase());
|
|
2369
|
+
else hideHighlight();
|
|
2370
|
+
},
|
|
2371
|
+
snapshot({ partial = false, since } = {}) {
|
|
2372
|
+
if (!observed) return null;
|
|
2373
|
+
const changed = observed.sample();
|
|
2374
|
+
const cursor = observed.revision;
|
|
2375
|
+
history.push(...observed.takeEvents());
|
|
2376
|
+
if (history.length > HISTORY) history.splice(0, history.length - HISTORY);
|
|
2377
|
+
const events = history.slice();
|
|
2378
|
+
const dom = (id) => {
|
|
2379
|
+
const described = observed.domFor(id);
|
|
2380
|
+
return described.length ? withoutElements(described) : void 0;
|
|
2381
|
+
};
|
|
2382
|
+
const cursors = typeof observed.changedSince === "function";
|
|
2383
|
+
const whole = partial === false || since === void 0 || !cursors || observed.outrun(since);
|
|
2384
|
+
if (whole) {
|
|
2385
|
+
if (partial && !cursors) {
|
|
2386
|
+
return serializeGraph(observed.graph, Date.now(), {
|
|
2387
|
+
ids: changed,
|
|
2388
|
+
removed: observed.takeRemoved(),
|
|
2389
|
+
events,
|
|
2390
|
+
dom
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
return serializeGraph(observed.graph, Date.now(), { events, dom, cursor });
|
|
2394
|
+
}
|
|
2395
|
+
return serializeGraph(observed.graph, Date.now(), {
|
|
2396
|
+
ids: observed.changedSince(since),
|
|
2397
|
+
removed: observed.removedSince(since),
|
|
2398
|
+
events,
|
|
2399
|
+
dom,
|
|
2400
|
+
cursor
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
};
|
|
2404
|
+
host2[HOOK] = hook;
|
|
2405
|
+
return hook;
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
// src/path.ts
|
|
2409
|
+
var normalize = (file) => file.replace(/\\/g, "/");
|
|
2410
|
+
var ROOTED = /^\/|^(Users|home|var|opt|private|tmp|mnt|srv)\//;
|
|
2411
|
+
var WORKSPACE_DIRS = /* @__PURE__ */ new Set(["packages", "apps", "libs", "examples"]);
|
|
2412
|
+
function shortenPath(file, root) {
|
|
2413
|
+
const path = normalize(file);
|
|
2414
|
+
if (root) {
|
|
2415
|
+
const base = normalize(root).replace(/\/+$/, "");
|
|
2416
|
+
if (base && path.startsWith(`${base}/`)) return path.slice(base.length + 1);
|
|
2417
|
+
if (base.startsWith("/") && path.startsWith(`${base.slice(1)}/`)) return path.slice(base.length);
|
|
2418
|
+
}
|
|
2419
|
+
const installed = path.lastIndexOf("node_modules/");
|
|
2420
|
+
if (installed >= 0) return path.slice(installed + "node_modules/".length);
|
|
2421
|
+
if (ROOTED.test(path)) {
|
|
2422
|
+
const segments = path.split("/");
|
|
2423
|
+
for (let i = segments.length - 2; i > 0; i--) {
|
|
2424
|
+
if (WORKSPACE_DIRS.has(segments[i])) return segments.slice(i).join("/");
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
return path;
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
// src/graph-view.ts
|
|
2431
|
+
var SVGNS = "http://www.w3.org/2000/svg";
|
|
2432
|
+
var COL = 200;
|
|
2433
|
+
var ROW = 92;
|
|
2434
|
+
var NW = 132;
|
|
2435
|
+
var placeOf = (node) => node.source ?? node.usedAt ?? node.declaredAt;
|
|
2436
|
+
var shortSource = (s) => {
|
|
2437
|
+
const file = (s.file ?? "").split("/").pop() ?? "";
|
|
2438
|
+
return s.line == null ? file : `${file}:${s.line}`;
|
|
2439
|
+
};
|
|
2440
|
+
var VALUE_CHARS = 7;
|
|
2441
|
+
var fit = (v) => {
|
|
2442
|
+
const text = v ?? "";
|
|
2443
|
+
return text.length > VALUE_CHARS ? text.slice(0, VALUE_CHARS - 1) + "…" : text;
|
|
2444
|
+
};
|
|
2445
|
+
var NH = 62;
|
|
2446
|
+
var PAD = 48;
|
|
2447
|
+
var STEP = 110;
|
|
2448
|
+
var FLASH = 560;
|
|
2449
|
+
var EDGE_FLASH = 260;
|
|
2450
|
+
var MIN_ZOOM = 0.2;
|
|
2451
|
+
var OVER_NATURAL = 12;
|
|
2452
|
+
var MIN_MAX_ZOOM = 8;
|
|
2453
|
+
var ABSOLUTE_MAX_ZOOM = 4e3;
|
|
2454
|
+
var DRAG_SLOP = 4;
|
|
2455
|
+
var KIND_CLASS = { state: "n-state", memo: "n-memo", effect: "n-effect", store: "n-store", resource: "n-resource", component: "n-component", control: "n-control", router: "n-router", handler: "n-handler", group: "n-group", binding: "n-binding", setter: "n-setter", directive: "n-directive", context: "n-context", provider: "n-provider" };
|
|
2456
|
+
var KIND_GLYPH = { state: "◆ state", memo: "ƒ memo", effect: "⚡ effect", store: "▤ store", resource: "⟳ resource", component: "◧ component", control: "⑂ control flow", router: "⤳ router", handler: "⇄ route", group: "▤ framework", binding: "⇥ dom", setter: "✎ sets", directive: "⌘ directive", context: "◈ context", provider: "◈ provider" };
|
|
2457
|
+
var GROUP_ID = -1;
|
|
2458
|
+
var bindingIds = /* @__PURE__ */ new Map();
|
|
2459
|
+
var bindingId = (place2) => {
|
|
2460
|
+
const held = bindingIds.get(place2);
|
|
2461
|
+
if (held !== void 0) return held;
|
|
2462
|
+
const id = -100 - bindingIds.size;
|
|
2463
|
+
bindingIds.set(place2, id);
|
|
2464
|
+
return id;
|
|
2465
|
+
};
|
|
2466
|
+
var setterId = (owner, index) => -1e5 - owner * 32 - index;
|
|
2467
|
+
function el(tag, attrs = {}) {
|
|
2468
|
+
const n = document.createElementNS(SVGNS, tag);
|
|
2469
|
+
for (const k in attrs) n.setAttribute(k, String(attrs[k]));
|
|
2470
|
+
return n;
|
|
2471
|
+
}
|
|
2472
|
+
function computeDepths(graph, deps) {
|
|
2473
|
+
const memo2 = /* @__PURE__ */ new Map();
|
|
2474
|
+
const depth = (id, seen) => {
|
|
2475
|
+
if (memo2.has(id)) return memo2.get(id);
|
|
2476
|
+
if (seen.has(id)) return 0;
|
|
2477
|
+
seen.add(id);
|
|
2478
|
+
const node = graph.get(id);
|
|
2479
|
+
let d = 0;
|
|
2480
|
+
if (node) for (const dep of deps(node)) d = Math.max(d, depth(dep, seen) + 1);
|
|
2481
|
+
seen.delete(id);
|
|
2482
|
+
memo2.set(id, d);
|
|
2483
|
+
return d;
|
|
2484
|
+
};
|
|
2485
|
+
for (const node of graph.values()) node.depth = depth(node.id, /* @__PURE__ */ new Set());
|
|
2486
|
+
}
|
|
2487
|
+
function createGraphView(svg, options = {}) {
|
|
2488
|
+
let signature = "";
|
|
2489
|
+
let selected = null;
|
|
2490
|
+
let focused = null;
|
|
2491
|
+
const nodeEls = /* @__PURE__ */ new Map();
|
|
2492
|
+
const edgeEls = [];
|
|
2493
|
+
const staged = /* @__PURE__ */ new Map();
|
|
2494
|
+
const edgeStaged = /* @__PURE__ */ new Map();
|
|
2495
|
+
const stagedFor = /* @__PURE__ */ new Map();
|
|
2496
|
+
let standIn = (id) => id;
|
|
2497
|
+
const opened2 = /* @__PURE__ */ new Set();
|
|
2498
|
+
const closed = /* @__PURE__ */ new Set();
|
|
2499
|
+
const foldStores = options.collapseStores !== false;
|
|
2500
|
+
let direction = options.direction ?? "horizontal";
|
|
2501
|
+
let groupExternal = options.groupExternal !== false;
|
|
2502
|
+
let showBindings = options.showBindings === true;
|
|
2503
|
+
let kinds = options.kinds ? new Set(options.kinds) : null;
|
|
2504
|
+
const uid = Math.random().toString(36).slice(2, 8);
|
|
2505
|
+
const labelClip = `fx-label-clip-${uid}`;
|
|
2506
|
+
const valueClip = `fx-value-clip-${uid}`;
|
|
2507
|
+
const LABEL_W = 66;
|
|
2508
|
+
const defs = el("defs");
|
|
2509
|
+
const forLabel = el("clipPath", { id: labelClip });
|
|
2510
|
+
forLabel.appendChild(el("rect", { x: 8, y: 0, width: LABEL_W, height: NH }));
|
|
2511
|
+
const sourceClip = `fx-source-clip-${uid}`;
|
|
2512
|
+
const forSource = el("clipPath", { id: sourceClip });
|
|
2513
|
+
forSource.appendChild(el("rect", { x: 8, y: 0, width: NW - 16, height: NH }));
|
|
2514
|
+
const forValue = el("clipPath", { id: valueClip });
|
|
2515
|
+
forValue.appendChild(el("rect", { x: 8 + LABEL_W + 6, y: 0, width: NW - LABEL_W - 10, height: NH }));
|
|
2516
|
+
defs.append(forLabel, forValue, forSource);
|
|
2517
|
+
const viewport = el("g", { class: "fx-viewport" });
|
|
2518
|
+
let edgeLayer = el("g", { class: "fx-edges" });
|
|
2519
|
+
let nodeLayer = el("g", { class: "fx-nodes" });
|
|
2520
|
+
viewport.append(edgeLayer, nodeLayer);
|
|
2521
|
+
svg.append(defs, viewport);
|
|
2522
|
+
const maxZoom = () => {
|
|
2523
|
+
const box = svg.viewBox?.baseVal;
|
|
2524
|
+
const rect = svg.getBoundingClientRect?.();
|
|
2525
|
+
if (!box?.width || !rect?.width || !rect.height) return MIN_MAX_ZOOM;
|
|
2526
|
+
const natural = Math.max(box.width / rect.width, (box.height || 0) / rect.height);
|
|
2527
|
+
return Math.min(ABSOLUTE_MAX_ZOOM, Math.max(MIN_MAX_ZOOM, natural * OVER_NATURAL));
|
|
2528
|
+
};
|
|
2529
|
+
let scale = 1;
|
|
2530
|
+
let panX = 0;
|
|
2531
|
+
let panY = 0;
|
|
2532
|
+
const applyView = () => viewport.setAttribute("transform", `translate(${panX} ${panY}) scale(${scale})`);
|
|
2533
|
+
const toUserSpace = (clientX, clientY) => {
|
|
2534
|
+
const ctm = svg.getScreenCTM?.();
|
|
2535
|
+
if (!ctm) return { x: 0, y: 0 };
|
|
2536
|
+
const point = Object.assign(svg.createSVGPoint(), { x: clientX, y: clientY }).matrixTransform(ctm.inverse());
|
|
2537
|
+
return { x: point.x, y: point.y };
|
|
2538
|
+
};
|
|
2539
|
+
const zoomAt = (factor, at3) => {
|
|
2540
|
+
const next = Math.min(maxZoom(), Math.max(MIN_ZOOM, scale * factor));
|
|
2541
|
+
if (next === scale) return;
|
|
2542
|
+
panX = at3.x - (at3.x - panX) * next / scale;
|
|
2543
|
+
panY = at3.y - (at3.y - panY) * next / scale;
|
|
2544
|
+
scale = next;
|
|
2545
|
+
applyView();
|
|
2546
|
+
};
|
|
2547
|
+
svg.addEventListener(
|
|
2548
|
+
"wheel",
|
|
2549
|
+
(event) => {
|
|
2550
|
+
event.preventDefault();
|
|
2551
|
+
zoomAt(Math.exp(-event.deltaY * 15e-4), toUserSpace(event.clientX, event.clientY));
|
|
2552
|
+
},
|
|
2553
|
+
{ passive: false }
|
|
2554
|
+
);
|
|
2555
|
+
let dragging = null;
|
|
2556
|
+
svg.addEventListener("pointerdown", (event) => {
|
|
2557
|
+
if (event.button !== 0) return;
|
|
2558
|
+
dragging = { x: event.clientX, y: event.clientY, panX, panY, moved: false };
|
|
2559
|
+
});
|
|
2560
|
+
svg.addEventListener("pointermove", (event) => {
|
|
2561
|
+
if (!dragging) return;
|
|
2562
|
+
const dx = event.clientX - dragging.x;
|
|
2563
|
+
const dy = event.clientY - dragging.y;
|
|
2564
|
+
if (!dragging.moved && Math.abs(dx) + Math.abs(dy) < DRAG_SLOP) return;
|
|
2565
|
+
if (!dragging.moved) svg.setPointerCapture?.(event.pointerId);
|
|
2566
|
+
dragging.moved = true;
|
|
2567
|
+
const ratio = svg.getScreenCTM?.()?.a || 1;
|
|
2568
|
+
panX = dragging.panX + dx / ratio;
|
|
2569
|
+
panY = dragging.panY + dy / ratio;
|
|
2570
|
+
applyView();
|
|
2571
|
+
});
|
|
2572
|
+
const endDrag = (event) => {
|
|
2573
|
+
if (dragging?.moved) svg.releasePointerCapture?.(event.pointerId);
|
|
2574
|
+
setTimeout(() => {
|
|
2575
|
+
dragging = null;
|
|
2576
|
+
}, 0);
|
|
2577
|
+
};
|
|
2578
|
+
svg.addEventListener("pointerup", endDrag);
|
|
2579
|
+
svg.addEventListener("pointercancel", endDrag);
|
|
2580
|
+
svg.addEventListener("dblclick", () => {
|
|
2581
|
+
scale = 1;
|
|
2582
|
+
panX = 0;
|
|
2583
|
+
panY = 0;
|
|
2584
|
+
applyView();
|
|
2585
|
+
});
|
|
2586
|
+
function stageWave(graph, now) {
|
|
2587
|
+
const fresh = [];
|
|
2588
|
+
for (const node of graph.values()) {
|
|
2589
|
+
if (!node.lastPulse || stagedFor.get(node.id) === node.lastPulse) continue;
|
|
2590
|
+
stagedFor.set(node.id, node.lastPulse);
|
|
2591
|
+
if (nodeEls.has(node.id)) fresh.push(node.id);
|
|
2592
|
+
}
|
|
2593
|
+
if (!fresh.length) return;
|
|
2594
|
+
const changed = new Set(fresh);
|
|
2595
|
+
const after = /* @__PURE__ */ new Map();
|
|
2596
|
+
for (const { a, b } of edgeEls) {
|
|
2597
|
+
if (changed.has(a) && changed.has(b)) (after.get(a) ?? after.set(a, []).get(a)).push(b);
|
|
2598
|
+
}
|
|
2599
|
+
const fed = new Set([...after.values()].flat());
|
|
2600
|
+
const step = /* @__PURE__ */ new Map();
|
|
2601
|
+
const queue = fresh.filter((id) => !fed.has(id)).map((id) => ({ id, depth: 0 }));
|
|
2602
|
+
if (!queue.length) for (const id of fresh) queue.push({ id, depth: 0 });
|
|
2603
|
+
while (queue.length) {
|
|
2604
|
+
const { id, depth } = queue.shift();
|
|
2605
|
+
if (step.has(id)) continue;
|
|
2606
|
+
step.set(id, depth);
|
|
2607
|
+
for (const next of after.get(id) ?? []) if (!step.has(next)) queue.push({ id: next, depth: depth + 1 });
|
|
2608
|
+
}
|
|
2609
|
+
for (const id of fresh) staged.set(id, now + (step.get(id) ?? 0) * STEP);
|
|
2610
|
+
for (const { a, b, el: el2 } of edgeEls) {
|
|
2611
|
+
const from = step.get(a);
|
|
2612
|
+
const to = step.get(b);
|
|
2613
|
+
if (from === void 0 || to === void 0 || to <= from) continue;
|
|
2614
|
+
edgeStaged.set(el2, now + from * STEP + STEP * 0.35);
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
function applySelected() {
|
|
2618
|
+
for (const [id, { g }] of nodeEls) g.classList.toggle("selected", id === selected);
|
|
2619
|
+
}
|
|
2620
|
+
function related(graph, from) {
|
|
2621
|
+
const feeds = /* @__PURE__ */ new Map();
|
|
2622
|
+
const fedBy = /* @__PURE__ */ new Map();
|
|
2623
|
+
const link = (source, target) => {
|
|
2624
|
+
(feeds.get(source) ?? feeds.set(source, /* @__PURE__ */ new Set()).get(source)).add(target);
|
|
2625
|
+
(fedBy.get(target) ?? fedBy.set(target, /* @__PURE__ */ new Set()).get(target)).add(source);
|
|
2626
|
+
};
|
|
2627
|
+
for (const node2 of graph.values()) {
|
|
2628
|
+
for (const dep of node2.deps) link(dep, node2.id);
|
|
2629
|
+
for (const written of node2.writes) link(node2.id, written);
|
|
2630
|
+
}
|
|
2631
|
+
const out = /* @__PURE__ */ new Set([from]);
|
|
2632
|
+
const walk2 = (edges) => {
|
|
2633
|
+
const queue = [from];
|
|
2634
|
+
while (queue.length) {
|
|
2635
|
+
for (const next of edges.get(queue.pop()) ?? []) {
|
|
2636
|
+
if (!out.has(next)) {
|
|
2637
|
+
out.add(next);
|
|
2638
|
+
queue.push(next);
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
};
|
|
2643
|
+
walk2(feeds);
|
|
2644
|
+
walk2(fedBy);
|
|
2645
|
+
const node = graph.get(from);
|
|
2646
|
+
if (node?.createdIn !== void 0) out.add(node.createdIn);
|
|
2647
|
+
for (const other of graph.values()) if (other.createdIn === from) out.add(other.id);
|
|
2648
|
+
return out;
|
|
2649
|
+
}
|
|
2650
|
+
function light(set) {
|
|
2651
|
+
if (!set) {
|
|
2652
|
+
svg.classList.remove("fx-hovering");
|
|
2653
|
+
for (const { g } of nodeEls.values()) g.classList.remove("hot");
|
|
2654
|
+
for (const { el: path } of edgeEls) path.classList.remove("hot");
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
svg.classList.add("fx-hovering");
|
|
2658
|
+
for (const [nodeId, { g }] of nodeEls) g.classList.toggle("hot", set.has(nodeId));
|
|
2659
|
+
for (const { a, b, el: path } of edgeEls) path.classList.toggle("hot", set.has(a) && set.has(b));
|
|
2660
|
+
}
|
|
2661
|
+
function hover(graph, id) {
|
|
2662
|
+
if (id === null) return light(focused);
|
|
2663
|
+
const set = new Set([...related(graph, id)].map(standIn));
|
|
2664
|
+
light(set);
|
|
2665
|
+
nodeEls.get(standIn(id))?.g.classList.add("hot");
|
|
2666
|
+
}
|
|
2667
|
+
function withEdges(graph) {
|
|
2668
|
+
if (!showBindings) return graph;
|
|
2669
|
+
const shown = new Map(graph);
|
|
2670
|
+
let added = false;
|
|
2671
|
+
for (const node of graph.values()) {
|
|
2672
|
+
node.targets?.forEach((target) => {
|
|
2673
|
+
const at3 = target.source;
|
|
2674
|
+
const place2 = `${target.name ?? ""}|${target.element}|${at3?.file ?? ""}:${at3?.line ?? ""}:${at3?.column ?? ""}`;
|
|
2675
|
+
const id = bindingId(place2);
|
|
2676
|
+
const card = shown.get(id) ?? {
|
|
2677
|
+
id,
|
|
2678
|
+
label: target.name ? `${target.name} of ${target.element}` : target.element,
|
|
2679
|
+
kind: "binding",
|
|
2680
|
+
// The card reads the signal, so the arrow runs the way the value does.
|
|
2681
|
+
deps: /* @__PURE__ */ new Set(),
|
|
2682
|
+
writes: /* @__PURE__ */ new Set(),
|
|
2683
|
+
runs: 0,
|
|
2684
|
+
lastPulse: 0,
|
|
2685
|
+
...at3 ? { source: at3 } : {}
|
|
2686
|
+
};
|
|
2687
|
+
card.deps.add(node.id);
|
|
2688
|
+
card.runs = Math.max(card.runs, node.runs);
|
|
2689
|
+
card.lastPulse = Math.max(card.lastPulse, node.lastPulse);
|
|
2690
|
+
shown.set(id, card);
|
|
2691
|
+
added = true;
|
|
2692
|
+
});
|
|
2693
|
+
node.setAt?.forEach((at3, i) => {
|
|
2694
|
+
const id = setterId(node.id, i);
|
|
2695
|
+
shown.set(id, {
|
|
2696
|
+
id,
|
|
2697
|
+
// Two handlers in one component can share an original position; the compiled line
|
|
2698
|
+
// is what separates the cards.
|
|
2699
|
+
label: `set at ${(at3.file ?? "").split("/").pop() ?? "?"}:${at3.line ?? ""}${at3.compiled && at3.compiled.line !== at3.line ? `→${at3.compiled.line}` : ""}`,
|
|
2700
|
+
kind: "setter",
|
|
2701
|
+
deps: /* @__PURE__ */ new Set(),
|
|
2702
|
+
// A handler writes the signal, so it gets the write edge a computation would.
|
|
2703
|
+
writes: /* @__PURE__ */ new Set([node.id]),
|
|
2704
|
+
runs: node.runs,
|
|
2705
|
+
// The write edge runs setter → signal, so the wave starts here.
|
|
2706
|
+
lastPulse: node.lastPulse,
|
|
2707
|
+
source: at3
|
|
2708
|
+
});
|
|
2709
|
+
added = true;
|
|
2710
|
+
});
|
|
2711
|
+
}
|
|
2712
|
+
return added ? shown : graph;
|
|
2713
|
+
}
|
|
2714
|
+
const inProject = (at3) => !!at3?.file && at3.external !== true;
|
|
2715
|
+
const machinery = (n) => {
|
|
2716
|
+
if (n.authored !== false) return false;
|
|
2717
|
+
if (n.kind === "state" || n.kind === "memo" || n.kind === "effect" || n.kind === "context") {
|
|
2718
|
+
return true;
|
|
2719
|
+
}
|
|
2720
|
+
if (n.kind === "component" || n.kind === "provider") {
|
|
2721
|
+
return !inProject(n.declaredAt) && !inProject(n.source) && !inProject(n.usedAt);
|
|
2722
|
+
}
|
|
2723
|
+
return false;
|
|
2724
|
+
};
|
|
2725
|
+
const VIEW_CARD = /* @__PURE__ */ new Set(["group", "binding", "setter"]);
|
|
2726
|
+
const kindKey = () => kinds ? [...kinds].sort().join("+") : "*";
|
|
2727
|
+
function onlyKinds(graph) {
|
|
2728
|
+
if (!kinds) return graph;
|
|
2729
|
+
const keep2 = (n) => VIEW_CARD.has(n.kind) || kinds.has(n.kind);
|
|
2730
|
+
const out = /* @__PURE__ */ new Map();
|
|
2731
|
+
for (const [id, node] of graph) {
|
|
2732
|
+
if (!keep2(node)) continue;
|
|
2733
|
+
let owner = node.createdIn;
|
|
2734
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2735
|
+
while (owner !== void 0 && !seen.has(owner)) {
|
|
2736
|
+
seen.add(owner);
|
|
2737
|
+
const up = graph.get(owner);
|
|
2738
|
+
if (up && keep2(up)) break;
|
|
2739
|
+
owner = up?.createdIn;
|
|
2740
|
+
}
|
|
2741
|
+
out.set(id, owner === node.createdIn ? node : { ...node, createdIn: owner });
|
|
2742
|
+
}
|
|
2743
|
+
return out;
|
|
2744
|
+
}
|
|
2745
|
+
function grouped(graph) {
|
|
2746
|
+
if (!groupExternal) return graph;
|
|
2747
|
+
const outsiders = [...graph.values()].filter(
|
|
2748
|
+
// A card carries the source of whatever made it, often a package. It already hangs off
|
|
2749
|
+
// its signal and must not be moved under the group.
|
|
2750
|
+
(n) => n.source?.external && n.createdIn === void 0 && n.kind !== "binding" && n.kind !== "setter" || machinery(n)
|
|
2751
|
+
);
|
|
2752
|
+
if (!outsiders.length) return graph;
|
|
2753
|
+
const shown = new Map(graph);
|
|
2754
|
+
shown.set(GROUP_ID, {
|
|
2755
|
+
id: GROUP_ID,
|
|
2756
|
+
label: "framework",
|
|
2757
|
+
kind: "group",
|
|
2758
|
+
deps: /* @__PURE__ */ new Set(),
|
|
2759
|
+
writes: /* @__PURE__ */ new Set(),
|
|
2760
|
+
runs: 0,
|
|
2761
|
+
lastPulse: 0
|
|
2762
|
+
});
|
|
2763
|
+
const moved = new Set(outsiders.map((n) => n.id));
|
|
2764
|
+
for (const node of outsiders) shown.set(node.id, { ...node, createdIn: GROUP_ID });
|
|
2765
|
+
for (const [id, node] of graph) {
|
|
2766
|
+
if (moved.has(id) || node.createdIn === void 0) continue;
|
|
2767
|
+
let owner = node.createdIn;
|
|
2768
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2769
|
+
while (owner !== void 0 && moved.has(owner) && !seen.has(owner)) {
|
|
2770
|
+
seen.add(owner);
|
|
2771
|
+
owner = graph.get(owner)?.createdIn;
|
|
2772
|
+
}
|
|
2773
|
+
if (owner !== node.createdIn) shown.set(id, { ...node, createdIn: owner });
|
|
2774
|
+
}
|
|
2775
|
+
return shown;
|
|
2776
|
+
}
|
|
2777
|
+
function rebuild(graph, folded) {
|
|
2778
|
+
edgeLayer.remove();
|
|
2779
|
+
nodeLayer.remove();
|
|
2780
|
+
nodeEls.clear();
|
|
2781
|
+
edgeEls.length = 0;
|
|
2782
|
+
edgeStaged.clear();
|
|
2783
|
+
edgeLayer = el("g", { class: "fx-edges" });
|
|
2784
|
+
nodeLayer = el("g", { class: "fx-nodes" });
|
|
2785
|
+
standIn = (id) => {
|
|
2786
|
+
let at3 = id;
|
|
2787
|
+
let outermost = id;
|
|
2788
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2789
|
+
while (at3 !== void 0 && !seen.has(at3)) {
|
|
2790
|
+
seen.add(at3);
|
|
2791
|
+
if (folded.has(at3) && at3 !== id) outermost = at3;
|
|
2792
|
+
at3 = graph.get(at3)?.createdIn;
|
|
2793
|
+
}
|
|
2794
|
+
return outermost;
|
|
2795
|
+
};
|
|
2796
|
+
const visible = /* @__PURE__ */ new Map();
|
|
2797
|
+
const hiddenCount = /* @__PURE__ */ new Map();
|
|
2798
|
+
for (const node of graph.values()) {
|
|
2799
|
+
const shown = standIn(node.id);
|
|
2800
|
+
if (shown === node.id) visible.set(node.id, node);
|
|
2801
|
+
else hiddenCount.set(shown, (hiddenCount.get(shown) ?? 0) + 1);
|
|
2802
|
+
}
|
|
2803
|
+
const readsOf = (node) => {
|
|
2804
|
+
const out = /* @__PURE__ */ new Set();
|
|
2805
|
+
for (const dep of node.deps) {
|
|
2806
|
+
const from = standIn(dep);
|
|
2807
|
+
if (from !== node.id && visible.has(from)) out.add(from);
|
|
2808
|
+
}
|
|
2809
|
+
if (node.createdIn !== void 0) {
|
|
2810
|
+
const owner = standIn(node.createdIn);
|
|
2811
|
+
if (owner !== node.id && visible.has(owner)) out.add(owner);
|
|
2812
|
+
}
|
|
2813
|
+
return out;
|
|
2814
|
+
};
|
|
2815
|
+
computeDepths(visible, readsOf);
|
|
2816
|
+
const byDepth = /* @__PURE__ */ new Map();
|
|
2817
|
+
let maxDepth = 0;
|
|
2818
|
+
for (const node of visible.values()) {
|
|
2819
|
+
const d = node.depth ?? 0;
|
|
2820
|
+
(byDepth.get(d) ?? byDepth.set(d, []).get(d)).push(node);
|
|
2821
|
+
maxDepth = Math.max(maxDepth, d);
|
|
2822
|
+
}
|
|
2823
|
+
for (const [d, nodes] of byDepth) byDepth.set(d, inPageOrder(nodes));
|
|
2824
|
+
let maxRows = 0;
|
|
2825
|
+
for (const nodes of byDepth.values()) maxRows = Math.max(maxRows, nodes.length);
|
|
2826
|
+
const down = direction === "vertical";
|
|
2827
|
+
const STEP_ACROSS = down ? COL + 24 : ROW;
|
|
2828
|
+
const pos = /* @__PURE__ */ new Map();
|
|
2829
|
+
for (const [d, nodes] of byDepth) {
|
|
2830
|
+
const offset = PAD + (maxRows * STEP_ACROSS - nodes.length * STEP_ACROSS) / 2;
|
|
2831
|
+
nodes.forEach(
|
|
2832
|
+
(node, i) => pos.set(
|
|
2833
|
+
node.id,
|
|
2834
|
+
down ? { x: offset + i * STEP_ACROSS, y: PAD + d * (NH + 46) } : { x: PAD + d * COL, y: offset + i * STEP_ACROSS }
|
|
2835
|
+
)
|
|
2836
|
+
);
|
|
2837
|
+
}
|
|
2838
|
+
const curve = (from, to) => {
|
|
2839
|
+
if (down) {
|
|
2840
|
+
const x12 = from.x + NW / 2;
|
|
2841
|
+
const y12 = from.y + NH;
|
|
2842
|
+
const x22 = to.x + NW / 2;
|
|
2843
|
+
const y22 = to.y;
|
|
2844
|
+
const my = (y12 + y22) / 2;
|
|
2845
|
+
return `M ${x12} ${y12} C ${x12} ${my}, ${x22} ${my}, ${x22} ${y22}`;
|
|
2846
|
+
}
|
|
2847
|
+
const x1 = from.x + NW;
|
|
2848
|
+
const y1 = from.y + NH / 2;
|
|
2849
|
+
const x2 = to.x;
|
|
2850
|
+
const y2 = to.y + NH / 2;
|
|
2851
|
+
const mx = (x1 + x2) / 2;
|
|
2852
|
+
return `M ${x1} ${y1} C ${mx} ${y1}, ${mx} ${y2}, ${x2} ${y2}`;
|
|
2853
|
+
};
|
|
2854
|
+
const addEdge = (a, b, kind, d) => {
|
|
2855
|
+
const path = el("path", { class: kind, d });
|
|
2856
|
+
edgeLayer.appendChild(path);
|
|
2857
|
+
edgeEls.push({ a, b, el: path });
|
|
2858
|
+
};
|
|
2859
|
+
for (const node of visible.values()) {
|
|
2860
|
+
const to = pos.get(node.id);
|
|
2861
|
+
const owner = node.createdIn !== void 0 ? standIn(node.createdIn) : void 0;
|
|
2862
|
+
for (const dep of readsOf(node)) {
|
|
2863
|
+
const from = pos.get(dep);
|
|
2864
|
+
const own = dep === owner && !node.deps.has(dep);
|
|
2865
|
+
addEdge(dep, node.id, own ? "fx-oedge" : "fx-edge", curve(from, to));
|
|
2866
|
+
}
|
|
2867
|
+
for (const w of node.writes) {
|
|
2868
|
+
const target = pos.get(standIn(w));
|
|
2869
|
+
if (target && standIn(w) !== node.id) addEdge(node.id, standIn(w), "fx-wedge", curve(to, target));
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
for (const node of visible.values()) {
|
|
2873
|
+
const p = pos.get(node.id);
|
|
2874
|
+
const from = node.source?.external ? " n-external" : "";
|
|
2875
|
+
const g = el("g", { class: `fx-node ${KIND_CLASS[node.kind]}${from}`, transform: `translate(${p.x} ${p.y})`, "data-id": node.id });
|
|
2876
|
+
g.appendChild(el("rect", { class: "fx-box", width: NW, height: NH, rx: 12 }));
|
|
2877
|
+
const kind = el("text", { class: "fx-kind", x: 12, y: 20 });
|
|
2878
|
+
kind.textContent = KIND_GLYPH[node.kind];
|
|
2879
|
+
const label = el("text", { class: "fx-label", x: 12, y: 38 });
|
|
2880
|
+
label.setAttribute("clip-path", `url(#${labelClip})`);
|
|
2881
|
+
label.textContent = node.label;
|
|
2882
|
+
const value = el("text", { class: "fx-value", x: 8 + LABEL_W + 6, y: 38 });
|
|
2883
|
+
value.setAttribute("clip-path", `url(#${valueClip})`);
|
|
2884
|
+
value.textContent = fit(node.value);
|
|
2885
|
+
const full = node.detail ?? node.value;
|
|
2886
|
+
if (full) value.appendChild(el("title")).textContent = full;
|
|
2887
|
+
g.append(kind, label, value);
|
|
2888
|
+
if (node.lazy) {
|
|
2889
|
+
const chunk = el("text", { class: "fx-lazy", x: 12, y: 52 });
|
|
2890
|
+
chunk.textContent = "lazy";
|
|
2891
|
+
chunk.appendChild(el("title")).textContent = "loaded as its own chunk";
|
|
2892
|
+
g.appendChild(chunk);
|
|
2893
|
+
}
|
|
2894
|
+
const mark = pluginBadge(node);
|
|
2895
|
+
if (mark) {
|
|
2896
|
+
const badge = el("text", { class: "fx-plugin", x: NW - 12, y: 52, "text-anchor": "end" });
|
|
2897
|
+
badge.textContent = fit(mark);
|
|
2898
|
+
badge.appendChild(el("title")).textContent = mark;
|
|
2899
|
+
g.appendChild(badge);
|
|
2900
|
+
}
|
|
2901
|
+
const summary2 = el("title");
|
|
2902
|
+
summary2.textContent = `${node.kind} ${node.label}${node.lazy ? " (lazy)" : ""}${full ? ` = ${full}` : ""}${placeOf(node) ? `
|
|
2903
|
+
${placeOf(node).file ?? ""}:${placeOf(node).line ?? ""}:${placeOf(node).column ?? ""}` : ""}`;
|
|
2904
|
+
g.appendChild(summary2);
|
|
2905
|
+
const source = el("text", { class: "fx-source", x: 12, y: 52 });
|
|
2906
|
+
source.setAttribute("clip-path", `url(#${sourceClip})`);
|
|
2907
|
+
const sourceTitle = el("title");
|
|
2908
|
+
source.appendChild(sourceTitle);
|
|
2909
|
+
g.appendChild(source);
|
|
2910
|
+
const inside = hiddenCount.get(node.id) ?? 0;
|
|
2911
|
+
const owns = inside > 0 || [...graph.values()].some((n) => n.createdIn === node.id);
|
|
2912
|
+
if (owns) {
|
|
2913
|
+
const badge = el("text", { class: "fx-fold", x: NW - 12, y: 20, "text-anchor": "end" });
|
|
2914
|
+
badge.textContent = inside > 0 ? `▸ ${inside}` : "▾";
|
|
2915
|
+
badge.appendChild(el("title")).textContent = inside > 0 ? `show ${inside} inside` : "fold what is inside";
|
|
2916
|
+
badge.addEventListener("click", (event) => {
|
|
2917
|
+
event.stopPropagation();
|
|
2918
|
+
toggleFold(node.id);
|
|
2919
|
+
});
|
|
2920
|
+
g.appendChild(badge);
|
|
2921
|
+
}
|
|
2922
|
+
g.addEventListener("click", () => {
|
|
2923
|
+
if (dragging?.moved) return;
|
|
2924
|
+
selected = node.id;
|
|
2925
|
+
applySelected();
|
|
2926
|
+
options.onSelect?.(node);
|
|
2927
|
+
});
|
|
2928
|
+
g.addEventListener("dblclick", (event) => {
|
|
2929
|
+
if (!options.onReveal) return;
|
|
2930
|
+
event.stopPropagation();
|
|
2931
|
+
options.onReveal(node);
|
|
2932
|
+
});
|
|
2933
|
+
g.addEventListener("mouseenter", () => hover(graph, node.id));
|
|
2934
|
+
g.addEventListener("mouseleave", () => hover(graph, null));
|
|
2935
|
+
g.style.cursor = "pointer";
|
|
2936
|
+
nodeLayer.appendChild(g);
|
|
2937
|
+
nodeEls.set(node.id, { g, value, source, sourceTitle });
|
|
2938
|
+
}
|
|
2939
|
+
viewport.append(edgeLayer, nodeLayer);
|
|
2940
|
+
const width = down ? PAD * 2 + maxRows * STEP_ACROSS + NW : PAD * 2 + maxDepth * COL + NW;
|
|
2941
|
+
const height = down ? PAD * 2 + (maxDepth + 1) * (NH + 46) : PAD * 2 + maxRows * ROW;
|
|
2942
|
+
svg.setAttribute("viewBox", `0 0 ${Math.max(width, 400)} ${Math.max(height, 240)}`);
|
|
2943
|
+
applySelected();
|
|
2944
|
+
}
|
|
2945
|
+
let lastGraph = /* @__PURE__ */ new Map();
|
|
2946
|
+
let lastShown = /* @__PURE__ */ new Map();
|
|
2947
|
+
function foldedIn(graph) {
|
|
2948
|
+
const hasChildren = /* @__PURE__ */ new Set();
|
|
2949
|
+
for (const node of graph.values()) if (node.createdIn !== void 0) hasChildren.add(node.createdIn);
|
|
2950
|
+
const inStore = (id) => {
|
|
2951
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2952
|
+
let at3 = graph.get(id)?.createdIn;
|
|
2953
|
+
while (at3 !== void 0 && !seen.has(at3)) {
|
|
2954
|
+
seen.add(at3);
|
|
2955
|
+
const up = graph.get(at3);
|
|
2956
|
+
if (up?.kind === "store") return true;
|
|
2957
|
+
at3 = up?.createdIn;
|
|
2958
|
+
}
|
|
2959
|
+
return false;
|
|
2960
|
+
};
|
|
2961
|
+
const folded = /* @__PURE__ */ new Set();
|
|
2962
|
+
for (const node of graph.values()) {
|
|
2963
|
+
if (!hasChildren.has(node.id)) continue;
|
|
2964
|
+
const byDefault = (node.kind === "group" || foldStores && (node.kind === "store" || inStore(node.id))) && !opened2.has(node.id);
|
|
2965
|
+
if (byDefault || closed.has(node.id)) folded.add(node.id);
|
|
2966
|
+
}
|
|
2967
|
+
return folded;
|
|
2968
|
+
}
|
|
2969
|
+
function toggleFold(id) {
|
|
2970
|
+
const folded = foldedIn(grouped(lastGraph)).has(id);
|
|
2971
|
+
if (folded) {
|
|
2972
|
+
opened2.add(id);
|
|
2973
|
+
closed.delete(id);
|
|
2974
|
+
} else {
|
|
2975
|
+
closed.add(id);
|
|
2976
|
+
opened2.delete(id);
|
|
2977
|
+
}
|
|
2978
|
+
signature = "";
|
|
2979
|
+
scale = 1;
|
|
2980
|
+
panX = 0;
|
|
2981
|
+
panY = 0;
|
|
2982
|
+
applyView();
|
|
2983
|
+
return !folded;
|
|
2984
|
+
}
|
|
2985
|
+
return {
|
|
2986
|
+
// Date.now, matching what the reactor stamps on a node. See instrument.ts.
|
|
2987
|
+
update(graph, now = Date.now()) {
|
|
2988
|
+
lastGraph = graph;
|
|
2989
|
+
const shown = grouped(withEdges(onlyKinds(graph)));
|
|
2990
|
+
lastShown = shown;
|
|
2991
|
+
const folded = foldedIn(shown);
|
|
2992
|
+
let sig = `${[...folded].sort().join(",")}|${groupExternal}|${direction}|${kindKey()}|`;
|
|
2993
|
+
for (const n of shown.values())
|
|
2994
|
+
sig += `${n.id}:${n.kind}:${[...n.deps].sort().join(",")}>${[...n.writes].sort().join(",")}^${n.createdIn ?? ""};`;
|
|
2995
|
+
if (sig !== signature) {
|
|
2996
|
+
signature = sig;
|
|
2997
|
+
rebuild(shown, folded);
|
|
2998
|
+
if (focused) light(focused);
|
|
2999
|
+
}
|
|
3000
|
+
stageWave(shown, now);
|
|
3001
|
+
for (const node of shown.values()) {
|
|
3002
|
+
const eles = nodeEls.get(node.id);
|
|
3003
|
+
if (!eles) continue;
|
|
3004
|
+
const next = fit(node.value);
|
|
3005
|
+
if (eles.value.textContent !== next) eles.value.textContent = next;
|
|
3006
|
+
const place2 = placeOf(node);
|
|
3007
|
+
const where = place2 ? shortSource(place2) : "";
|
|
3008
|
+
if (eles.source.textContent !== where) {
|
|
3009
|
+
eles.source.textContent = where;
|
|
3010
|
+
eles.sourceTitle.textContent = place2 ? `${place2.file ? shortenPath(place2.file, options.root) : ""}:${place2.line ?? ""}:${place2.column ?? ""}` : "";
|
|
3011
|
+
}
|
|
3012
|
+
const age = now - (staged.get(node.id) ?? node.lastPulse);
|
|
3013
|
+
eles.g.style.setProperty("--pulse", (age >= 0 && age < FLASH ? 1 - age / FLASH : 0).toFixed(3));
|
|
3014
|
+
}
|
|
3015
|
+
for (const { el: el2 } of edgeEls) {
|
|
3016
|
+
const at3 = edgeStaged.get(el2);
|
|
3017
|
+
const age = at3 === void 0 ? Infinity : now - at3;
|
|
3018
|
+
el2.style.setProperty("--pulse", (age >= 0 && age < EDGE_FLASH ? 1 - age / EDGE_FLASH : 0).toFixed(3));
|
|
3019
|
+
}
|
|
3020
|
+
},
|
|
3021
|
+
clear() {
|
|
3022
|
+
signature = "";
|
|
3023
|
+
selected = null;
|
|
3024
|
+
focused = null;
|
|
3025
|
+
svg.classList.remove("fx-hovering");
|
|
3026
|
+
edgeLayer.replaceChildren();
|
|
3027
|
+
nodeLayer.replaceChildren();
|
|
3028
|
+
nodeEls.clear();
|
|
3029
|
+
edgeEls.length = 0;
|
|
3030
|
+
staged.clear();
|
|
3031
|
+
stagedFor.clear();
|
|
3032
|
+
edgeStaged.clear();
|
|
3033
|
+
options.onSelect?.(null);
|
|
3034
|
+
},
|
|
3035
|
+
select(id) {
|
|
3036
|
+
selected = id;
|
|
3037
|
+
applySelected();
|
|
3038
|
+
},
|
|
3039
|
+
zoomBy(factor) {
|
|
3040
|
+
const box = svg.viewBox?.baseVal;
|
|
3041
|
+
zoomAt(factor, { x: (box?.width ?? 400) / 2, y: (box?.height ?? 240) / 2 });
|
|
3042
|
+
},
|
|
3043
|
+
resetView() {
|
|
3044
|
+
scale = 1;
|
|
3045
|
+
panX = 0;
|
|
3046
|
+
panY = 0;
|
|
3047
|
+
applyView();
|
|
3048
|
+
},
|
|
3049
|
+
toggle: toggleFold,
|
|
3050
|
+
setGroupExternal(next) {
|
|
3051
|
+
if (groupExternal === next) return;
|
|
3052
|
+
groupExternal = next;
|
|
3053
|
+
signature = "";
|
|
3054
|
+
scale = 1;
|
|
3055
|
+
panX = 0;
|
|
3056
|
+
panY = 0;
|
|
3057
|
+
applyView();
|
|
3058
|
+
},
|
|
3059
|
+
get groupExternal() {
|
|
3060
|
+
return groupExternal;
|
|
3061
|
+
},
|
|
3062
|
+
setShowBindings(next) {
|
|
3063
|
+
if (showBindings === next) return;
|
|
3064
|
+
showBindings = next;
|
|
3065
|
+
signature = "";
|
|
3066
|
+
},
|
|
3067
|
+
get showBindings() {
|
|
3068
|
+
return showBindings;
|
|
3069
|
+
},
|
|
3070
|
+
setKinds(next) {
|
|
3071
|
+
const key = next ? [...next].sort().join("+") : "*";
|
|
3072
|
+
if (key === kindKey()) return;
|
|
3073
|
+
kinds = next ? new Set(next) : null;
|
|
3074
|
+
signature = "";
|
|
3075
|
+
},
|
|
3076
|
+
get kinds() {
|
|
3077
|
+
return kinds;
|
|
3078
|
+
},
|
|
3079
|
+
get shown() {
|
|
3080
|
+
return lastShown;
|
|
3081
|
+
},
|
|
3082
|
+
focus(ids) {
|
|
3083
|
+
focused = ids?.length ? new Set(ids.map(standIn)) : null;
|
|
3084
|
+
light(focused);
|
|
3085
|
+
},
|
|
3086
|
+
setDirection(next) {
|
|
3087
|
+
if (direction === next) return;
|
|
3088
|
+
direction = next;
|
|
3089
|
+
signature = "";
|
|
3090
|
+
scale = 1;
|
|
3091
|
+
panX = 0;
|
|
3092
|
+
panY = 0;
|
|
3093
|
+
applyView();
|
|
3094
|
+
},
|
|
3095
|
+
get direction() {
|
|
3096
|
+
return direction;
|
|
3097
|
+
}
|
|
3098
|
+
};
|
|
3099
|
+
}
|
|
3100
|
+
var DEVTOOLS_CSS = `
|
|
3101
|
+
/* An edge carries a pulse while the change is crossing it, so a propagation reads as one
|
|
3102
|
+
thing moving rather than everything lighting at once. */
|
|
3103
|
+
.fx-edge {
|
|
3104
|
+
fill: none;
|
|
3105
|
+
stroke: var(--fx-edge, #38415a);
|
|
3106
|
+
stroke-width: calc(1.6px + var(--pulse, 0) * 1.7px);
|
|
3107
|
+
opacity: calc(.75 + var(--pulse, 0) * .25);
|
|
3108
|
+
filter: drop-shadow(0 0 calc(var(--pulse, 0) * 5px) var(--fx-accent, #5eead4));
|
|
3109
|
+
}
|
|
3110
|
+
.fx-oedge { fill: none; stroke: var(--fx-own, #64748b); stroke-width: 1.2; stroke-dasharray: 1 4; stroke-linecap: round; opacity: .55; }
|
|
3111
|
+
.fx-wedge {
|
|
3112
|
+
fill: none;
|
|
3113
|
+
stroke: var(--fx-write, #fb7185);
|
|
3114
|
+
stroke-width: calc(1.4px + var(--pulse, 0) * 1.6px);
|
|
3115
|
+
stroke-dasharray: 3 4;
|
|
3116
|
+
opacity: calc(.65 + var(--pulse, 0) * .35);
|
|
3117
|
+
}
|
|
3118
|
+
.fx-node { color: var(--fx-accent, #5eead4); filter: drop-shadow(0 0 calc(var(--pulse,0) * 13px) var(--glow, #5eead4)); }
|
|
3119
|
+
${kindRules((k) => `.fx-node.n-${k}`, "--glow")}
|
|
3120
|
+
.fx-node.n-group { opacity: .8; }
|
|
3121
|
+
.fx-node.n-group .fx-box { stroke-dasharray: 4 3; }
|
|
3122
|
+
/* Neither is a node: dashed and dimmer, so the reactive shape reads first. */
|
|
3123
|
+
.fx-node.n-binding { opacity: .72; }
|
|
3124
|
+
.fx-node.n-setter { opacity: .72; }
|
|
3125
|
+
.fx-node.n-binding .fx-box, .fx-node.n-setter .fx-box { stroke-dasharray: 3 3; }
|
|
3126
|
+
.fx-node.n-external { opacity: .55; }
|
|
3127
|
+
.fx-node.n-external .fx-box { stroke-dasharray: 3 2; }
|
|
3128
|
+
.fx-box { fill: var(--fx-node-bg, #131722); stroke: var(--glow, #5eead4); stroke-width: 1.4; stroke-opacity: calc(.5 + var(--pulse,0) * .5); }
|
|
3129
|
+
.fx-node.selected .fx-box { fill: var(--fx-node-bg-selected, #1a2230); stroke-width: 2.4; }
|
|
3130
|
+
.fx-kind { fill: var(--glow, #5eead4); font-family: ui-monospace, monospace; font-size: 9.5px; opacity: .85; }
|
|
3131
|
+
.fx-label { fill: var(--fx-label, #e7e9ee); font-family: ui-sans-serif, system-ui, sans-serif; font-size: 13px; font-weight: 600; }
|
|
3132
|
+
.fx-value { fill: var(--fx-muted, #8a90a2); font-family: ui-monospace, monospace; font-size: 12px; }
|
|
3133
|
+
.fx-source { fill: var(--fx-muted, #8a90a2); font-family: ui-monospace, monospace; font-size: 9.5px; opacity: .75; }
|
|
3134
|
+
.fx-plugin { fill: var(--fx-accent, #5eead4); font-family: ui-monospace, monospace; font-size: 9px; opacity: .9; }
|
|
3135
|
+
.fx-lazy { fill: var(--fx-router, #7dd3fc); font-family: ui-monospace, monospace; font-size: 9px; letter-spacing: .04em; opacity: .9; }
|
|
3136
|
+
.fx-fold { fill: var(--glow, #5eead4); font-family: ui-monospace, monospace; font-size: 10px; cursor: pointer; opacity: .8; }
|
|
3137
|
+
.fx-fold:hover { opacity: 1; }
|
|
3138
|
+
.fx-node.n-effect .fx-value { fill: var(--effect, #fbbf24); }
|
|
3139
|
+
.fx-node.n-resource .fx-value { fill: var(--fx-write, #fb7185); }
|
|
3140
|
+
/* Hovering one node is a question about that node: everything it cannot reach steps back. */
|
|
3141
|
+
.fx-hovering .fx-node { opacity: .2; transition: opacity .12s ease; }
|
|
3142
|
+
.fx-hovering .fx-node.hot { opacity: 1; }
|
|
3143
|
+
.fx-hovering .fx-node.n-external.hot { opacity: .7; }
|
|
3144
|
+
.fx-hovering .fx-edge, .fx-hovering .fx-oedge, .fx-hovering .fx-wedge { opacity: .08; }
|
|
3145
|
+
.fx-hovering .fx-edge.hot { opacity: 1; stroke: var(--fx-accent, #5eead4); }
|
|
3146
|
+
.fx-hovering .fx-oedge.hot { opacity: .8; }
|
|
3147
|
+
.fx-hovering .fx-wedge.hot { opacity: .9; }
|
|
3148
|
+
`;
|
|
3149
|
+
|
|
3150
|
+
// src/float.ts
|
|
3151
|
+
var MIN_W = 240;
|
|
3152
|
+
var MIN_H = 160;
|
|
3153
|
+
function draggable(handle, onMove, onStart) {
|
|
3154
|
+
handle.addEventListener("pointerdown", (event) => {
|
|
3155
|
+
if (event.button !== 0) return;
|
|
3156
|
+
if (event.target.closest("button")) return;
|
|
3157
|
+
event.preventDefault();
|
|
3158
|
+
handle.setPointerCapture(event.pointerId);
|
|
3159
|
+
const fromX = event.clientX;
|
|
3160
|
+
const fromY = event.clientY;
|
|
3161
|
+
onStart?.();
|
|
3162
|
+
const move = (e) => onMove(e.clientX - fromX, e.clientY - fromY);
|
|
3163
|
+
const stop = () => {
|
|
3164
|
+
handle.removeEventListener("pointermove", move);
|
|
3165
|
+
handle.removeEventListener("pointerup", stop);
|
|
3166
|
+
handle.removeEventListener("pointercancel", stop);
|
|
3167
|
+
handle.removeEventListener("lostpointercapture", stop);
|
|
3168
|
+
if (handle.hasPointerCapture(event.pointerId)) handle.releasePointerCapture(event.pointerId);
|
|
3169
|
+
};
|
|
3170
|
+
handle.addEventListener("pointermove", move);
|
|
3171
|
+
handle.addEventListener("pointerup", stop);
|
|
3172
|
+
handle.addEventListener("pointercancel", stop);
|
|
3173
|
+
handle.addEventListener("lostpointercapture", stop);
|
|
3174
|
+
});
|
|
3175
|
+
}
|
|
3176
|
+
var opened = 0;
|
|
3177
|
+
function floatCard(doc, title, at3) {
|
|
3178
|
+
const card = doc.createElement("div");
|
|
3179
|
+
card.className = "fx-float";
|
|
3180
|
+
const spot = at3 ?? {
|
|
3181
|
+
x: 60 + opened % 4 * 28,
|
|
3182
|
+
y: 60 + opened % 4 * 28,
|
|
3183
|
+
width: 360,
|
|
3184
|
+
height: 280
|
|
3185
|
+
};
|
|
3186
|
+
opened += 1;
|
|
3187
|
+
const bar = doc.createElement("div");
|
|
3188
|
+
bar.className = "fx-float-bar";
|
|
3189
|
+
const name = doc.createElement("span");
|
|
3190
|
+
name.textContent = title;
|
|
3191
|
+
bar.append(name);
|
|
3192
|
+
card.append(bar);
|
|
3193
|
+
let x = spot.x;
|
|
3194
|
+
let y = spot.y;
|
|
3195
|
+
let w = spot.width;
|
|
3196
|
+
let h = spot.height;
|
|
3197
|
+
const apply = () => {
|
|
3198
|
+
const view = doc.defaultView;
|
|
3199
|
+
const vw = view?.innerWidth ?? w;
|
|
3200
|
+
const vh = view?.innerHeight ?? h;
|
|
3201
|
+
w = Math.min(Math.max(MIN_W, w), vw);
|
|
3202
|
+
h = Math.min(Math.max(MIN_H, h), vh);
|
|
3203
|
+
x = Math.min(Math.max(0, x), Math.max(0, vw - w));
|
|
3204
|
+
y = Math.min(Math.max(0, y), Math.max(0, vh - h));
|
|
3205
|
+
card.style.left = `${x}px`;
|
|
3206
|
+
card.style.top = `${y}px`;
|
|
3207
|
+
card.style.width = `${w}px`;
|
|
3208
|
+
card.style.height = `${h}px`;
|
|
3209
|
+
};
|
|
3210
|
+
let from = { x, y, w, h };
|
|
3211
|
+
const remember = () => {
|
|
3212
|
+
from = { x, y, w, h };
|
|
3213
|
+
};
|
|
3214
|
+
for (const edge of ["n", "s", "e", "w", "ne", "nw", "se", "sw"]) {
|
|
3215
|
+
const handle = doc.createElement("div");
|
|
3216
|
+
handle.className = `fx-float-h fx-float-${edge}`;
|
|
3217
|
+
card.append(handle);
|
|
3218
|
+
draggable(
|
|
3219
|
+
handle,
|
|
3220
|
+
(dx, dy) => {
|
|
3221
|
+
if (edge.includes("e")) w = from.w + dx;
|
|
3222
|
+
if (edge.includes("s")) h = from.h + dy;
|
|
3223
|
+
if (edge.includes("w")) {
|
|
3224
|
+
w = Math.max(MIN_W, from.w - dx);
|
|
3225
|
+
x = from.x + from.w - w;
|
|
3226
|
+
}
|
|
3227
|
+
if (edge.includes("n")) {
|
|
3228
|
+
h = Math.max(MIN_H, from.h - dy);
|
|
3229
|
+
y = from.y + from.h - h;
|
|
3230
|
+
}
|
|
3231
|
+
apply();
|
|
3232
|
+
},
|
|
3233
|
+
remember
|
|
3234
|
+
);
|
|
3235
|
+
}
|
|
3236
|
+
draggable(
|
|
3237
|
+
bar,
|
|
3238
|
+
(dx, dy) => {
|
|
3239
|
+
x = from.x + dx;
|
|
3240
|
+
y = from.y + dy;
|
|
3241
|
+
apply();
|
|
3242
|
+
},
|
|
3243
|
+
remember
|
|
3244
|
+
);
|
|
3245
|
+
apply();
|
|
3246
|
+
return { card, bar, remove: () => card.remove() };
|
|
3247
|
+
}
|
|
3248
|
+
function makeFloatable(pane, options) {
|
|
3249
|
+
const doc = pane.ownerDocument;
|
|
3250
|
+
let card = null;
|
|
3251
|
+
let home = null;
|
|
3252
|
+
const button = doc.createElement("button");
|
|
3253
|
+
button.type = "button";
|
|
3254
|
+
button.className = "fx-float-btn";
|
|
3255
|
+
button.textContent = "⇱";
|
|
3256
|
+
button.title = `Float ${options.title}`;
|
|
3257
|
+
pane.classList.add("fx-floatable");
|
|
3258
|
+
pane.append(button);
|
|
3259
|
+
const keep2 = new MutationObserver(() => {
|
|
3260
|
+
if (button.parentNode !== pane) pane.append(button);
|
|
3261
|
+
});
|
|
3262
|
+
keep2.observe(pane, { childList: true });
|
|
3263
|
+
function dock() {
|
|
3264
|
+
if (!card || !home) return;
|
|
3265
|
+
home.parent.insertBefore(pane, home.before);
|
|
3266
|
+
card.remove();
|
|
3267
|
+
card = null;
|
|
3268
|
+
home = null;
|
|
3269
|
+
pane.classList.remove("fx-floated");
|
|
3270
|
+
button.textContent = "⇱";
|
|
3271
|
+
button.title = `Float ${options.title}`;
|
|
3272
|
+
options.onChange?.(false);
|
|
3273
|
+
}
|
|
3274
|
+
function float() {
|
|
3275
|
+
if (card || !pane.parentNode) return;
|
|
3276
|
+
home = { parent: pane.parentNode, before: pane.nextSibling };
|
|
3277
|
+
const built = floatCard(doc, options.title, options.at);
|
|
3278
|
+
card = built.card;
|
|
3279
|
+
card.append(pane);
|
|
3280
|
+
doc.body.appendChild(card);
|
|
3281
|
+
pane.classList.add("fx-floated");
|
|
3282
|
+
button.textContent = "⇲";
|
|
3283
|
+
button.title = `Put ${options.title} back`;
|
|
3284
|
+
options.onChange?.(true);
|
|
3285
|
+
}
|
|
3286
|
+
button.addEventListener("click", (event) => {
|
|
3287
|
+
event.stopPropagation();
|
|
3288
|
+
if (card) dock();
|
|
3289
|
+
else float();
|
|
3290
|
+
});
|
|
3291
|
+
return {
|
|
3292
|
+
get floating() {
|
|
3293
|
+
return card !== null;
|
|
3294
|
+
},
|
|
3295
|
+
toggle() {
|
|
3296
|
+
if (card) dock();
|
|
3297
|
+
else float();
|
|
3298
|
+
return card !== null;
|
|
3299
|
+
},
|
|
3300
|
+
dock,
|
|
3301
|
+
destroy() {
|
|
3302
|
+
keep2.disconnect();
|
|
3303
|
+
dock();
|
|
3304
|
+
button.remove();
|
|
3305
|
+
pane.classList.remove("fx-floatable");
|
|
3306
|
+
}
|
|
3307
|
+
};
|
|
3308
|
+
}
|
|
3309
|
+
var FLOAT_CSS = `
|
|
3310
|
+
.fx-floatable { position: relative; }
|
|
3311
|
+
.fx-float-btn {
|
|
3312
|
+
position: absolute; top: 2px; right: 2px; z-index: 5;
|
|
3313
|
+
width: 18px; height: 18px; padding: 0; line-height: 1;
|
|
3314
|
+
font-size: 11px; color: var(--fx-muted, #8a90a2);
|
|
3315
|
+
background: var(--fx-panel, #14161c);
|
|
3316
|
+
border: 1px solid var(--fx-line, #262a35); border-radius: 4px;
|
|
3317
|
+
cursor: pointer; opacity: 0; transition: opacity .12s;
|
|
3318
|
+
}
|
|
3319
|
+
.fx-floatable:hover .fx-float-btn, .fx-float-btn:focus-visible { opacity: 1; }
|
|
3320
|
+
.fx-float-btn:hover { color: var(--fx-accent, #5eead4); border-color: var(--fx-accent, #5eead4); }
|
|
3321
|
+
|
|
3322
|
+
.fx-float {
|
|
3323
|
+
position: fixed; z-index: 60; display: flex; flex-direction: column;
|
|
3324
|
+
background: var(--fx-panel, #14161c);
|
|
3325
|
+
border: 1px solid var(--fx-line, #262a35); border-radius: 8px;
|
|
3326
|
+
box-shadow: 0 12px 34px rgba(0, 0, 0, .5); overflow: hidden;
|
|
3327
|
+
}
|
|
3328
|
+
.fx-float-bar {
|
|
3329
|
+
display: flex; align-items: center; padding: 4px 8px; cursor: move;
|
|
3330
|
+
font-size: 11px; color: var(--fx-muted, #8a90a2);
|
|
3331
|
+
background: var(--fx-bg, #0f1115); border-bottom: 1px solid var(--fx-line, #262a35);
|
|
3332
|
+
user-select: none;
|
|
3333
|
+
}
|
|
3334
|
+
/* The pane fills the card, whatever it was sized to in the layout it came from. */
|
|
3335
|
+
.fx-float > .fx-floated {
|
|
3336
|
+
flex: 1; min-height: 0; width: auto; height: auto; overflow: auto;
|
|
3337
|
+
}
|
|
3338
|
+
/* Edges and corners. Thin, and over everything, so a card can be resized from whichever
|
|
3339
|
+
side is reachable. */
|
|
3340
|
+
.fx-float-h { position: absolute; z-index: 1; }
|
|
3341
|
+
.fx-float-n, .fx-float-s { left: 8px; right: 8px; height: 6px; cursor: ns-resize; }
|
|
3342
|
+
.fx-float-e, .fx-float-w { top: 8px; bottom: 8px; width: 6px; cursor: ew-resize; }
|
|
3343
|
+
.fx-float-n { top: -3px; }
|
|
3344
|
+
.fx-float-s { bottom: -3px; }
|
|
3345
|
+
.fx-float-e { right: -3px; }
|
|
3346
|
+
.fx-float-w { left: -3px; }
|
|
3347
|
+
.fx-float-ne, .fx-float-nw, .fx-float-se, .fx-float-sw { width: 14px; height: 14px; }
|
|
3348
|
+
.fx-float-nw { top: -3px; left: -3px; cursor: nwse-resize; }
|
|
3349
|
+
.fx-float-ne { top: -3px; right: -3px; cursor: nesw-resize; }
|
|
3350
|
+
.fx-float-sw { bottom: -3px; left: -3px; cursor: nesw-resize; }
|
|
3351
|
+
.fx-float-se {
|
|
3352
|
+
bottom: 0; right: 0; cursor: nwse-resize;
|
|
3353
|
+
background: linear-gradient(135deg, transparent 50%, var(--fx-line, #262a35) 50%);
|
|
3354
|
+
}
|
|
3355
|
+
`;
|
|
3356
|
+
|
|
3357
|
+
// src/value-pane.ts
|
|
3358
|
+
var escape2 = (text) => text.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
3359
|
+
function createValuePane(host2) {
|
|
3360
|
+
const doc = host2.ownerDocument;
|
|
3361
|
+
let built = null;
|
|
3362
|
+
const close = () => {
|
|
3363
|
+
built?.remove();
|
|
3364
|
+
built = null;
|
|
3365
|
+
};
|
|
3366
|
+
const onKey = (event) => {
|
|
3367
|
+
if (event.key === "Escape") close();
|
|
3368
|
+
};
|
|
3369
|
+
doc.addEventListener("keydown", onKey);
|
|
3370
|
+
return {
|
|
3371
|
+
open(title, text) {
|
|
3372
|
+
close();
|
|
3373
|
+
built = floatCard(doc, title, { x: 80, y: 80, width: 420, height: 320 });
|
|
3374
|
+
const shut = doc.createElement("button");
|
|
3375
|
+
shut.type = "button";
|
|
3376
|
+
shut.className = "vp-close";
|
|
3377
|
+
shut.textContent = "✕";
|
|
3378
|
+
shut.title = "Close";
|
|
3379
|
+
shut.addEventListener("click", close);
|
|
3380
|
+
built.bar.append(shut);
|
|
3381
|
+
const body = doc.createElement("pre");
|
|
3382
|
+
body.className = "vp-body";
|
|
3383
|
+
body.innerHTML = escape2(indentValue(text));
|
|
3384
|
+
built.card.append(body);
|
|
3385
|
+
doc.body.appendChild(built.card);
|
|
3386
|
+
},
|
|
3387
|
+
close,
|
|
3388
|
+
get isOpen() {
|
|
3389
|
+
return built !== null;
|
|
3390
|
+
},
|
|
3391
|
+
destroy() {
|
|
3392
|
+
doc.removeEventListener("keydown", onKey);
|
|
3393
|
+
close();
|
|
3394
|
+
}
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3397
|
+
var VALUE_PANE_CSS = `
|
|
3398
|
+
.vp-close {
|
|
3399
|
+
margin-left: auto; background: none; border: 0; cursor: pointer;
|
|
3400
|
+
color: var(--fx-muted, #8a90a2); font-size: 12px; line-height: 1; padding: 2px 4px;
|
|
3401
|
+
}
|
|
3402
|
+
.vp-close:hover { color: var(--fx-label, #e7e9ee); }
|
|
3403
|
+
.vp-body {
|
|
3404
|
+
flex: 1; margin: 0; padding: 8px; overflow: auto; white-space: pre;
|
|
3405
|
+
font-family: ui-monospace, monospace; font-size: 11.5px; line-height: 1.5;
|
|
3406
|
+
color: var(--fx-label, #e7e9ee);
|
|
3407
|
+
}
|
|
3408
|
+
`;
|
|
3409
|
+
|
|
3410
|
+
// src/inspector.ts
|
|
3411
|
+
var escape3 = (text) => text.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
3412
|
+
var clip = (text, n = 140) => text.length > n ? `${text.slice(0, n - 1)}…` : text;
|
|
3413
|
+
var place = (text, source, live) => {
|
|
3414
|
+
if (!source || !live || !source.file) return escape3(text);
|
|
3415
|
+
return `<button type="button" class="ins-at" data-file="${escape3(source.file)}" data-line="${source.line ?? ""}" data-column="${source.column ?? ""}" title="Open ${escape3(source.file)}">${escape3(text)}</button>`;
|
|
3416
|
+
};
|
|
3417
|
+
var at = (source, root) => source ? `${source.file ? shortenPath(source.file, root) : ""}:${source.line ?? ""}:${source.column ?? ""}` : "";
|
|
3418
|
+
var bothAt = (source, root) => {
|
|
3419
|
+
if (!source) return "";
|
|
3420
|
+
const out = source.compiled;
|
|
3421
|
+
const same2 = out?.line === source.line && out?.column === source.column;
|
|
3422
|
+
return out && !same2 ? `${at(source, root)} · out ${out.line ?? ""}:${out.column ?? ""}` : at(source, root);
|
|
3423
|
+
};
|
|
3424
|
+
function authoredPlace(node) {
|
|
3425
|
+
for (const place2 of [node.source, node.declaredAt, node.usedAt]) {
|
|
3426
|
+
if (place2 && !place2.external) return place2;
|
|
3427
|
+
}
|
|
3428
|
+
return node.declaredAt ?? node.source ?? node.usedAt;
|
|
3429
|
+
}
|
|
3430
|
+
function reaches(graph, id) {
|
|
3431
|
+
const count = /* @__PURE__ */ new Map();
|
|
3432
|
+
const writesHere = (nodes) => nodes.some((n) => n.writes?.includes(id) || writesHere(n.children));
|
|
3433
|
+
for (const node of graph.values()) {
|
|
3434
|
+
if (node.id === id || !node.dom?.length) continue;
|
|
3435
|
+
if (writesHere(node.dom)) count.set(node.label, (count.get(node.label) ?? 0) + 1);
|
|
3436
|
+
}
|
|
3437
|
+
return [...count].map(([label, n]) => n > 1 ? `${label} ×${n}` : label);
|
|
3438
|
+
}
|
|
3439
|
+
function valueOf(node, limit) {
|
|
3440
|
+
if (node.read) {
|
|
3441
|
+
try {
|
|
3442
|
+
return formatValue(node.read(), limit);
|
|
3443
|
+
} catch {
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
return node.detail ?? node.value ?? "";
|
|
3447
|
+
}
|
|
3448
|
+
function createInspector(host2, options = {}) {
|
|
3449
|
+
const empty = options.empty ?? "click a node to inspect";
|
|
3450
|
+
const limit = options.limit ?? 600;
|
|
3451
|
+
const reveals = typeof options.onReveal === "function";
|
|
3452
|
+
const root = options.root;
|
|
3453
|
+
const edits = typeof options.onEdit === "function";
|
|
3454
|
+
let editing = null;
|
|
3455
|
+
let field = null;
|
|
3456
|
+
let internals = options.internals === true;
|
|
3457
|
+
let pane = null;
|
|
3458
|
+
const doc = host2.ownerDocument;
|
|
3459
|
+
host2.addEventListener("click", (event) => {
|
|
3460
|
+
const cell = event.target?.closest?.("[data-full]");
|
|
3461
|
+
if (!cell) return;
|
|
3462
|
+
pane ??= createValuePane(host2);
|
|
3463
|
+
pane.open(cell.dataset.label || "value", cell.dataset.full || "");
|
|
3464
|
+
});
|
|
3465
|
+
const header = doc.createElement("div");
|
|
3466
|
+
header.className = "ins-header";
|
|
3467
|
+
const bar = doc.createElement("label");
|
|
3468
|
+
bar.className = "ins-bar";
|
|
3469
|
+
const box = doc.createElement("input");
|
|
3470
|
+
box.type = "checkbox";
|
|
3471
|
+
box.checked = internals;
|
|
3472
|
+
bar.append(box, doc.createTextNode("framework"));
|
|
3473
|
+
bar.title = "Also show where the framework created this, not only where it was written";
|
|
3474
|
+
const body = doc.createElement("div");
|
|
3475
|
+
body.className = "ins-body";
|
|
3476
|
+
header.appendChild(bar);
|
|
3477
|
+
host2.replaceChildren(header, body);
|
|
3478
|
+
box.addEventListener("change", () => {
|
|
3479
|
+
internals = box.checked;
|
|
3480
|
+
signature = "";
|
|
3481
|
+
});
|
|
3482
|
+
if (edits) {
|
|
3483
|
+
host2.addEventListener("click", (event) => {
|
|
3484
|
+
const cell = event.target?.closest?.(".ins-edit");
|
|
3485
|
+
if (!cell || editing !== null) return;
|
|
3486
|
+
const id = Number(cell.dataset.id);
|
|
3487
|
+
editing = id;
|
|
3488
|
+
const input = host2.ownerDocument.createElement("input");
|
|
3489
|
+
field = input;
|
|
3490
|
+
input.className = "ins-input";
|
|
3491
|
+
input.value = cell.dataset.raw ?? "";
|
|
3492
|
+
cell.replaceWith(input);
|
|
3493
|
+
input.focus();
|
|
3494
|
+
input.select();
|
|
3495
|
+
const done = (commit) => {
|
|
3496
|
+
if (editing === null) return;
|
|
3497
|
+
editing = null;
|
|
3498
|
+
field = null;
|
|
3499
|
+
signature = "";
|
|
3500
|
+
if (commit) options.onEdit(id, input.value);
|
|
3501
|
+
};
|
|
3502
|
+
input.addEventListener("keydown", (e) => {
|
|
3503
|
+
if (e.key === "Enter") done(true);
|
|
3504
|
+
else if (e.key === "Escape") done(false);
|
|
3505
|
+
});
|
|
3506
|
+
input.addEventListener("blur", () => done(true));
|
|
3507
|
+
});
|
|
3508
|
+
}
|
|
3509
|
+
if (reveals) {
|
|
3510
|
+
host2.addEventListener("click", (event) => {
|
|
3511
|
+
const button = event.target?.closest?.(".ins-at");
|
|
3512
|
+
if (!button) return;
|
|
3513
|
+
const line = Number(button.dataset.line);
|
|
3514
|
+
const column = Number(button.dataset.column);
|
|
3515
|
+
options.onReveal({
|
|
3516
|
+
file: button.dataset.file,
|
|
3517
|
+
...Number.isFinite(line) && button.dataset.line ? { line } : {},
|
|
3518
|
+
...Number.isFinite(column) && button.dataset.column ? { column } : {}
|
|
3519
|
+
});
|
|
3520
|
+
});
|
|
3521
|
+
}
|
|
3522
|
+
let signature = "";
|
|
3523
|
+
return {
|
|
3524
|
+
show(graph, id) {
|
|
3525
|
+
const node = id == null ? void 0 : graph.get(id);
|
|
3526
|
+
if (!node) {
|
|
3527
|
+
if (signature === "empty") return;
|
|
3528
|
+
signature = "empty";
|
|
3529
|
+
body.innerHTML = `<span class="ins-empty">${escape3(empty)}</span>`;
|
|
3530
|
+
return;
|
|
3531
|
+
}
|
|
3532
|
+
const labelOf = (other) => graph.get(other)?.label ?? "?";
|
|
3533
|
+
const reads = [...node.deps].map(labelOf);
|
|
3534
|
+
const feeds = [...graph.values()].filter((n) => n.deps.has(node.id) && n.kind !== "binding").map((n) => n.label);
|
|
3535
|
+
const writes = [...node.writes].map(labelOf);
|
|
3536
|
+
const value = valueOf(node, limit);
|
|
3537
|
+
const state = nodeState(node);
|
|
3538
|
+
const synthetic = node.kind === "binding" || node.kind === "setter" || node.kind === "group";
|
|
3539
|
+
const owner = node.createdIn !== void 0 ? graph.get(node.createdIn)?.label : void 0;
|
|
3540
|
+
const reaching = reaches(graph, node.id);
|
|
3541
|
+
const made = [...graph.values()].filter((n) => n.createdIn === node.id).map((n) => n.label);
|
|
3542
|
+
const bound = node.targets?.length ? node.targets.map((t) => {
|
|
3543
|
+
const where = bothAt(t.source, root);
|
|
3544
|
+
const what = escape3(t.name ? `${t.name} of ${t.element}` : t.element);
|
|
3545
|
+
return where ? `${what} (${place(where, t.source, reveals)})` : what;
|
|
3546
|
+
}).join(", ") : node.bindings ? `${node.bindings} dom binding${node.bindings > 1 ? "s" : ""}` : "";
|
|
3547
|
+
const relations = [
|
|
3548
|
+
`<span>↤ reads ${reads.length ? `<b>${escape3(reads.join(", "))}</b>` : "<i>nothing</i>"}</span>`,
|
|
3549
|
+
// `bound` already carries markup for the places a value reaches, so only the
|
|
3550
|
+
// labels are escaped here.
|
|
3551
|
+
`<span>↦ feeds ${feeds.length || bound ? `<b>${[...feeds.map(escape3), bound].filter(Boolean).join(", ")}</b>` : "<i>nothing yet</i>"}</span>`,
|
|
3552
|
+
writes.length ? `<span>✎ writes <b>${escape3(writes.join(", "))}</b></span>` : "",
|
|
3553
|
+
// Where the value is set from code the graph has no node for.
|
|
3554
|
+
node.setAt?.length ? `<span>⌁ set at <b>${node.setAt.map((w) => place(bothAt(w, root), w, reveals)).join(", ")}</b></span>` : "",
|
|
3555
|
+
owner ? `<span>◧ made in <b>${escape3(owner)}</b></span>` : "",
|
|
3556
|
+
reaching.length ? `<span>◨ reaches <b>${escape3(reaching.join(", "))}</b></span>` : "",
|
|
3557
|
+
...pluginRows(node, graph).map((row) => {
|
|
3558
|
+
const where = row.at && { ...row.at, file: row.at.file ?? node.source?.file };
|
|
3559
|
+
const text = where?.file ? bothAt(where, root) : "";
|
|
3560
|
+
const more = row.value !== void 0 && row.value.length > 140;
|
|
3561
|
+
return `<span${more ? ` class="ins-more" data-full="${escape3(row.value)}" data-label="${escape3(row.label)}"` : ""} title="${escape3(row.value ?? row.label)}">⌘ ${escape3(row.label)}${row.value ? `=<b>${escape3(clip(row.value))}</b>` : ""}${text ? ` ${place(text, where, reveals)}` : ""}</span>`;
|
|
3562
|
+
}),
|
|
3563
|
+
// What the tag was written with. A prop's position has no file of its own: it was
|
|
3564
|
+
// written on the tag, so it belongs to whichever file that is.
|
|
3565
|
+
...(node.props ?? []).map((p) => {
|
|
3566
|
+
const where = p.at && { ...p.at, file: p.at.file ?? node.source?.file };
|
|
3567
|
+
const at3 = where?.file ? bothAt(where, root) : "";
|
|
3568
|
+
return `<span>⇥ ${escape3(p.name)}=<b>${escape3(clip(p.value ?? "", 60))}</b>${at3 ? ` ${place(at3, where, reveals)}` : ""}</span>`;
|
|
3569
|
+
}),
|
|
3570
|
+
made.length ? `<span>▸ made <b>${escape3(made.join(", "))}</b></span>` : ""
|
|
3571
|
+
].filter(Boolean).join("");
|
|
3572
|
+
const next = `${node.id}|${node.kind}|${value}|${node.runs}|${state ?? ""}|${relations}|${at(node.source, root)}|${at(node.usedAt, root)}|${bound}`;
|
|
3573
|
+
if (editing !== null && field?.isConnected !== true) {
|
|
3574
|
+
editing = null;
|
|
3575
|
+
field = null;
|
|
3576
|
+
}
|
|
3577
|
+
if (editing !== null) return;
|
|
3578
|
+
if (next === signature) return;
|
|
3579
|
+
signature = next;
|
|
3580
|
+
body.innerHTML = `<div class="ins-head"><span class="ins-kind n-${node.kind}">${node.kind}</span><b>${escape3(node.label)}</b>` + (synthetic ? '<span class="ins-meta">not a node</span></div>' : `<span class="ins-meta">v${nodeVersion(node)} · ${node.runs} runs${state ? ` · ${state}` : ""}</span></div>`) + (edits && node.kind === "state" ? `<div class="ins-val ins-edit" data-id="${node.id}" data-raw="${escape3(value)}" title="Click to set ${escape3(node.label)}">${escape3(clip(value))}</div>` : `<div class="ins-val${value.length > 140 ? " ins-more" : ""}"${value.length > 140 ? ` data-full="${escape3(value)}" data-label="${escape3(node.label)}"` : ""} title="${escape3(value)}">${escape3(clip(value))}</div>`) + `<div class="ins-rel">${relations}</div>` + // Only when the graph carries one. A snapshot from a build that was not marked has
|
|
3581
|
+
// no locations at all, and an empty line there reads as missing data.
|
|
3582
|
+
(node.source ? `<div class="ins-source">${node.source.external ? "📦" : "📍"} ${place(bothAt(node.source, root), node.source, reveals)}</div>` : "") + // A component is created where its tag is written; where it is declared is somewhere
|
|
3583
|
+
// else, and only worth a line when it is.
|
|
3584
|
+
(node.declaredAt && at(node.declaredAt, root) !== at(node.source, root) ? `<div class="ins-source">◧ declared ${place(at(node.declaredAt, root), node.declaredAt, reveals)}</div>` : "") + // A node a package made on the application's behalf: the package's line is where it
|
|
3585
|
+
// was created, and this is the line that asked for it.
|
|
3586
|
+
(node.usedAt ? `<div class="ins-source">↗ used at ${place(bothAt(node.usedAt, root), node.usedAt, reveals)}</div>` : "") + // Nothing said where it came from at all: the compiler did not mark this project and
|
|
3587
|
+
// the stack had nothing to offer either. Saying so beats a card that just sits there.
|
|
3588
|
+
(!node.source && !node.usedAt && !node.declaredAt ? '<div class="ins-source">— no source: build without sourceLocations</div>' : "");
|
|
3589
|
+
},
|
|
3590
|
+
showElement(graph, pick) {
|
|
3591
|
+
if (!pick) {
|
|
3592
|
+
if (signature === "empty") return;
|
|
3593
|
+
signature = "empty";
|
|
3594
|
+
body.innerHTML = `<span class="ins-empty">${escape3(empty)}</span>`;
|
|
3595
|
+
return;
|
|
3596
|
+
}
|
|
3597
|
+
const owner = pick.owner === void 0 ? void 0 : graph.get(pick.owner);
|
|
3598
|
+
const writers = (pick.writes ?? []).map((id) => graph.get(id)).filter((n) => n !== void 0);
|
|
3599
|
+
const bindingAt = (node) => node.targets?.find((t) => t.element === pick.label || t.element.endsWith(` in ${pick.label}`))?.source;
|
|
3600
|
+
const rows = writers.map((node) => {
|
|
3601
|
+
const where = bindingAt(node);
|
|
3602
|
+
const text = where ? bothAt(where, root) : "";
|
|
3603
|
+
return `<span>⌁ <b>${escape3(node.label)}</b>${text ? ` ${place(text, where, reveals)}` : ""}</span>`;
|
|
3604
|
+
}).join("");
|
|
3605
|
+
const relations = [
|
|
3606
|
+
owner ? `<span>◧ rendered by <b>${escape3(owner.label)}</b></span>` : "<span>◧ rendered by <i>nothing in the graph</i></span>",
|
|
3607
|
+
writers.length ? rows : "<span>⌁ <i>nothing writes here</i></span>",
|
|
3608
|
+
...pluginElementRows(pick.extra).map((row) => {
|
|
3609
|
+
const where = row.at && { ...row.at, file: row.at.file ?? owner?.source?.file };
|
|
3610
|
+
const text = where?.file ? bothAt(where, root) : "";
|
|
3611
|
+
const more = row.value !== void 0 && row.value.length > 140;
|
|
3612
|
+
return `<span${more ? ` class="ins-more" data-full="${escape3(row.value)}" data-label="${escape3(row.label)}"` : ""} title="${escape3(row.value ?? row.label)}">⌘ ${escape3(row.label)}${row.value ? `=<b>${escape3(clip(row.value))}</b>` : ""}${text ? ` ${place(text, where, reveals)}` : ""}</span>`;
|
|
3613
|
+
})
|
|
3614
|
+
].join("");
|
|
3615
|
+
const source = pick.at ?? (owner && (owner.declaredAt ?? authoredPlace(owner)));
|
|
3616
|
+
const madeIn = internals && owner?.source && owner.source !== source ? owner.source : void 0;
|
|
3617
|
+
const next = `el|${pick.key}|${pick.label}|${relations}|${at(source, root)}|${at(madeIn, root)}`;
|
|
3618
|
+
if (editing !== null && field?.isConnected !== true) {
|
|
3619
|
+
editing = null;
|
|
3620
|
+
field = null;
|
|
3621
|
+
}
|
|
3622
|
+
if (editing !== null) return;
|
|
3623
|
+
if (next === signature) return;
|
|
3624
|
+
signature = next;
|
|
3625
|
+
body.innerHTML = `<div class="ins-head"><span class="ins-kind n-element">element</span><b>${escape3(pick.label)}</b><span class="ins-meta">not a node</span></div><div class="ins-rel">${relations}</div>` + // The tag's own line. An element has no position of its own in the graph — only the
|
|
3626
|
+
// component that rendered it does — so this is as close as the source gets.
|
|
3627
|
+
(source ? `<div class="ins-source">${source.external ? "📦" : "📍"} ${place(bothAt(source, root), source, reveals)}</div>` : "") + (madeIn ? `<div class="ins-source">📦 made in ${place(bothAt(madeIn, root), madeIn, reveals)}</div>` : "");
|
|
3628
|
+
},
|
|
3629
|
+
clear() {
|
|
3630
|
+
signature = "";
|
|
3631
|
+
body.innerHTML = `<span class="ins-empty">${escape3(empty)}</span>`;
|
|
3632
|
+
}
|
|
3633
|
+
};
|
|
3634
|
+
}
|
|
3635
|
+
var INSPECTOR_CSS = `
|
|
3636
|
+
${VALUE_PANE_CSS}
|
|
3637
|
+
/* Anything cut to fit says so, and opens the whole of it. */
|
|
3638
|
+
.ins-more { cursor: zoom-in; }
|
|
3639
|
+
.ins-header{ display: flex; align-items: center; gap: 8px; padding: .3rem .5rem; }
|
|
3640
|
+
.ins-body { padding: .5rem; }
|
|
3641
|
+
.ins-more:hover { text-decoration: underline dotted; }
|
|
3642
|
+
.ins-empty { color: var(--fx-muted, #8a90a2); font-size: 12px; }
|
|
3643
|
+
.ins-bar {
|
|
3644
|
+
display: flex; align-items: center; gap: 5px; margin-bottom: 6px;
|
|
3645
|
+
font-size: 10.5px; color: var(--fx-muted, #8a90a2); cursor: pointer; user-select: none;
|
|
3646
|
+
}
|
|
3647
|
+
.ins-bar:hover { color: var(--fx-label, #e7e9ee); }
|
|
3648
|
+
.ins-bar input { width: 11px; height: 11px; margin: 0; accent-color: var(--fx-accent, #5eead4); }
|
|
3649
|
+
.ins-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
|
|
3650
|
+
.ins-head b { font-size: 13px; color: var(--fx-label, #e7e9ee); }
|
|
3651
|
+
.ins-kind { font-family: ui-monospace, monospace; font-size: 9.5px; text-transform: uppercase; letter-spacing: 1px; }
|
|
3652
|
+
${kindRules((k) => `.ins-kind.n-${k}`, "color")}
|
|
3653
|
+
.ins-meta { margin-left: auto; color: var(--fx-muted, #8a90a2); font-family: ui-monospace, monospace; font-size: 10.5px; }
|
|
3654
|
+
.ins-val { font-family: ui-monospace, monospace; font-size: 12px; color: var(--fx-label, #e7e9ee); word-break: break-word; }
|
|
3655
|
+
.ins-rel { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 6px; color: var(--fx-muted, #8a90a2); font-size: 11.5px; }
|
|
3656
|
+
.ins-rel b { color: var(--fx-label, #e7e9ee); font-weight: 600; }
|
|
3657
|
+
.ins-rel i { font-style: normal; opacity: .6; }
|
|
3658
|
+
.ins-edit { cursor: text; border-bottom: 1px dashed var(--fx-line, #262a35); }
|
|
3659
|
+
.ins-edit:hover { border-bottom-color: var(--fx-accent, #5eead4); }
|
|
3660
|
+
.ins-input {
|
|
3661
|
+
width: 100%; box-sizing: border-box; padding: 1px 4px;
|
|
3662
|
+
font: inherit; font-family: ui-monospace, monospace;
|
|
3663
|
+
color: var(--fx-label, #e7e9ee); background: var(--fx-bg, #0f1115);
|
|
3664
|
+
border: 1px solid var(--fx-accent, #5eead4); border-radius: 3px;
|
|
3665
|
+
}
|
|
3666
|
+
.ins-at {
|
|
3667
|
+
font: inherit; color: inherit; background: none; border: 0; padding: 0;
|
|
3668
|
+
cursor: pointer; text-decoration: underline; text-decoration-style: dotted;
|
|
3669
|
+
text-underline-offset: 2px;
|
|
3670
|
+
}
|
|
3671
|
+
.ins-at:hover { color: var(--fx-accent, #5eead4); }
|
|
3672
|
+
.ins-at:focus-visible { outline: 1px solid var(--fx-accent, #5eead4); outline-offset: 1px; }
|
|
3673
|
+
.ins-source { margin-top: 6px; font-family: ui-monospace, monospace; font-size: 10.5px; color: var(--fx-muted, #8a90a2); }
|
|
3674
|
+
`;
|
|
3675
|
+
|
|
3676
|
+
// src/copy.ts
|
|
3677
|
+
function confirmOn(button, said = "copied") {
|
|
3678
|
+
if (!button) return;
|
|
3679
|
+
const was = button.textContent;
|
|
3680
|
+
button.textContent = said;
|
|
3681
|
+
setTimeout(() => {
|
|
3682
|
+
button.textContent = was;
|
|
3683
|
+
}, 1200);
|
|
3684
|
+
}
|
|
3685
|
+
function fallback(text, button) {
|
|
3686
|
+
try {
|
|
3687
|
+
const doc = button?.ownerDocument ?? document;
|
|
3688
|
+
const area = doc.createElement("textarea");
|
|
3689
|
+
area.value = text;
|
|
3690
|
+
area.setAttribute("readonly", "");
|
|
3691
|
+
area.style.position = "fixed";
|
|
3692
|
+
area.style.opacity = "0";
|
|
3693
|
+
doc.body.appendChild(area);
|
|
3694
|
+
area.select();
|
|
3695
|
+
doc.execCommand("copy");
|
|
3696
|
+
area.remove();
|
|
3697
|
+
confirmOn(button);
|
|
3698
|
+
return true;
|
|
3699
|
+
} catch {
|
|
3700
|
+
return false;
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
async function copyText(text, button) {
|
|
3704
|
+
const clipboard = globalThis.navigator?.clipboard;
|
|
3705
|
+
if (clipboard?.writeText) {
|
|
3706
|
+
try {
|
|
3707
|
+
await clipboard.writeText(text);
|
|
3708
|
+
confirmOn(button);
|
|
3709
|
+
return true;
|
|
3710
|
+
} catch {
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
return fallback(text, button);
|
|
3714
|
+
}
|
|
3715
|
+
|
|
3716
|
+
// src/change-view.ts
|
|
3717
|
+
var escape4 = (text) => text.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
3718
|
+
var SIGN = { added: "+", removed: "−", changed: "~" };
|
|
3719
|
+
function pretty(json2) {
|
|
3720
|
+
if (!json2) return void 0;
|
|
3721
|
+
try {
|
|
3722
|
+
return JSON.stringify(JSON.parse(json2), null, 2);
|
|
3723
|
+
} catch {
|
|
3724
|
+
return void 0;
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
function sideOf(text, json2, cls) {
|
|
3728
|
+
const block = pretty(json2);
|
|
3729
|
+
if (!block) return `<span class="ch-${cls}">${escape4(text ?? "")}</span>`;
|
|
3730
|
+
return `<div class="ch-block ch-${cls}"><button type="button" class="ch-copy" title="Copy" data-copy="${escape4(block)}">copy</button><pre class="ch-json">${escape4(block)}</pre></div>`;
|
|
3731
|
+
}
|
|
3732
|
+
function changeRows(change2, limit = Infinity) {
|
|
3733
|
+
if (!change2?.entries?.length) return "";
|
|
3734
|
+
const shown = change2.entries.slice(0, limit);
|
|
3735
|
+
const hidden = change2.entries.length - shown.length + (change2.more ?? 0);
|
|
3736
|
+
const rows = shown.map((entry) => {
|
|
3737
|
+
const from = sideOf(entry.from, entry.fromJson, "from");
|
|
3738
|
+
const to = sideOf(entry.to, entry.toJson, "to");
|
|
3739
|
+
const sides = entry.op === "changed" ? `${from}<span class="ch-arrow">→</span>${to}` : entry.op === "added" ? to : from;
|
|
3740
|
+
const stacked = entry.fromJson || entry.toJson ? " ch-stacked" : "";
|
|
3741
|
+
return `<div class="ch-row ch-${entry.op}${stacked}"><span class="ch-sign">${SIGN[entry.op] ?? "·"}</span><span class="ch-key">${escape4(entry.key)}</span><span class="ch-sides">${sides}</span></div>`;
|
|
3742
|
+
}).join("");
|
|
3743
|
+
const size = change2.length ? `<div class="ch-size">${change2.length.from} → ${change2.length.to} items</div>` : "";
|
|
3744
|
+
return `<div class="ch-list">${size}${rows}${hidden > 0 ? `<div class="ch-more">and ${hidden} more</div>` : ""}</div>`;
|
|
3745
|
+
}
|
|
3746
|
+
function handleCopyClick(event) {
|
|
3747
|
+
const button = event.target?.closest?.(".ch-copy");
|
|
3748
|
+
if (!button) return false;
|
|
3749
|
+
void copyText(button.dataset.copy ?? "", button);
|
|
3750
|
+
return true;
|
|
3751
|
+
}
|
|
3752
|
+
var CHANGE_CSS = `
|
|
3753
|
+
.ch-list { margin-top: 4px; font-family: ui-monospace, monospace; font-size: 11px; }
|
|
3754
|
+
.ch-size { color: var(--fx-muted, #8a90a2); margin-bottom: 2px; }
|
|
3755
|
+
.ch-row { display: flex; gap: 6px; align-items: baseline; padding: 1px 0; }
|
|
3756
|
+
.ch-row.ch-stacked { align-items: flex-start; padding: 3px 0; }
|
|
3757
|
+
.ch-sign { flex: none; width: 9px; text-align: center; }
|
|
3758
|
+
.ch-added .ch-sign { color: var(--state, #34d399); }
|
|
3759
|
+
.ch-removed .ch-sign { color: var(--fx-write, #fb7185); }
|
|
3760
|
+
.ch-changed .ch-sign { color: var(--effect, #fbbf24); }
|
|
3761
|
+
.ch-key { flex: none; color: var(--fx-muted, #8a90a2); }
|
|
3762
|
+
.ch-sides { min-width: 0; flex: 1; overflow-wrap: anywhere; }
|
|
3763
|
+
.ch-stacked .ch-sides { display: flex; flex-direction: column; gap: 2px; }
|
|
3764
|
+
.ch-from { color: var(--fx-muted, #8a90a2); }
|
|
3765
|
+
.ch-row:not(.ch-stacked) .ch-from { text-decoration: line-through; }
|
|
3766
|
+
.ch-arrow { color: var(--fx-muted, #8a90a2); margin: 0 4px; }
|
|
3767
|
+
.ch-to { color: var(--fx-label, #e7e9ee); }
|
|
3768
|
+
.ch-more { color: var(--fx-muted, #8a90a2); margin-top: 2px; }
|
|
3769
|
+
/* In the corner: an object can be tall, and a button under it is a scroll away. */
|
|
3770
|
+
.ch-block { position: relative; border-radius: 4px; border: 1px solid var(--fx-line, #ffffff14); }
|
|
3771
|
+
.ch-block.ch-from { opacity: .72; }
|
|
3772
|
+
.ch-json { margin: 0; padding: 5px 7px; overflow-x: auto; white-space: pre; font: inherit; }
|
|
3773
|
+
.ch-copy {
|
|
3774
|
+
position: absolute; top: 3px; right: 3px; padding: 1px 6px; cursor: pointer;
|
|
3775
|
+
font: inherit; font-size: 9.5px; opacity: 0; transition: opacity .12s;
|
|
3776
|
+
border: 1px solid var(--fx-line, #ffffff26); border-radius: 3px;
|
|
3777
|
+
background: var(--fx-panel, #14161c); color: var(--fx-muted, #8a90a2);
|
|
3778
|
+
}
|
|
3779
|
+
.ch-block:hover .ch-copy, .ch-copy:focus { opacity: 1; }
|
|
3780
|
+
.ch-copy:hover { color: var(--fx-label, #e7e9ee); }
|
|
3781
|
+
`;
|
|
3782
|
+
|
|
3783
|
+
// src/timeline.ts
|
|
3784
|
+
var escape5 = (text) => text.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
3785
|
+
var clock = (t) => {
|
|
3786
|
+
const d = new Date(t);
|
|
3787
|
+
const pad = (n, width = 2) => String(n).padStart(width, "0");
|
|
3788
|
+
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;
|
|
3789
|
+
};
|
|
3790
|
+
function subject(commit, nameOf) {
|
|
3791
|
+
if (!commit.cause) return commit.commit === 0 ? "first render" : "continued";
|
|
3792
|
+
return nameOf(commit.cause);
|
|
3793
|
+
}
|
|
3794
|
+
function change(commit) {
|
|
3795
|
+
const cause = commit.cause;
|
|
3796
|
+
if (!cause || cause.from === void 0 || cause.to === void 0) return "";
|
|
3797
|
+
return `${cause.from} → ${cause.to}`;
|
|
3798
|
+
}
|
|
3799
|
+
function detail(commit, nameOf) {
|
|
3800
|
+
const lines = [`${subject(commit, nameOf)} ${change(commit)}`.trim()];
|
|
3801
|
+
const named = (kind, verb) => {
|
|
3802
|
+
const ids = commit.events.filter((e) => e.kind === kind && e !== commit.cause).map(nameOf);
|
|
3803
|
+
if (ids.length) lines.push(`${verb}: ${[...new Set(ids)].join(", ")}`);
|
|
3804
|
+
};
|
|
3805
|
+
named("ran", "ran");
|
|
3806
|
+
named("write", "written");
|
|
3807
|
+
named("created", "made");
|
|
3808
|
+
named("disposed", "gone");
|
|
3809
|
+
return lines.join("\n");
|
|
3810
|
+
}
|
|
3811
|
+
function summary(commit) {
|
|
3812
|
+
const counts = { ran: 0, created: 0, disposed: 0, write: 0 };
|
|
3813
|
+
for (const event of commit.events) counts[event.kind]++;
|
|
3814
|
+
const writes = counts.write - (commit.cause ? 1 : 0);
|
|
3815
|
+
return [
|
|
3816
|
+
counts.ran && `${counts.ran} ran`,
|
|
3817
|
+
writes > 0 && `${writes} written`,
|
|
3818
|
+
counts.created && `${counts.created} made`,
|
|
3819
|
+
counts.disposed && `${counts.disposed} gone`
|
|
3820
|
+
].filter(Boolean).join(" · ");
|
|
3821
|
+
}
|
|
3822
|
+
function createTimeline(host2, options = {}) {
|
|
3823
|
+
const empty = options.empty ?? "nothing has happened yet";
|
|
3824
|
+
const limit = options.limit ?? 100;
|
|
3825
|
+
let events = [];
|
|
3826
|
+
let seen = -1;
|
|
3827
|
+
let selected = null;
|
|
3828
|
+
let filter = "";
|
|
3829
|
+
let signature = "";
|
|
3830
|
+
let paused = false;
|
|
3831
|
+
host2.innerHTML = '<div class="tl-head"><input class="tl-filter" type="search" placeholder="filter by name or value" aria-label="Filter the timeline" /><button type="button" class="tl-pause" title="Stop taking new updates, so the list holds still while you read it">❚❚</button></div><div class="tl-list"></div>';
|
|
3832
|
+
const input = host2.querySelector(".tl-filter");
|
|
3833
|
+
const list = host2.querySelector(".tl-list");
|
|
3834
|
+
const pause = host2.querySelector(".tl-pause");
|
|
3835
|
+
const all = () => toCommits(events).slice(-limit).reverse();
|
|
3836
|
+
const idsOf = (commit) => {
|
|
3837
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3838
|
+
for (const event of commit.events) {
|
|
3839
|
+
ids.add(event.id);
|
|
3840
|
+
if (event.by !== void 0) ids.add(event.by);
|
|
3841
|
+
}
|
|
3842
|
+
return [...ids];
|
|
3843
|
+
};
|
|
3844
|
+
const haystack = (commit, nameOf) => `${detail(commit, nameOf)} ${summary(commit)}`.toLowerCase();
|
|
3845
|
+
const shown = (nameOf) => filter ? all().filter((c) => haystack(c, nameOf).includes(filter)) : all();
|
|
3846
|
+
input.addEventListener("input", () => {
|
|
3847
|
+
filter = input.value.trim().toLowerCase();
|
|
3848
|
+
signature = "";
|
|
3849
|
+
});
|
|
3850
|
+
const showPaused = () => {
|
|
3851
|
+
pause.textContent = paused ? "▶" : "❚❚";
|
|
3852
|
+
pause.classList.toggle("on", paused);
|
|
3853
|
+
pause.title = paused ? "Taking updates again" : "Stop taking new updates, so the list holds still while you read it";
|
|
3854
|
+
};
|
|
3855
|
+
pause.addEventListener("click", () => {
|
|
3856
|
+
paused = !paused;
|
|
3857
|
+
showPaused();
|
|
3858
|
+
signature = "";
|
|
3859
|
+
});
|
|
3860
|
+
list.addEventListener("click", (e) => {
|
|
3861
|
+
if (handleCopyClick(e)) return;
|
|
3862
|
+
const row = e.target?.closest?.(".tl-commit");
|
|
3863
|
+
if (!row) return;
|
|
3864
|
+
const number = Number(row.dataset.commit);
|
|
3865
|
+
if (e.target.classList.contains("tl-more")) {
|
|
3866
|
+
const full = all().find((c) => c.commit === number);
|
|
3867
|
+
if (full) options.onExpand?.(full);
|
|
3868
|
+
return;
|
|
3869
|
+
}
|
|
3870
|
+
selected = selected === number ? null : number;
|
|
3871
|
+
const commit = all().find((c) => c.commit === selected) ?? null;
|
|
3872
|
+
options.onSelect?.(commit ? idsOf(commit) : [], commit);
|
|
3873
|
+
signature = "";
|
|
3874
|
+
});
|
|
3875
|
+
return {
|
|
3876
|
+
setPaused(next) {
|
|
3877
|
+
paused = next;
|
|
3878
|
+
showPaused();
|
|
3879
|
+
signature = "";
|
|
3880
|
+
},
|
|
3881
|
+
get paused() {
|
|
3882
|
+
return paused;
|
|
3883
|
+
},
|
|
3884
|
+
push(incoming) {
|
|
3885
|
+
if (paused) return;
|
|
3886
|
+
let added = false;
|
|
3887
|
+
for (const event of incoming) {
|
|
3888
|
+
if (event.seq <= seen) continue;
|
|
3889
|
+
events.push(event);
|
|
3890
|
+
seen = event.seq;
|
|
3891
|
+
added = true;
|
|
3892
|
+
}
|
|
3893
|
+
if (!added) return;
|
|
3894
|
+
if (events.length > limit * 40) events = events.slice(-limit * 40);
|
|
3895
|
+
},
|
|
3896
|
+
render(graph) {
|
|
3897
|
+
const nameOf = (e) => graph.get(e.id)?.label ?? e.label ?? `#${e.id}`;
|
|
3898
|
+
const rows = shown(nameOf);
|
|
3899
|
+
const next = `${seen}|${selected ?? ""}|${filter}|${rows.length}|${graph.size}`;
|
|
3900
|
+
if (next === signature) return;
|
|
3901
|
+
signature = next;
|
|
3902
|
+
if (!rows.length) {
|
|
3903
|
+
list.innerHTML = `<span class="tl-empty">${escape5(filter ? "nothing matches" : empty)}</span>`;
|
|
3904
|
+
return;
|
|
3905
|
+
}
|
|
3906
|
+
list.innerHTML = rows.map((commit) => {
|
|
3907
|
+
const open = commit.commit === selected;
|
|
3908
|
+
const did = summary(commit);
|
|
3909
|
+
const moved = change(commit);
|
|
3910
|
+
const parts = [
|
|
3911
|
+
["ran", "ran"],
|
|
3912
|
+
["write", "written"],
|
|
3913
|
+
["created", "made"],
|
|
3914
|
+
["disposed", "gone"]
|
|
3915
|
+
];
|
|
3916
|
+
const body = parts.map(([kind, verb]) => {
|
|
3917
|
+
const names = [
|
|
3918
|
+
...new Set(
|
|
3919
|
+
commit.events.filter((e) => e.kind === kind && e !== commit.cause).map(nameOf)
|
|
3920
|
+
)
|
|
3921
|
+
];
|
|
3922
|
+
return names.length ? `<div class="tl-part"><span class="tl-verb">${verb}</span> ${escape5(names.join(", "))}</div>` : "";
|
|
3923
|
+
}).join("");
|
|
3924
|
+
return `<div class="tl-commit${open ? " selected" : ""}" data-commit="${commit.commit}" title="${escape5(detail(commit, nameOf))}"><div class="tl-line"><span class="tl-caret">${open ? "▾" : "▸"}</span><span class="tl-time">${clock(commit.t)}</span><span class="tl-name">${escape5(subject(commit, nameOf))}</span>` + (did ? `<span class="tl-did">${escape5(did)}</span>` : "") + "</div>" + // Open, the value gets the width and wraps.
|
|
3925
|
+
(open ? `<div class="tl-body">${moved ? `<div class="tl-change">${escape5(moved)}</div>` : ""}${changeRows(commit.cause?.change, 6)}${body}${// Only worth offering when there is more than the row can hold.
|
|
3926
|
+
options.onExpand && (commit.cause?.change || commit.events.length > 6) ? '<button type="button" class="tl-more">open in full</button>' : ""}</div>` : "") + "</div>";
|
|
3927
|
+
}).join("");
|
|
3928
|
+
},
|
|
3929
|
+
selected() {
|
|
3930
|
+
const commit = all().find((c) => c.commit === selected);
|
|
3931
|
+
return commit ? idsOf(commit) : [];
|
|
3932
|
+
},
|
|
3933
|
+
clear() {
|
|
3934
|
+
events = [];
|
|
3935
|
+
seen = -1;
|
|
3936
|
+
selected = null;
|
|
3937
|
+
signature = "";
|
|
3938
|
+
list.innerHTML = `<span class="tl-empty">${escape5(empty)}</span>`;
|
|
3939
|
+
}
|
|
3940
|
+
};
|
|
3941
|
+
}
|
|
3942
|
+
var TIMELINE_CSS = `
|
|
3943
|
+
.tl-head { position: relative; display: flex; gap: 4px; padding: 0 .25rem; }
|
|
3944
|
+
.tl-pause {
|
|
3945
|
+
position: absolute; right: .4rem; top: 40%; transform: translateY(-50%);
|
|
3946
|
+
flex: none; width: 26px; cursor: pointer; font: inherit; font-size: 10px;
|
|
3947
|
+
border: 1px solid var(--fx-line, #ffffff1f); border-radius: 4px;
|
|
3948
|
+
background: transparent; color: var(--fx-muted, #8a90a2);
|
|
3949
|
+
}
|
|
3950
|
+
.tl-pause:hover { color: var(--fx-label, #e7e9ee); }
|
|
3951
|
+
.tl-pause.on { color: var(--fx-accent, #5eead4); border-color: var(--fx-accent, #5eead4); }
|
|
3952
|
+
.tl-filter {
|
|
3953
|
+
flex: 1; min-width: 0; box-sizing: border-box; padding: 3px 6px;
|
|
3954
|
+
font: inherit; font-size: 11.5px; border-radius: 4px;
|
|
3955
|
+
border: 1px solid var(--fx-line, #ffffff1f);
|
|
3956
|
+
background: var(--fx-input, transparent); color: var(--fx-label, #e7e9ee);
|
|
3957
|
+
padding-inline-end: 2rem;
|
|
3958
|
+
}
|
|
3959
|
+
.tl-empty { color: var(--fx-muted, #8a90a2); font-size: 12px; }
|
|
3960
|
+
.tl-commit { padding: 4px 6px; border-radius: 4px; cursor: pointer; font-size: 11.5px; }
|
|
3961
|
+
.tl-commit:hover { background: var(--fx-hover, #ffffff10); }
|
|
3962
|
+
.tl-commit.selected { background: var(--fx-selected, #ffffff1a); }
|
|
3963
|
+
.tl-line { display: flex; align-items: baseline; gap: 6px; }
|
|
3964
|
+
.tl-caret { color: var(--fx-muted, #8a90a2); font-size: 9px; flex: none; }
|
|
3965
|
+
.tl-time { font-family: ui-monospace, monospace; font-size: 10px; color: var(--fx-muted, #8a90a2); flex: none; }
|
|
3966
|
+
.tl-name { color: var(--fx-label, #e7e9ee); font-family: ui-monospace, monospace; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
3967
|
+
.tl-did { margin-left: auto; color: var(--fx-muted, #8a90a2); flex: none; }
|
|
3968
|
+
.tl-body { margin: 3px 0 2px 15px; }
|
|
3969
|
+
/* Wraps: an open entry exists to show the whole value. */
|
|
3970
|
+
.tl-change {
|
|
3971
|
+
font-family: ui-monospace, monospace; font-size: 11px;
|
|
3972
|
+
color: var(--fx-accent, #5eead4); overflow-wrap: anywhere; white-space: pre-wrap;
|
|
3973
|
+
}
|
|
3974
|
+
.tl-part { margin-top: 2px; color: var(--fx-label, #e7e9ee); font-size: 11px; overflow-wrap: anywhere; }
|
|
3975
|
+
.tl-verb { color: var(--fx-muted, #8a90a2); }
|
|
3976
|
+
.tl-more {
|
|
3977
|
+
margin-top: 5px; padding: 2px 8px; font: inherit; font-size: 10.5px; cursor: pointer;
|
|
3978
|
+
border: 1px solid var(--fx-line, #ffffff1f); border-radius: 4px;
|
|
3979
|
+
background: transparent; color: var(--fx-muted, #8a90a2);
|
|
3980
|
+
}
|
|
3981
|
+
.tl-more:hover { color: var(--fx-label, #e7e9ee); }
|
|
3982
|
+
${CHANGE_CSS}`;
|
|
3983
|
+
|
|
3984
|
+
// src/kind-filter.ts
|
|
3985
|
+
var FILTERABLE_KINDS = [
|
|
3986
|
+
"state",
|
|
3987
|
+
"memo",
|
|
3988
|
+
"effect",
|
|
3989
|
+
"store",
|
|
3990
|
+
"resource",
|
|
3991
|
+
"component",
|
|
3992
|
+
"provider",
|
|
3993
|
+
"control",
|
|
3994
|
+
"router",
|
|
3995
|
+
"handler",
|
|
3996
|
+
"context",
|
|
3997
|
+
"directive"
|
|
3998
|
+
];
|
|
3999
|
+
var LABEL = { handler: "route" };
|
|
4000
|
+
function createKindFilter(host2, options) {
|
|
4001
|
+
const doc = host2.ownerDocument;
|
|
4002
|
+
const kinds = options.kinds ?? FILTERABLE_KINDS;
|
|
4003
|
+
const on = new Set(kinds);
|
|
4004
|
+
const root = doc.createElement("div");
|
|
4005
|
+
root.className = "fx-kinds";
|
|
4006
|
+
const button = doc.createElement("button");
|
|
4007
|
+
button.type = "button";
|
|
4008
|
+
button.className = "fx-kinds-button fx-dropdown-btn";
|
|
4009
|
+
button.setAttribute("aria-haspopup", "true");
|
|
4010
|
+
button.setAttribute("aria-expanded", "false");
|
|
4011
|
+
button.title = "Choose which kinds of node the graph draws";
|
|
4012
|
+
const caret = doc.createElement("div");
|
|
4013
|
+
caret.className = "fx-dropdown-caret";
|
|
4014
|
+
const panel = doc.createElement("div");
|
|
4015
|
+
panel.className = "fx-kinds-panel";
|
|
4016
|
+
if (options.panelClass) {
|
|
4017
|
+
panel.classList.add(...options.panelClass.split(/\s+/).filter((c) => c));
|
|
4018
|
+
}
|
|
4019
|
+
panel.hidden = true;
|
|
4020
|
+
const all = doc.createElement("input");
|
|
4021
|
+
all.type = "checkbox";
|
|
4022
|
+
const allRow = doc.createElement("label");
|
|
4023
|
+
allRow.className = "fx-kinds-all";
|
|
4024
|
+
allRow.append(all, doc.createTextNode("all"));
|
|
4025
|
+
const boxes = /* @__PURE__ */ new Map();
|
|
4026
|
+
for (const kind of kinds) {
|
|
4027
|
+
const box = doc.createElement("input");
|
|
4028
|
+
box.type = "checkbox";
|
|
4029
|
+
box.checked = true;
|
|
4030
|
+
const row = doc.createElement("label");
|
|
4031
|
+
row.append(box, doc.createTextNode(LABEL[kind] ?? kind));
|
|
4032
|
+
panel.append(row);
|
|
4033
|
+
boxes.set(kind, box);
|
|
4034
|
+
}
|
|
4035
|
+
panel.prepend(allRow);
|
|
4036
|
+
root.append(button, panel);
|
|
4037
|
+
host2.append(root);
|
|
4038
|
+
const everything = () => on.size === kinds.length;
|
|
4039
|
+
const chosen = () => kinds.filter((kind) => on.has(kind));
|
|
4040
|
+
function paint() {
|
|
4041
|
+
const name = options.label ?? "Kinds";
|
|
4042
|
+
button.textContent = everything() ? name : `${name} · ${on.size}`;
|
|
4043
|
+
button.classList.toggle("on", !everything());
|
|
4044
|
+
all.checked = everything();
|
|
4045
|
+
button.appendChild(caret);
|
|
4046
|
+
all.indeterminate = on.size > 0 && !everything();
|
|
4047
|
+
for (const [kind, box] of boxes) box.checked = on.has(kind);
|
|
4048
|
+
}
|
|
4049
|
+
function report() {
|
|
4050
|
+
options.onChange(everything() ? null : chosen());
|
|
4051
|
+
}
|
|
4052
|
+
for (const [kind, box] of boxes) {
|
|
4053
|
+
box.addEventListener("change", () => {
|
|
4054
|
+
if (box.checked) on.add(kind);
|
|
4055
|
+
else on.delete(kind);
|
|
4056
|
+
paint();
|
|
4057
|
+
report();
|
|
4058
|
+
});
|
|
4059
|
+
}
|
|
4060
|
+
all.addEventListener("change", () => {
|
|
4061
|
+
for (const kind of kinds) on.add(kind);
|
|
4062
|
+
paint();
|
|
4063
|
+
report();
|
|
4064
|
+
});
|
|
4065
|
+
const close = () => {
|
|
4066
|
+
panel.hidden = true;
|
|
4067
|
+
button.setAttribute("aria-expanded", "false");
|
|
4068
|
+
};
|
|
4069
|
+
button.addEventListener("click", (event) => {
|
|
4070
|
+
event.stopPropagation();
|
|
4071
|
+
panel.hidden = !panel.hidden;
|
|
4072
|
+
button.setAttribute("aria-expanded", String(!panel.hidden));
|
|
4073
|
+
});
|
|
4074
|
+
panel.addEventListener("click", (event) => event.stopPropagation());
|
|
4075
|
+
const away = () => close();
|
|
4076
|
+
const key = (event) => {
|
|
4077
|
+
if (event.key === "Escape") close();
|
|
4078
|
+
};
|
|
4079
|
+
doc.addEventListener("click", away);
|
|
4080
|
+
doc.addEventListener("keydown", key);
|
|
4081
|
+
paint();
|
|
4082
|
+
return {
|
|
4083
|
+
get selected() {
|
|
4084
|
+
return chosen();
|
|
4085
|
+
},
|
|
4086
|
+
select(next) {
|
|
4087
|
+
on.clear();
|
|
4088
|
+
for (const kind of next ?? kinds) on.add(kind);
|
|
4089
|
+
paint();
|
|
4090
|
+
},
|
|
4091
|
+
destroy() {
|
|
4092
|
+
doc.removeEventListener("click", away);
|
|
4093
|
+
doc.removeEventListener("keydown", key);
|
|
4094
|
+
root.remove();
|
|
4095
|
+
}
|
|
4096
|
+
};
|
|
4097
|
+
}
|
|
4098
|
+
var KIND_FILTER_CSS = `
|
|
4099
|
+
.fx-kinds { position: relative; display: inline-flex; }
|
|
4100
|
+
.fx-kinds-button { white-space: nowrap; }
|
|
4101
|
+
/* The hidden attribute is display:none from the user agent, which a class setting display outranks. */
|
|
4102
|
+
.fx-kinds-panel[hidden] { display: none; }
|
|
4103
|
+
.fx-kinds-panel {
|
|
4104
|
+
position: absolute; top: calc(100% + 4px); z-index: 40;
|
|
4105
|
+
display: grid; gap: 2px; padding: 6px 8px; min-width: 132px;
|
|
4106
|
+
background: var(--fx-panel, #14161c);
|
|
4107
|
+
border: 1px solid var(--fx-line, #262a35); border-radius: 6px;
|
|
4108
|
+
box-shadow: 0 6px 18px rgba(0, 0, 0, .38);
|
|
4109
|
+
}
|
|
4110
|
+
.fx-kinds-panel:not(.fx-panel-left){ right: 0; }
|
|
4111
|
+
.fx-kinds-panel.fx-panel-left { left: 0; }
|
|
4112
|
+
.fx-kinds-panel label {
|
|
4113
|
+
display: flex; align-items: center; gap: 6px;
|
|
4114
|
+
font-size: 11px; color: var(--fx-muted, #8a90a2); cursor: pointer; user-select: none;
|
|
4115
|
+
}
|
|
4116
|
+
.fx-kinds-panel label:hover { color: var(--fx-label, #e7e9ee); }
|
|
4117
|
+
.fx-kinds-panel input { width: 11px; height: 11px; margin: 0; accent-color: var(--fx-accent, #5eead4); }
|
|
4118
|
+
.fx-kinds-all {
|
|
4119
|
+
padding-bottom: 4px; margin-bottom: 2px;
|
|
4120
|
+
border-bottom: 1px solid var(--fx-line, #262a35);
|
|
4121
|
+
color: var(--fx-label, #e7e9ee) !important;
|
|
4122
|
+
}
|
|
4123
|
+
`;
|
|
4124
|
+
var DROPDOWN_FILTER_CSS = `
|
|
4125
|
+
.fx-dropdown-btn { background-color: transparent; appearance: none; border: 1px solid var(--line-strong); outline: none; border-radius: .25rem; padding-block: .25rem; padding-inline: .875rem; font-size: .75rem; cursor: pointer }
|
|
4126
|
+
.fx-dropdown-btn:hover { border-color: color-mix(in oklab, var(--accent) 50%, transparent); background-color: var(--bg); }
|
|
4127
|
+
.fx-dropdown-caret { width: 0;height: 0;border-left: .15rem solid transparent; border-right: .15rem solid transparent; border-top: .15rem solid currentColor; transition: transform 0.2s ease; right: .4rem; top: 50%; color: color-mix(in oklab, var(--muted) 50%, transparent); position: absolute; }
|
|
4128
|
+
`;
|
|
4129
|
+
|
|
4130
|
+
// src/menu.ts
|
|
4131
|
+
function createMenu(host2, options) {
|
|
4132
|
+
const doc = host2.ownerDocument;
|
|
4133
|
+
const root = doc.createElement("div");
|
|
4134
|
+
root.className = "fx-menu";
|
|
4135
|
+
const button = doc.createElement("button");
|
|
4136
|
+
button.type = "button";
|
|
4137
|
+
button.className = "fx-menu-button fx-dropdown-btn";
|
|
4138
|
+
button.setAttribute("aria-haspopup", "true");
|
|
4139
|
+
button.setAttribute("aria-expanded", "false");
|
|
4140
|
+
if (options.hint) button.title = options.hint;
|
|
4141
|
+
const caret = doc.createElement("div");
|
|
4142
|
+
caret.className = "fx-dropdown-caret";
|
|
4143
|
+
const panel = doc.createElement("div");
|
|
4144
|
+
panel.className = "fx-menu-panel";
|
|
4145
|
+
if (options.menuClass) {
|
|
4146
|
+
panel.classList.add(...options.menuClass.split(/\s+/).filter((c) => c));
|
|
4147
|
+
}
|
|
4148
|
+
panel.hidden = true;
|
|
4149
|
+
const boxes = /* @__PURE__ */ new Map();
|
|
4150
|
+
for (const item of options.items) {
|
|
4151
|
+
if (item.type === "action") {
|
|
4152
|
+
const run = doc.createElement("button");
|
|
4153
|
+
run.type = "button";
|
|
4154
|
+
run.className = "fx-menu-action";
|
|
4155
|
+
run.textContent = item.label;
|
|
4156
|
+
if (item.hint) run.title = item.hint;
|
|
4157
|
+
run.addEventListener("click", () => {
|
|
4158
|
+
close();
|
|
4159
|
+
item.onRun();
|
|
4160
|
+
});
|
|
4161
|
+
panel.append(run);
|
|
4162
|
+
continue;
|
|
4163
|
+
}
|
|
4164
|
+
const box = doc.createElement("input");
|
|
4165
|
+
box.type = "checkbox";
|
|
4166
|
+
box.checked = item.on === true;
|
|
4167
|
+
const row = doc.createElement("label");
|
|
4168
|
+
if (item.hint) row.title = item.hint;
|
|
4169
|
+
row.append(box, doc.createTextNode(item.label));
|
|
4170
|
+
box.addEventListener("change", () => {
|
|
4171
|
+
paint();
|
|
4172
|
+
item.onChange(box.checked);
|
|
4173
|
+
});
|
|
4174
|
+
panel.append(row);
|
|
4175
|
+
boxes.set(item.label, box);
|
|
4176
|
+
}
|
|
4177
|
+
root.append(button, panel);
|
|
4178
|
+
host2.append(root);
|
|
4179
|
+
function paint() {
|
|
4180
|
+
let on = 0;
|
|
4181
|
+
for (const box of boxes.values()) if (box.checked) on++;
|
|
4182
|
+
button.textContent = on ? `${options.label} · ${on}` : options.label;
|
|
4183
|
+
button.appendChild(caret);
|
|
4184
|
+
button.classList.toggle("on", on > 0);
|
|
4185
|
+
}
|
|
4186
|
+
function close() {
|
|
4187
|
+
panel.hidden = true;
|
|
4188
|
+
button.setAttribute("aria-expanded", "false");
|
|
4189
|
+
}
|
|
4190
|
+
button.addEventListener("click", (event) => {
|
|
4191
|
+
event.stopPropagation();
|
|
4192
|
+
panel.hidden = !panel.hidden;
|
|
4193
|
+
button.setAttribute("aria-expanded", String(!panel.hidden));
|
|
4194
|
+
});
|
|
4195
|
+
panel.addEventListener("click", (event) => event.stopPropagation());
|
|
4196
|
+
const away = () => close();
|
|
4197
|
+
const key = (event) => {
|
|
4198
|
+
if (event.key === "Escape") close();
|
|
4199
|
+
};
|
|
4200
|
+
doc.addEventListener("click", away);
|
|
4201
|
+
doc.addEventListener("keydown", key);
|
|
4202
|
+
paint();
|
|
4203
|
+
return {
|
|
4204
|
+
set(label, on) {
|
|
4205
|
+
const box = boxes.get(label);
|
|
4206
|
+
if (!box) return;
|
|
4207
|
+
box.checked = on;
|
|
4208
|
+
paint();
|
|
4209
|
+
},
|
|
4210
|
+
destroy() {
|
|
4211
|
+
doc.removeEventListener("click", away);
|
|
4212
|
+
doc.removeEventListener("keydown", key);
|
|
4213
|
+
root.remove();
|
|
4214
|
+
}
|
|
4215
|
+
};
|
|
4216
|
+
}
|
|
4217
|
+
var MENU_CSS = `
|
|
4218
|
+
/* The button itself is styled by DROPDOWN_FILTER_CSS, shared with the kind filter. */
|
|
4219
|
+
.fx-menu { position: relative; display: inline-flex; }
|
|
4220
|
+
.fx-menu-button { white-space: nowrap; position: relative; padding-inline-end: 1.1rem; }
|
|
4221
|
+
/* hidden is display:none from the user agent, which a class setting display outranks. */
|
|
4222
|
+
.fx-menu-panel[hidden] { display: none; }
|
|
4223
|
+
.fx-menu-panel {
|
|
4224
|
+
position: absolute; top: calc(100% + 4px); z-index: 40;
|
|
4225
|
+
display: grid; gap: 2px; padding: 6px 8px; min-width: 150px;
|
|
4226
|
+
background: var(--fx-panel, #14161c);
|
|
4227
|
+
border: 1px solid var(--fx-line, #262a35); border-radius: 6px;
|
|
4228
|
+
box-shadow: 0 6px 18px rgba(0, 0, 0, .38);
|
|
4229
|
+
}
|
|
4230
|
+
.fx-menu-panel:not(.fx-panel-left){ right: 0; }
|
|
4231
|
+
.fx-menu-panel.fx-panel-left { left: 0; }
|
|
4232
|
+
.fx-menu-panel label {
|
|
4233
|
+
display: flex; align-items: center; gap: 6px;
|
|
4234
|
+
font-size: 11px; color: var(--fx-muted, #8a90a2); cursor: pointer; user-select: none;
|
|
4235
|
+
}
|
|
4236
|
+
.fx-menu-panel label:hover { color: var(--fx-label, #e7e9ee); }
|
|
4237
|
+
.fx-menu-panel input { width: 11px; height: 11px; margin: 0; accent-color: var(--fx-accent, #5eead4); }
|
|
4238
|
+
.fx-menu-action {
|
|
4239
|
+
text-align: left; padding: 2px 0; border: 0; background: none; cursor: pointer;
|
|
4240
|
+
font: inherit; font-size: 11px; color: var(--fx-muted, #8a90a2);
|
|
4241
|
+
/* A button does not inherit this, so an action read lowercase beside uppercase toggles. */
|
|
4242
|
+
text-transform: inherit; letter-spacing: inherit;
|
|
4243
|
+
}
|
|
4244
|
+
.fx-menu-action:hover { color: var(--fx-accent, #5eead4); }
|
|
4245
|
+
`;
|
|
4246
|
+
|
|
4247
|
+
// src/detail-panel.ts
|
|
4248
|
+
var escape6 = (text) => text.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
4249
|
+
var clock2 = (t) => {
|
|
4250
|
+
const d = new Date(t);
|
|
4251
|
+
const pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
4252
|
+
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;
|
|
4253
|
+
};
|
|
4254
|
+
var VERBS = {
|
|
4255
|
+
write: "written",
|
|
4256
|
+
ran: "ran",
|
|
4257
|
+
created: "made",
|
|
4258
|
+
disposed: "gone"
|
|
4259
|
+
};
|
|
4260
|
+
var MIN_W2 = 320;
|
|
4261
|
+
var MIN_H2 = 200;
|
|
4262
|
+
function draggable2(handle, onMove) {
|
|
4263
|
+
handle.addEventListener("pointerdown", (event) => {
|
|
4264
|
+
if (event.button !== 0) return;
|
|
4265
|
+
if (event.target.closest("button")) return;
|
|
4266
|
+
event.preventDefault();
|
|
4267
|
+
event.stopPropagation();
|
|
4268
|
+
handle.setPointerCapture(event.pointerId);
|
|
4269
|
+
let lastX = event.clientX;
|
|
4270
|
+
let lastY = event.clientY;
|
|
4271
|
+
const move = (e) => {
|
|
4272
|
+
onMove(e.clientX - lastX, e.clientY - lastY);
|
|
4273
|
+
lastX = e.clientX;
|
|
4274
|
+
lastY = e.clientY;
|
|
4275
|
+
};
|
|
4276
|
+
const stop = () => {
|
|
4277
|
+
handle.removeEventListener("pointermove", move);
|
|
4278
|
+
handle.removeEventListener("pointerup", stop);
|
|
4279
|
+
handle.removeEventListener("pointercancel", stop);
|
|
4280
|
+
handle.removeEventListener("lostpointercapture", stop);
|
|
4281
|
+
if (handle.hasPointerCapture(event.pointerId)) handle.releasePointerCapture(event.pointerId);
|
|
4282
|
+
};
|
|
4283
|
+
handle.addEventListener("pointermove", move);
|
|
4284
|
+
handle.addEventListener("pointerup", stop);
|
|
4285
|
+
handle.addEventListener("pointercancel", stop);
|
|
4286
|
+
handle.addEventListener("lostpointercapture", stop);
|
|
4287
|
+
});
|
|
4288
|
+
}
|
|
4289
|
+
function createDetailPanel(host2, options = {}) {
|
|
4290
|
+
const panel = host2.ownerDocument.createElement("div");
|
|
4291
|
+
panel.className = "dp-panel";
|
|
4292
|
+
panel.hidden = true;
|
|
4293
|
+
panel.innerHTML = '<div class="dp-bar"><span class="dp-title"></span><button type="button" class="dp-close" title="Close" aria-label="Close">✕</button></div><div class="dp-body"></div><div class="dp-grip" title="Drag to resize"></div>';
|
|
4294
|
+
host2.appendChild(panel);
|
|
4295
|
+
const title = panel.querySelector(".dp-title");
|
|
4296
|
+
const body = panel.querySelector(".dp-body");
|
|
4297
|
+
const bar = panel.querySelector(".dp-bar");
|
|
4298
|
+
const grip = panel.querySelector(".dp-grip");
|
|
4299
|
+
let x = 0;
|
|
4300
|
+
let y = 0;
|
|
4301
|
+
let width = 520;
|
|
4302
|
+
let height = 380;
|
|
4303
|
+
let open = false;
|
|
4304
|
+
const place2 = () => {
|
|
4305
|
+
panel.style.left = `${Math.round(x)}px`;
|
|
4306
|
+
panel.style.top = `${Math.round(y)}px`;
|
|
4307
|
+
panel.style.width = `${Math.round(width)}px`;
|
|
4308
|
+
panel.style.height = `${Math.round(height)}px`;
|
|
4309
|
+
};
|
|
4310
|
+
draggable2(bar, (dx, dy) => {
|
|
4311
|
+
const view = panel.ownerDocument.defaultView;
|
|
4312
|
+
x = Math.max(0, Math.min((view?.innerWidth ?? width) - 60, x + dx));
|
|
4313
|
+
y = Math.max(0, Math.min((view?.innerHeight ?? height) - 30, y + dy));
|
|
4314
|
+
place2();
|
|
4315
|
+
});
|
|
4316
|
+
draggable2(grip, (dx, dy) => {
|
|
4317
|
+
width = Math.max(MIN_W2, width + dx);
|
|
4318
|
+
height = Math.max(MIN_H2, height + dy);
|
|
4319
|
+
place2();
|
|
4320
|
+
});
|
|
4321
|
+
panel.querySelector(".dp-close")?.addEventListener("click", () => close());
|
|
4322
|
+
body.addEventListener("click", (e) => {
|
|
4323
|
+
if (handleCopyClick(e)) return;
|
|
4324
|
+
const row = e.target.closest(".dp-node");
|
|
4325
|
+
if (row?.dataset.id) options.onPick?.(Number(row.dataset.id));
|
|
4326
|
+
});
|
|
4327
|
+
function close() {
|
|
4328
|
+
open = false;
|
|
4329
|
+
panel.hidden = true;
|
|
4330
|
+
}
|
|
4331
|
+
return {
|
|
4332
|
+
open(commit, graph) {
|
|
4333
|
+
const nameOf = (e) => graph.get(e.id)?.label ?? e.label ?? `#${e.id}`;
|
|
4334
|
+
const cause = commit.cause;
|
|
4335
|
+
title.textContent = cause ? `${nameOf(cause)} · ${clock2(commit.t)}` : `${commit.commit === 0 ? "first render" : `commit ${commit.commit}`} · ${clock2(commit.t)}`;
|
|
4336
|
+
const headline = cause && cause.from !== void 0 && cause.to !== void 0 ? `<div class="dp-change"><span class="ch-from">${escape6(cause.from)}</span><span class="ch-arrow">→</span><span class="ch-to">${escape6(cause.to)}</span></div>` : "";
|
|
4337
|
+
const groups = ["write", "ran", "created", "disposed"].map((kind) => {
|
|
4338
|
+
const rows = commit.events.filter((e) => e.kind === kind && e !== cause);
|
|
4339
|
+
if (!rows.length) return "";
|
|
4340
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4341
|
+
for (const event of rows) {
|
|
4342
|
+
const found = seen.get(event.id);
|
|
4343
|
+
if (found) found.count++;
|
|
4344
|
+
else seen.set(event.id, { name: nameOf(event), count: 1 });
|
|
4345
|
+
}
|
|
4346
|
+
const items = [...seen].map(
|
|
4347
|
+
([id, { name, count }]) => `<button type="button" class="dp-node" data-id="${id}">${escape6(name)}${count > 1 ? ` ×${count}` : ""}</button>`
|
|
4348
|
+
).join("");
|
|
4349
|
+
return `<div class="dp-group"><div class="dp-verb">${VERBS[kind]}</div><div class="dp-nodes">${items}</div></div>`;
|
|
4350
|
+
}).join("");
|
|
4351
|
+
const node = cause ? graph.get(cause.id) : void 0;
|
|
4352
|
+
const where = (t) => {
|
|
4353
|
+
if (!t.source) return "";
|
|
4354
|
+
const file = (t.source.file ?? "").split("/").pop() ?? "";
|
|
4355
|
+
const out = t.source.compiled;
|
|
4356
|
+
const tail = out && out.line !== t.source.line ? ` · out ${out.line}` : "";
|
|
4357
|
+
return ` — ${file}:${t.source.line ?? ""}${tail}`;
|
|
4358
|
+
};
|
|
4359
|
+
const targets = node?.targets?.length ? `<div class="dp-group"><div class="dp-verb">updates</div><div class="dp-targets">${node.targets.map(
|
|
4360
|
+
(t) => `<div>${escape6(t.name ? `${t.name} of ${t.element}` : t.element)}<span class="dp-where">${escape6(where(t))}</span></div>`
|
|
4361
|
+
).join("")}</div></div>` : "";
|
|
4362
|
+
body.innerHTML = headline + changeRows(cause?.change) + groups + targets;
|
|
4363
|
+
if (!open) {
|
|
4364
|
+
const view = panel.ownerDocument.defaultView;
|
|
4365
|
+
x = Math.max(12, ((view?.innerWidth ?? 900) - width) / 2);
|
|
4366
|
+
y = Math.max(12, ((view?.innerHeight ?? 600) - height) / 3);
|
|
4367
|
+
place2();
|
|
4368
|
+
}
|
|
4369
|
+
open = true;
|
|
4370
|
+
panel.hidden = false;
|
|
4371
|
+
},
|
|
4372
|
+
close,
|
|
4373
|
+
get isOpen() {
|
|
4374
|
+
return open;
|
|
4375
|
+
},
|
|
4376
|
+
destroy() {
|
|
4377
|
+
panel.remove();
|
|
4378
|
+
}
|
|
4379
|
+
};
|
|
4380
|
+
}
|
|
4381
|
+
var DETAIL_PANEL_CSS = `
|
|
4382
|
+
.dp-panel {
|
|
4383
|
+
position: fixed; z-index: 40; display: flex; flex-direction: column;
|
|
4384
|
+
border: 1px solid var(--fx-line, #ffffff26); border-radius: 8px;
|
|
4385
|
+
background: var(--fx-panel, #14161c); color: var(--fx-label, #e7e9ee);
|
|
4386
|
+
box-shadow: 0 12px 40px rgba(0, 0, 0, .45); overflow: hidden;
|
|
4387
|
+
}
|
|
4388
|
+
.dp-panel[hidden] { display: none; }
|
|
4389
|
+
.dp-bar {
|
|
4390
|
+
display: flex; align-items: center; gap: 8px; padding: 6px 8px 6px 12px;
|
|
4391
|
+
cursor: move; user-select: none;
|
|
4392
|
+
border-bottom: 1px solid var(--fx-line, #ffffff1f);
|
|
4393
|
+
}
|
|
4394
|
+
.dp-title { font-family: ui-monospace, monospace; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
4395
|
+
.dp-close {
|
|
4396
|
+
margin-left: auto; width: 22px; height: 20px; padding: 0; cursor: pointer; font: inherit; font-size: 11px;
|
|
4397
|
+
border: 1px solid var(--fx-line, #ffffff1f); border-radius: 4px;
|
|
4398
|
+
background: transparent; color: var(--fx-muted, #8a90a2);
|
|
4399
|
+
}
|
|
4400
|
+
.dp-close:hover { color: var(--fx-label, #e7e9ee); }
|
|
4401
|
+
.dp-body { flex: 1; min-height: 0; overflow: auto; padding: 10px 12px; }
|
|
4402
|
+
.dp-change { font-family: ui-monospace, monospace; font-size: 12px; overflow-wrap: anywhere; }
|
|
4403
|
+
.dp-group { margin-top: 10px; }
|
|
4404
|
+
.dp-verb { font-size: 10.5px; text-transform: uppercase; letter-spacing: 1px; color: var(--fx-muted, #8a90a2); }
|
|
4405
|
+
.dp-nodes { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
|
|
4406
|
+
.dp-node {
|
|
4407
|
+
padding: 2px 7px; cursor: pointer; font: inherit; font-family: ui-monospace, monospace; font-size: 11px;
|
|
4408
|
+
border: 1px solid var(--fx-line, #ffffff1f); border-radius: 999px;
|
|
4409
|
+
background: transparent; color: var(--fx-label, #e7e9ee);
|
|
4410
|
+
}
|
|
4411
|
+
.dp-node:hover { border-color: var(--fx-accent, #5eead4); }
|
|
4412
|
+
.dp-targets { margin-top: 4px; font-family: ui-monospace, monospace; font-size: 11px; color: var(--fx-label, #e7e9ee); }
|
|
4413
|
+
.dp-where { color: var(--fx-muted, #8a90a2); }
|
|
4414
|
+
/* Bottom-right, where every desktop puts it. */
|
|
4415
|
+
.dp-grip {
|
|
4416
|
+
position: absolute; right: 0; bottom: 0; width: 16px; height: 16px; cursor: nwse-resize;
|
|
4417
|
+
background: linear-gradient(135deg, transparent 50%, var(--fx-line, #ffffff40) 50%);
|
|
4418
|
+
}
|
|
4419
|
+
${CHANGE_CSS}`;
|
|
4420
|
+
|
|
4421
|
+
// src/export.ts
|
|
4422
|
+
var at2 = (source) => {
|
|
4423
|
+
if (!source) return void 0;
|
|
4424
|
+
const where = `${source.file ?? ""}:${source.line ?? ""}:${source.column ?? ""}`;
|
|
4425
|
+
const out = source.compiled;
|
|
4426
|
+
const same2 = out?.line === source.line && out?.column === source.column;
|
|
4427
|
+
return out && !same2 ? `${where} (out ${out.line ?? ""}:${out.column ?? ""})` : where;
|
|
4428
|
+
};
|
|
4429
|
+
function naming(graph) {
|
|
4430
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4431
|
+
for (const node of graph.values()) seen.set(node.label, (seen.get(node.label) ?? 0) + 1);
|
|
4432
|
+
return (id) => {
|
|
4433
|
+
const node = graph.get(id);
|
|
4434
|
+
if (!node) return `#${id}`;
|
|
4435
|
+
return (seen.get(node.label) ?? 0) > 1 ? `${node.label}#${node.id}` : node.label;
|
|
4436
|
+
};
|
|
4437
|
+
}
|
|
4438
|
+
function exportGraph(graph, options = {}) {
|
|
4439
|
+
const limit = options.limit ?? 400;
|
|
4440
|
+
const name = naming(graph);
|
|
4441
|
+
const nodes = [...graph.values()].map((node) => {
|
|
4442
|
+
let value = node.detail ?? node.value;
|
|
4443
|
+
if (node.read) {
|
|
4444
|
+
try {
|
|
4445
|
+
value = formatValue(node.read(), limit);
|
|
4446
|
+
} catch {
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
const feeds = [...node.deps].map(name);
|
|
4450
|
+
const wrote = [...node.writes].map(name);
|
|
4451
|
+
const state = nodeState(node);
|
|
4452
|
+
return {
|
|
4453
|
+
id: node.id,
|
|
4454
|
+
label: node.label,
|
|
4455
|
+
kind: node.kind,
|
|
4456
|
+
...node.authored === false ? { authored: false } : {},
|
|
4457
|
+
...value ? { value } : {},
|
|
4458
|
+
...node.runs ? { runs: node.runs } : {},
|
|
4459
|
+
// Only a node with a core has one; a static node would report a version of 0.
|
|
4460
|
+
...node.core?.version !== void 0 ? { version: nodeVersion(node) } : {},
|
|
4461
|
+
...state ? { state } : {},
|
|
4462
|
+
...feeds.length ? { reads: feeds } : {},
|
|
4463
|
+
...wrote.length ? { writes: wrote } : {},
|
|
4464
|
+
...node.createdIn !== void 0 ? { madeIn: name(node.createdIn) } : {},
|
|
4465
|
+
...at2(node.source) ? { source: at2(node.source) } : {},
|
|
4466
|
+
...at2(node.declaredAt) ? { declaredAt: at2(node.declaredAt) } : {},
|
|
4467
|
+
...at2(node.usedAt) ? { usedAt: at2(node.usedAt) } : {},
|
|
4468
|
+
...node.targets?.length ? {
|
|
4469
|
+
updates: node.targets.map((target) => {
|
|
4470
|
+
const what = target.name ? `${target.name} of ${target.element}` : target.element;
|
|
4471
|
+
const where = at2(target.source);
|
|
4472
|
+
return where ? `${what} — ${where}` : what;
|
|
4473
|
+
})
|
|
4474
|
+
} : {},
|
|
4475
|
+
...node.setAt?.length ? { setAt: node.setAt.map((s) => at2(s)).filter(Boolean) } : {}
|
|
4476
|
+
};
|
|
4477
|
+
});
|
|
4478
|
+
return {
|
|
4479
|
+
t: Date.now(),
|
|
4480
|
+
from: options.fromSource ? "source" : "running application",
|
|
4481
|
+
nodes,
|
|
4482
|
+
...options.events?.length ? { events: [...options.events] } : {}
|
|
4483
|
+
};
|
|
4484
|
+
}
|
|
4485
|
+
function graphToJson(graph, options = {}) {
|
|
4486
|
+
return JSON.stringify(exportGraph(graph, options), null, 2);
|
|
4487
|
+
}
|
|
4488
|
+
export {
|
|
4489
|
+
CHANGE_CSS,
|
|
4490
|
+
DETAIL_PANEL_CSS,
|
|
4491
|
+
DEVTOOLS_CSS,
|
|
4492
|
+
DROPDOWN_FILTER_CSS,
|
|
4493
|
+
FILTERABLE_KINDS,
|
|
4494
|
+
FLOAT_CSS,
|
|
4495
|
+
INSPECTOR_CSS,
|
|
4496
|
+
KIND_FILTER_CSS,
|
|
4497
|
+
MENU_CSS,
|
|
4498
|
+
TIMELINE_CSS,
|
|
4499
|
+
TREE_CSS,
|
|
4500
|
+
TREE_KINDS,
|
|
4501
|
+
VALUE_PANE_CSS,
|
|
4502
|
+
annotationOf,
|
|
4503
|
+
applySnapshot,
|
|
4504
|
+
callSite,
|
|
4505
|
+
changeRows,
|
|
4506
|
+
componentTree,
|
|
4507
|
+
copyText,
|
|
4508
|
+
createComponentTree,
|
|
4509
|
+
createDetailPanel,
|
|
4510
|
+
createGraphView,
|
|
4511
|
+
createInspector,
|
|
4512
|
+
createKindFilter,
|
|
4513
|
+
createMenu,
|
|
4514
|
+
createReactor,
|
|
4515
|
+
createTimeline,
|
|
4516
|
+
createValuePane,
|
|
4517
|
+
currentCallSite,
|
|
4518
|
+
describeNode,
|
|
4519
|
+
devtoolsPlugins,
|
|
4520
|
+
diffValues,
|
|
4521
|
+
exportGraph,
|
|
4522
|
+
formatValue,
|
|
4523
|
+
graphToJson,
|
|
4524
|
+
handleCopyClick,
|
|
4525
|
+
hideHighlight,
|
|
4526
|
+
indentValue,
|
|
4527
|
+
installDevtoolsHook,
|
|
4528
|
+
makeFloatable,
|
|
4529
|
+
markReactiveSource,
|
|
4530
|
+
nodeState,
|
|
4531
|
+
nodeVersion,
|
|
4532
|
+
observeGraph,
|
|
4533
|
+
pluginBadge,
|
|
4534
|
+
pluginRows,
|
|
4535
|
+
registerDevtoolsPlugin,
|
|
4536
|
+
removeHighlight,
|
|
4537
|
+
serializeGraph,
|
|
4538
|
+
shorten,
|
|
4539
|
+
shortenPath,
|
|
4540
|
+
showHighlight,
|
|
4541
|
+
snapshotToRenderMap,
|
|
4542
|
+
startPicking,
|
|
4543
|
+
toCommits
|
|
4544
|
+
};
|