@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.
Files changed (158) hide show
  1. package/README.md +80 -2
  2. package/dist/callsite.cjs +72 -0
  3. package/dist/callsite.d.ts +22 -0
  4. package/dist/callsite.d.ts.map +1 -0
  5. package/dist/callsite.js +82 -0
  6. package/dist/callsite.mjs +51 -0
  7. package/dist/change-view.cjs +134 -0
  8. package/dist/change-view.d.ts +10 -0
  9. package/dist/change-view.d.ts.map +1 -0
  10. package/dist/change-view.js +102 -0
  11. package/dist/change-view.mjs +111 -0
  12. package/dist/copy.cjs +63 -0
  13. package/dist/copy.d.ts +9 -0
  14. package/dist/copy.d.ts.map +1 -0
  15. package/dist/copy.js +52 -0
  16. package/dist/copy.mjs +42 -0
  17. package/dist/describe.cjs +55 -0
  18. package/dist/describe.d.ts +3 -0
  19. package/dist/describe.d.ts.map +1 -0
  20. package/dist/describe.js +47 -0
  21. package/dist/describe.mjs +34 -0
  22. package/dist/detail-panel.cjs +307 -0
  23. package/dist/detail-panel.d.ts +17 -0
  24. package/dist/detail-panel.d.ts.map +1 -0
  25. package/dist/detail-panel.js +217 -0
  26. package/dist/detail-panel.mjs +284 -0
  27. package/dist/diff.cjs +225 -0
  28. package/dist/diff.d.ts +31 -0
  29. package/dist/diff.d.ts.map +1 -0
  30. package/dist/diff.js +132 -0
  31. package/dist/diff.mjs +202 -0
  32. package/dist/dom.cjs +204 -0
  33. package/dist/dom.d.ts +81 -0
  34. package/dist/dom.d.ts.map +1 -0
  35. package/dist/dom.js +269 -0
  36. package/dist/dom.mjs +183 -0
  37. package/dist/export.cjs +221 -0
  38. package/dist/export.d.ts +55 -0
  39. package/dist/export.d.ts.map +1 -0
  40. package/dist/export.js +90 -0
  41. package/dist/export.mjs +209 -0
  42. package/dist/float.cjs +232 -0
  43. package/dist/float.d.ts +54 -0
  44. package/dist/float.d.ts.map +1 -0
  45. package/dist/float.js +256 -0
  46. package/dist/float.mjs +211 -0
  47. package/dist/format.cjs +175 -0
  48. package/dist/format.d.ts +16 -0
  49. package/dist/format.d.ts.map +1 -0
  50. package/dist/format.js +188 -0
  51. package/dist/format.mjs +154 -0
  52. package/dist/graph-view.cjs +893 -0
  53. package/dist/graph-view.d.ts +79 -2
  54. package/dist/graph-view.d.ts.map +1 -1
  55. package/dist/graph-view.js +866 -50
  56. package/dist/graph-view.mjs +870 -0
  57. package/dist/highlight.cjs +140 -0
  58. package/dist/highlight.d.ts +56 -0
  59. package/dist/highlight.d.ts.map +1 -0
  60. package/dist/highlight.js +154 -0
  61. package/dist/highlight.mjs +119 -0
  62. package/dist/history.cjs +95 -0
  63. package/dist/history.d.ts +59 -0
  64. package/dist/history.d.ts.map +1 -0
  65. package/dist/history.js +85 -0
  66. package/dist/history.mjs +74 -0
  67. package/dist/hook.cjs +1562 -0
  68. package/dist/hook.d.ts +60 -0
  69. package/dist/hook.d.ts.map +1 -0
  70. package/dist/hook.js +142 -0
  71. package/dist/hook.mjs +1553 -0
  72. package/dist/index.cjs +4552 -0
  73. package/dist/index.d.ts +44 -1
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +25 -1
  76. package/dist/index.mjs +4544 -0
  77. package/dist/inspector.cjs +688 -0
  78. package/dist/inspector.d.ts +60 -0
  79. package/dist/inspector.d.ts.map +1 -0
  80. package/dist/inspector.js +415 -0
  81. package/dist/inspector.mjs +676 -0
  82. package/dist/instrument.cjs +497 -0
  83. package/dist/instrument.d.ts +147 -5
  84. package/dist/instrument.d.ts.map +1 -1
  85. package/dist/instrument.js +164 -44
  86. package/dist/instrument.mjs +487 -0
  87. package/dist/kind-filter.cjs +172 -0
  88. package/dist/kind-filter.d.ts +39 -0
  89. package/dist/kind-filter.d.ts.map +1 -0
  90. package/dist/kind-filter.js +151 -0
  91. package/dist/kind-filter.mjs +151 -0
  92. package/dist/menu.cjs +141 -0
  93. package/dist/menu.d.ts +37 -0
  94. package/dist/menu.d.ts.map +1 -0
  95. package/dist/menu.js +125 -0
  96. package/dist/menu.mjs +120 -0
  97. package/dist/observe.cjs +1352 -0
  98. package/dist/observe.d.ts +80 -0
  99. package/dist/observe.d.ts.map +1 -0
  100. package/dist/observe.js +893 -0
  101. package/dist/observe.mjs +1345 -0
  102. package/dist/palette.cjs +52 -0
  103. package/dist/palette.d.ts +11 -0
  104. package/dist/palette.d.ts.map +1 -0
  105. package/dist/palette.js +27 -0
  106. package/dist/palette.mjs +31 -0
  107. package/dist/path.cjs +45 -0
  108. package/dist/path.d.ts +19 -0
  109. package/dist/path.d.ts.map +1 -0
  110. package/dist/path.js +49 -0
  111. package/dist/path.mjs +24 -0
  112. package/dist/plugin.cjs +87 -0
  113. package/dist/plugin.d.ts +121 -0
  114. package/dist/plugin.d.ts.map +1 -0
  115. package/dist/plugin.js +74 -0
  116. package/dist/plugin.mjs +66 -0
  117. package/dist/plugins/context.cjs +98 -0
  118. package/dist/plugins/context.d.ts +10 -0
  119. package/dist/plugins/context.d.ts.map +1 -0
  120. package/dist/plugins/context.js +80 -0
  121. package/dist/plugins/context.mjs +75 -0
  122. package/dist/plugins/directives.cjs +98 -0
  123. package/dist/plugins/directives.d.ts +25 -0
  124. package/dist/plugins/directives.d.ts.map +1 -0
  125. package/dist/plugins/directives.js +78 -0
  126. package/dist/plugins/directives.mjs +75 -0
  127. package/dist/plugins/index.cjs +162 -0
  128. package/dist/plugins/index.d.ts +7 -0
  129. package/dist/plugins/index.d.ts.map +1 -0
  130. package/dist/plugins/index.js +9 -0
  131. package/dist/plugins/index.mjs +140 -0
  132. package/dist/source.cjs +111 -0
  133. package/dist/source.d.ts +72 -0
  134. package/dist/source.d.ts.map +1 -0
  135. package/dist/source.js +153 -0
  136. package/dist/source.mjs +90 -0
  137. package/dist/timeline.cjs +350 -0
  138. package/dist/timeline.d.ts +30 -0
  139. package/dist/timeline.d.ts.map +1 -0
  140. package/dist/timeline.js +271 -0
  141. package/dist/timeline.mjs +327 -0
  142. package/dist/tree.cjs +574 -0
  143. package/dist/tree.d.ts +128 -0
  144. package/dist/tree.d.ts.map +1 -0
  145. package/dist/tree.js +645 -0
  146. package/dist/tree.mjs +551 -0
  147. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  148. package/dist/value-pane.cjs +223 -0
  149. package/dist/value-pane.d.ts +10 -0
  150. package/dist/value-pane.d.ts.map +1 -0
  151. package/dist/value-pane.js +61 -0
  152. package/dist/value-pane.mjs +200 -0
  153. package/dist/wire.cjs +109 -0
  154. package/dist/wire.d.ts +100 -0
  155. package/dist/wire.d.ts.map +1 -0
  156. package/dist/wire.js +93 -0
  157. package/dist/wire.mjs +89 -0
  158. package/package.json +17 -5
@@ -0,0 +1,1345 @@
1
+ // src/observe.ts
2
+ import {
3
+ observeReactiveNodes,
4
+ untrack as untrack2
5
+ } from "@fluixi/reactive/signal";
6
+
7
+ // src/source.ts
8
+ var MARKER = "__fx_source";
9
+ var COMPONENT_MARKER = "__fx_component";
10
+ var HOLE_MARKER = "__fx_hole";
11
+ var PROPS_MARKER = "__fx_props";
12
+ var MARKS = "__FLUIXI_DEVTOOLS_MARKS__";
13
+ var host = globalThis;
14
+ var shared = host[MARKS] ?? {
15
+ pending: null,
16
+ expiring: false,
17
+ components: /* @__PURE__ */ new Map()
18
+ };
19
+ host[MARKS] = shared;
20
+ function markReactiveSource(file, line, column, name) {
21
+ const location2 = locate(file, line, column);
22
+ if (shared.pending && location2.external && !shared.pending.location?.external) return;
23
+ shared.pending = { name, location: location2 };
24
+ shared.expiring = false;
25
+ }
26
+ function locate(file, line, column) {
27
+ const external = !!file && (/^([a-zA-Z]:)?[\\/]/.test(file) || file.includes("node_modules/"));
28
+ return external ? { file, line, column, external } : { file, line, column };
29
+ }
30
+ function markComponentSource(name, file, line, column) {
31
+ const seen = shared.components.get(name);
32
+ const next = locate(file, line, column);
33
+ if (seen === void 0) {
34
+ shared.components.set(name, next);
35
+ return;
36
+ }
37
+ if (!seen || seen.file !== file || seen.line !== line) shared.components.set(name, null);
38
+ }
39
+ function componentSource(name) {
40
+ return shared.components.get(name) ?? void 0;
41
+ }
42
+ function takeReactiveSource() {
43
+ const mark = shared.pending;
44
+ if (mark) {
45
+ if (mark.name !== void 0) shared.pending = { location: mark.location };
46
+ if (!shared.expiring) {
47
+ shared.expiring = true;
48
+ queueMicrotask(() => {
49
+ shared.expiring = false;
50
+ shared.pending = null;
51
+ });
52
+ }
53
+ }
54
+ return mark;
55
+ }
56
+ var HOLES = /* @__PURE__ */ new WeakMap();
57
+ var HOLE_TAG = "__fx_at";
58
+ function markHole(accessor, line, column, name) {
59
+ if (accessor && (typeof accessor === "object" || typeof accessor === "function")) {
60
+ const at = name === void 0 ? { line, column } : { line, column, name };
61
+ HOLES.set(accessor, at);
62
+ try {
63
+ Object.defineProperty(accessor, HOLE_TAG, { value: at, configurable: true });
64
+ } catch {
65
+ }
66
+ }
67
+ shared.pending = null;
68
+ return accessor;
69
+ }
70
+ var PROPS = /* @__PURE__ */ new WeakMap();
71
+ function markProps(props, at) {
72
+ if (props && typeof props === "object") PROPS.set(props, at);
73
+ return props;
74
+ }
75
+ function propsLocations(props) {
76
+ return props && typeof props === "object" ? PROPS.get(props) : void 0;
77
+ }
78
+ function holeLocation(accessor) {
79
+ if (!accessor || typeof accessor !== "object" && typeof accessor !== "function") return void 0;
80
+ return HOLES.get(accessor) ?? accessor[HOLE_TAG];
81
+ }
82
+ function installSourceMarker() {
83
+ if (typeof host[MARKER] !== "function") host[MARKER] = markReactiveSource;
84
+ if (typeof host[COMPONENT_MARKER] !== "function") host[COMPONENT_MARKER] = markComponentSource;
85
+ if (typeof host[HOLE_MARKER] !== "function") host[HOLE_MARKER] = markHole;
86
+ if (typeof host[PROPS_MARKER] !== "function") host[PROPS_MARKER] = markProps;
87
+ }
88
+
89
+ // src/callsite.ts
90
+ var EXTENSION = ["chrome-extension://", "moz-extension://", "safari-web-extension://", "extension://"];
91
+ var INTERNAL = [
92
+ "/packages/reactive/",
93
+ "/packages/dom/",
94
+ "/packages/core/",
95
+ "/packages/devtools/",
96
+ "/@fluixi/",
97
+ "node_modules",
98
+ "fluixi-devtools",
99
+ ...EXTENSION
100
+ ];
101
+ var REPORTING = [
102
+ "/packages/reactive/src/lib/signal/graph/",
103
+ "/packages/devtools/",
104
+ "fluixi-devtools",
105
+ // Not a fallback either: naming the agent as where a node came from is worse than nothing.
106
+ ...EXTENSION
107
+ ];
108
+ var FRAME = /(?:at\s+(?:[^(]*\()?)([^()\s]+):(\d+):(\d+)\)?$/;
109
+ var isInternal = (frame) => INTERNAL.some((part) => frame.includes(part));
110
+ function locate2(frame, origin) {
111
+ const match = FRAME.exec(frame);
112
+ if (!match) return void 0;
113
+ let file = match[1];
114
+ if (origin && file.startsWith(origin)) file = file.slice(origin.length);
115
+ file = file.replace(/^\/@fs\//, "/").replace(/\?.*$/, "").replace(/^\//, "");
116
+ return file ? { file, line: Number(match[2]), column: Number(match[3]) } : void 0;
117
+ }
118
+ function callSite(stack, origin) {
119
+ if (!stack) return void 0;
120
+ let fallback;
121
+ for (const line of stack.split("\n")) {
122
+ const frame = line.trim();
123
+ if (!frame.startsWith("at ")) continue;
124
+ if (!isInternal(frame)) {
125
+ const location2 = locate2(frame, origin);
126
+ if (location2) return { location: location2, external: false };
127
+ continue;
128
+ }
129
+ if (!fallback && !REPORTING.some((part) => frame.includes(part))) fallback = locate2(frame, origin);
130
+ }
131
+ return fallback ? { location: { ...fallback, external: true }, external: true } : void 0;
132
+ }
133
+ function currentCallSite(origin) {
134
+ return callSite(new Error().stack, origin);
135
+ }
136
+
137
+ // src/instrument.ts
138
+ import {
139
+ createSignal,
140
+ createMemo,
141
+ createEffect,
142
+ createResource,
143
+ createRoot,
144
+ untrack,
145
+ onCleanup,
146
+ getOwner,
147
+ runWithOwner,
148
+ Signal
149
+ } from "@fluixi/reactive/signal";
150
+ import { createStore } from "@fluixi/reactive/store";
151
+
152
+ // src/format.ts
153
+ var MAX_DEPTH = 3;
154
+ var MAX_ENTRIES = 8;
155
+ function domName(v) {
156
+ if (typeof v?.nodeType !== "number" || typeof v.nodeName !== "string") return null;
157
+ return v.tagName ? `<${v.tagName.toLowerCase()}>` : `#${v.nodeName.toLowerCase()}`;
158
+ }
159
+ function fnName(v) {
160
+ return v.name ? `ƒ ${v.name}()` : "ƒ";
161
+ }
162
+ function className(v) {
163
+ const name = v.constructor?.name;
164
+ return name && name !== "Object" ? `${name} ` : "";
165
+ }
166
+ function primitive(v) {
167
+ if (v === null) return "null";
168
+ switch (typeof v) {
169
+ case "undefined":
170
+ return "undefined";
171
+ case "string":
172
+ return JSON.stringify(v);
173
+ case "number":
174
+ case "boolean":
175
+ return String(v);
176
+ case "bigint":
177
+ return `${v}n`;
178
+ case "symbol":
179
+ return v.toString();
180
+ case "function":
181
+ return fnName(v);
182
+ default:
183
+ return null;
184
+ }
185
+ }
186
+ function walk(value, depth, seen) {
187
+ const simple = primitive(value);
188
+ if (simple !== null) return simple;
189
+ const object = value;
190
+ if (seen.has(object)) return "[circular]";
191
+ const dom = domName(object);
192
+ if (dom) return dom;
193
+ if (object instanceof Date) return Number.isNaN(object.getTime()) ? "Invalid Date" : object.toISOString();
194
+ if (object instanceof RegExp) return String(object);
195
+ if (object instanceof Error) return `${object.name}: ${object.message}`;
196
+ if (object instanceof Promise) return "Promise";
197
+ if (ArrayBuffer.isView(object)) {
198
+ return `${className(object).trim()}(${object.length ?? ""})`;
199
+ }
200
+ if (depth >= MAX_DEPTH) return Array.isArray(object) ? "[…]" : "{…}";
201
+ seen.add(object);
202
+ try {
203
+ if (Array.isArray(object)) {
204
+ const shown2 = object.slice(0, MAX_ENTRIES).map((item) => walk(item, depth + 1, seen));
205
+ if (object.length > MAX_ENTRIES) shown2.push("…");
206
+ return `[${shown2.join(", ")}]`;
207
+ }
208
+ if (object instanceof Map) {
209
+ const shown2 = [];
210
+ for (const [k, v] of object) {
211
+ if (shown2.length === MAX_ENTRIES) {
212
+ shown2.push("…");
213
+ break;
214
+ }
215
+ shown2.push(`${walk(k, depth + 1, seen)} => ${walk(v, depth + 1, seen)}`);
216
+ }
217
+ return `Map(${object.size}) {${shown2.join(", ")}}`;
218
+ }
219
+ if (object instanceof Set) {
220
+ const shown2 = [];
221
+ for (const item of object) {
222
+ if (shown2.length === MAX_ENTRIES) {
223
+ shown2.push("…");
224
+ break;
225
+ }
226
+ shown2.push(walk(item, depth + 1, seen));
227
+ }
228
+ return `Set(${object.size}) {${shown2.join(", ")}}`;
229
+ }
230
+ const keys = Object.keys(object);
231
+ const shown = keys.slice(0, MAX_ENTRIES).map((key) => {
232
+ let read;
233
+ try {
234
+ read = walk(object[key], depth + 1, seen);
235
+ } catch {
236
+ read = "[unreadable]";
237
+ }
238
+ return `${key}: ${read}`;
239
+ });
240
+ if (keys.length > MAX_ENTRIES) shown.push("…");
241
+ return `${className(object)}{${shown.join(", ")}}`;
242
+ } finally {
243
+ seen.delete(object);
244
+ }
245
+ }
246
+ function formatValue(value, limit = 240) {
247
+ let text;
248
+ try {
249
+ text = walk(value, 0, /* @__PURE__ */ new Set());
250
+ } catch {
251
+ text = "[unreadable]";
252
+ }
253
+ return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
254
+ }
255
+ function shorten(text, limit = 18) {
256
+ return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
257
+ }
258
+
259
+ // src/instrument.ts
260
+ var KIND_OF_BUILTIN = {
261
+ Show: "control",
262
+ For: "control",
263
+ Index: "control",
264
+ Switch: "control",
265
+ Match: "control",
266
+ Portal: "control",
267
+ Dynamic: "control",
268
+ ErrorBoundary: "control",
269
+ Suspense: "control",
270
+ SuspenseList: "control",
271
+ Await: "control",
272
+ Router: "router",
273
+ Outlet: "router",
274
+ Redirect: "router",
275
+ Link: "router"
276
+ };
277
+
278
+ // src/history.ts
279
+ var OFF = {
280
+ wrote() {
281
+ },
282
+ ran() {
283
+ },
284
+ created() {
285
+ },
286
+ disposed() {
287
+ },
288
+ take: () => [],
289
+ dropped: 0,
290
+ clear() {
291
+ }
292
+ };
293
+ function createHistory(limit) {
294
+ if (limit <= 0) return OFF;
295
+ let events = [];
296
+ let seq = 0;
297
+ let commit = 0;
298
+ let dropped = 0;
299
+ const push = (event) => {
300
+ events.push({ ...event, seq: seq++, t: Date.now(), commit });
301
+ if (events.length > limit) {
302
+ dropped += events.length - limit;
303
+ events = events.slice(-limit);
304
+ }
305
+ };
306
+ return {
307
+ wrote(id, by, detail) {
308
+ if (by === void 0) commit++;
309
+ push({ kind: "write", id, by, ...detail });
310
+ },
311
+ ran(id, by) {
312
+ push({ kind: "ran", id, by });
313
+ },
314
+ created(id, by, label) {
315
+ push({ kind: "created", id, by, label });
316
+ },
317
+ disposed(id, label) {
318
+ push({ kind: "disposed", id, label });
319
+ },
320
+ take() {
321
+ const taken = events;
322
+ events = [];
323
+ return taken;
324
+ },
325
+ get dropped() {
326
+ return dropped;
327
+ },
328
+ clear() {
329
+ events = [];
330
+ dropped = 0;
331
+ }
332
+ };
333
+ }
334
+
335
+ // src/diff.ts
336
+ var MAX_ENTRIES2 = 24;
337
+ var ENTRY_VALUE = 120;
338
+ var ENTRY_JSON = 4e3;
339
+ var show = (value) => formatValue(value, ENTRY_VALUE);
340
+ function json(value) {
341
+ if (value === null || typeof value !== "object") return void 0;
342
+ try {
343
+ const text = JSON.stringify(value);
344
+ return text && text.length <= ENTRY_JSON ? text : void 0;
345
+ } catch {
346
+ return void 0;
347
+ }
348
+ }
349
+ var side = (value, key) => {
350
+ const text = json(value);
351
+ return { [key]: show(value), ...text ? { [`${key}Json`]: text } : {} };
352
+ };
353
+ var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
354
+ function same(a, b) {
355
+ if (Object.is(a, b)) return true;
356
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
357
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
358
+ const ka = Object.keys(a);
359
+ const kb = Object.keys(b);
360
+ if (ka.length !== kb.length) return false;
361
+ return ka.every((k) => Object.is(a[k], b[k]));
362
+ }
363
+ function diffArray(before, after) {
364
+ let head = 0;
365
+ while (head < before.length && head < after.length && same(before[head], after[head])) head++;
366
+ let tail = 0;
367
+ while (tail < before.length - head && tail < after.length - head && same(before[before.length - 1 - tail], after[after.length - 1 - tail])) {
368
+ tail++;
369
+ }
370
+ const entries = [];
371
+ let skipped = 0;
372
+ const push = (entry) => {
373
+ if (entries.length < MAX_ENTRIES2) entries.push(entry);
374
+ else skipped++;
375
+ };
376
+ const removedCount = before.length - head - tail;
377
+ const addedCount = after.length - head - tail;
378
+ const overlap = Math.min(removedCount, addedCount);
379
+ for (let i = 0; i < overlap; i++) {
380
+ push({
381
+ key: String(head + i),
382
+ op: "changed",
383
+ ...side(before[head + i], "from"),
384
+ ...side(after[head + i], "to")
385
+ });
386
+ }
387
+ for (let i = overlap; i < addedCount; i++) {
388
+ push({ key: String(head + i), op: "added", ...side(after[head + i], "to") });
389
+ }
390
+ for (let i = overlap; i < removedCount; i++) {
391
+ push({ key: String(head + i), op: "removed", ...side(before[head + i], "from") });
392
+ }
393
+ return {
394
+ shape: "array",
395
+ entries,
396
+ ...skipped ? { more: skipped } : {},
397
+ ...before.length !== after.length ? { length: { from: before.length, to: after.length } } : {}
398
+ };
399
+ }
400
+ function diffObject(before, after) {
401
+ const entries = [];
402
+ let skipped = 0;
403
+ const push = (entry) => {
404
+ if (entries.length < MAX_ENTRIES2) entries.push(entry);
405
+ else skipped++;
406
+ };
407
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])) {
408
+ const had = key in before;
409
+ const has = key in after;
410
+ if (had && has) {
411
+ if (!same(before[key], after[key])) {
412
+ push({ key, op: "changed", ...side(before[key], "from"), ...side(after[key], "to") });
413
+ }
414
+ } else if (has) push({ key, op: "added", ...side(after[key], "to") });
415
+ else push({ key, op: "removed", ...side(before[key], "from") });
416
+ }
417
+ return { shape: "object", entries, ...skipped ? { more: skipped } : {} };
418
+ }
419
+ function diffValues(before, after) {
420
+ if (Array.isArray(before) && Array.isArray(after)) {
421
+ const change = diffArray(before, after);
422
+ return change.entries?.length || change.length ? change : void 0;
423
+ }
424
+ if (isPlainObject(before) && isPlainObject(after)) {
425
+ const change = diffObject(before, after);
426
+ return change.entries?.length ? change : void 0;
427
+ }
428
+ return void 0;
429
+ }
430
+
431
+ // src/describe.ts
432
+ var MAX_CLASSES = 2;
433
+ function textOwner(node) {
434
+ const parent = node.parentElement;
435
+ return parent ? `text in ${describeElement(parent)}` : "text";
436
+ }
437
+ function describeElement(el) {
438
+ const tag = el.tagName.toLowerCase();
439
+ const id = el.id ? `#${el.id}` : "";
440
+ const raw = typeof el.className === "string" ? el.className : el.getAttribute("class") ?? "";
441
+ const classes = raw.split(/\s+/).filter(Boolean).slice(0, MAX_CLASSES).map((c) => `.${c}`).join("");
442
+ return `${tag}${id}${classes}`;
443
+ }
444
+ function describeNode(target) {
445
+ const node = target;
446
+ if (!node || typeof node.nodeType !== "number") return "detached";
447
+ switch (node.nodeType) {
448
+ case 1:
449
+ return describeElement(node);
450
+ case 3:
451
+ return textOwner(node);
452
+ case 8: {
453
+ const parent = node.parentElement;
454
+ return parent ? `slot in ${describeElement(parent)}` : "slot";
455
+ }
456
+ case 11:
457
+ return "fragment";
458
+ default:
459
+ return node.nodeName?.toLowerCase() ?? "node";
460
+ }
461
+ }
462
+
463
+ // src/dom.ts
464
+ var LOC_TAG = "__fx_loc";
465
+ function locationOf(element) {
466
+ return element[LOC_TAG];
467
+ }
468
+ var DOM_BUDGET = 200;
469
+ function describeElement2(element) {
470
+ let out = element.tagName.toLowerCase();
471
+ if (element.id) out += `#${element.id}`;
472
+ const classes = element.classList;
473
+ if (classes.length > 0) {
474
+ out += `.${classes[0]}`;
475
+ if (classes.length > 1) out += `.${classes[1]}`;
476
+ }
477
+ return out;
478
+ }
479
+ function describeAll(graph, roots, writtenBy, kinds, extraFor) {
480
+ const owners = /* @__PURE__ */ new Map();
481
+ const writers = /* @__PURE__ */ new Map();
482
+ const rendered = /* @__PURE__ */ new Map();
483
+ for (const node of graph.values()) {
484
+ if (kinds.has(node.kind)) {
485
+ const found = roots(node.id);
486
+ rendered.set(node.id, found);
487
+ for (const element of found) if (!owners.has(element)) owners.set(element, node.id);
488
+ continue;
489
+ }
490
+ for (const element of writtenBy(node.id)) {
491
+ const held = writers.get(element);
492
+ if (held) held.push(node.id);
493
+ else writers.set(element, [node.id]);
494
+ }
495
+ }
496
+ const { claimed, containers } = claimOrphans(graph, rendered, writtenBy, kinds, owners);
497
+ const claims = /* @__PURE__ */ new Map();
498
+ for (const [element, id] of claimed) {
499
+ const held = claims.get(id);
500
+ if (held) held.push(element);
501
+ else claims.set(id, [element]);
502
+ }
503
+ const landing = landings(graph, rendered, writtenBy, kinds, claimed, containers);
504
+ const out = /* @__PURE__ */ new Map();
505
+ for (const node of graph.values()) {
506
+ if (!kinds.has(node.kind)) continue;
507
+ const budget = { left: DOM_BUDGET };
508
+ const walk2 = (element) => {
509
+ const owner = owners.get(element) ?? claimed.get(element);
510
+ const where = locationOf(element);
511
+ if (owner !== void 0 && owner !== node.id) {
512
+ return { label: describeElement2(element), children: [], owner, element, ...where ? { at: where } : {} };
513
+ }
514
+ if (containers.has(element)) {
515
+ return { label: describeElement2(element), children: [], container: true, element, ...where ? { at: where } : {} };
516
+ }
517
+ const children = [];
518
+ const kids = element.children;
519
+ for (let i = 0, n = kids.length; i < n && budget.left > 0; i++) {
520
+ budget.left -= 1;
521
+ children.push(walk2(kids[i]));
522
+ }
523
+ const writes = writers.get(element);
524
+ const extra = extraFor?.(element);
525
+ const inside = landing.get(element)?.filter((id) => within(graph, id, node.id));
526
+ return {
527
+ label: describeElement2(element),
528
+ children,
529
+ element,
530
+ ...where ? { at: where } : {},
531
+ ...writes?.length ? { writes } : {},
532
+ ...inside?.length ? { inside } : {},
533
+ ...extra ? { extra } : {}
534
+ };
535
+ };
536
+ out.set(node.id, [...rendered.get(node.id) ?? [], ...claims.get(node.id) ?? []].map(walk2));
537
+ }
538
+ return out;
539
+ }
540
+ function within(graph, id, ancestor) {
541
+ const seen = /* @__PURE__ */ new Set();
542
+ for (let at = graph.get(id)?.createdIn; at !== void 0 && !seen.has(at); at = graph.get(at)?.createdIn) {
543
+ if (at === ancestor) return true;
544
+ seen.add(at);
545
+ }
546
+ return false;
547
+ }
548
+ function landings(graph, rendered, writtenBy, kinds, claimed, containers) {
549
+ const claimers = new Set(claimed.values());
550
+ const landing = /* @__PURE__ */ new Map();
551
+ for (const node of graph.values()) {
552
+ if (!kinds.has(node.kind) || (rendered.get(node.id) ?? []).length) continue;
553
+ if (claimers.has(node.id)) continue;
554
+ let shallowest;
555
+ let best = Infinity;
556
+ for (const element of writtenBy(node.id)) {
557
+ if (containers.has(element)) continue;
558
+ const depth = depthInDocument(element);
559
+ if (depth < best) {
560
+ best = depth;
561
+ shallowest = element;
562
+ }
563
+ }
564
+ if (!shallowest) continue;
565
+ const held = landing.get(shallowest);
566
+ if (held) held.push(node.id);
567
+ else landing.set(shallowest, [node.id]);
568
+ }
569
+ for (const [element, ids] of landing) {
570
+ landing.set(element, ids.filter((id) => !ids.some((other) => other !== id && within(graph, id, other))));
571
+ }
572
+ return landing;
573
+ }
574
+ function tagDepths(graph, kinds) {
575
+ const depth = /* @__PURE__ */ new Map();
576
+ const of = (id, seen) => {
577
+ const held = depth.get(id);
578
+ if (held !== void 0) return held;
579
+ if (seen.has(id)) return 0;
580
+ seen.add(id);
581
+ const parent = graph.get(id)?.createdIn;
582
+ const value = parent === void 0 ? 0 : of(parent, seen) + 1;
583
+ depth.set(id, value);
584
+ return value;
585
+ };
586
+ for (const node of graph.values()) if (kinds.has(node.kind)) of(node.id, /* @__PURE__ */ new Set());
587
+ return depth;
588
+ }
589
+ function depthInDocument(element) {
590
+ let steps = 0;
591
+ for (let at = element.parentElement; at; at = at.parentElement) steps++;
592
+ return steps;
593
+ }
594
+ function claimOrphans(graph, rendered, writtenBy, kinds, owners) {
595
+ const depth = tagDepths(graph, kinds);
596
+ const wraps = (element) => {
597
+ for (const owned of owners.keys()) if (element !== owned && element.contains(owned)) return true;
598
+ return false;
599
+ };
600
+ const writing = /* @__PURE__ */ new Map();
601
+ for (const node of graph.values()) {
602
+ if (!kinds.has(node.kind) || (rendered.get(node.id) ?? []).length) continue;
603
+ for (const element of writtenBy(node.id)) {
604
+ if (owners.has(element)) continue;
605
+ const held = writing.get(element);
606
+ if (held) held.push(node.id);
607
+ else writing.set(element, [node.id]);
608
+ }
609
+ }
610
+ const claimed = /* @__PURE__ */ new Map();
611
+ const containers = /* @__PURE__ */ new Set();
612
+ const covered = (element) => {
613
+ for (let at = element.parentElement; at; at = at.parentElement) {
614
+ if (owners.has(at) || claimed.has(at)) return true;
615
+ }
616
+ return false;
617
+ };
618
+ const order = [...writing.keys()].sort((a, b) => depthInDocument(a) - depthInDocument(b));
619
+ for (const element of order) {
620
+ if (covered(element)) continue;
621
+ const ids = writing.get(element);
622
+ const container = wraps(element);
623
+ const pick = ids.reduce((best, id) => {
624
+ const a = depth.get(id) ?? 0;
625
+ const b = depth.get(best) ?? 0;
626
+ return container ? a < b ? id : best : a > b ? id : best;
627
+ });
628
+ claimed.set(element, pick);
629
+ if (container) containers.add(element);
630
+ }
631
+ return { claimed, containers };
632
+ }
633
+
634
+ // src/plugin.ts
635
+ var plugins = [];
636
+ function devtoolsPlugins() {
637
+ return plugins;
638
+ }
639
+
640
+ // src/palette.ts
641
+ var KIND_COLOR = {
642
+ state: "#34d399",
643
+ memo: "#a78bfa",
644
+ effect: "#fbbf24",
645
+ store: "#38bdf8",
646
+ resource: "#fb7185",
647
+ component: "#f0abfc",
648
+ provider: "#fb923c",
649
+ control: "#22d3ee",
650
+ router: "#818cf8",
651
+ handler: "#f472b6",
652
+ context: "#a3e635",
653
+ directive: "#5eead4",
654
+ binding: "#5eead4",
655
+ setter: "#fb7185",
656
+ group: "#8a90a2",
657
+ element: "#8a90a2"
658
+ };
659
+ var kindVar = (kind) => `--fx-kind-${kind}`;
660
+ var kindColor = (kind) => `var(${kindVar(kind)}, ${KIND_COLOR[kind]})`;
661
+ var KINDS = Object.keys(KIND_COLOR);
662
+ function kindRules(selector, property) {
663
+ return KINDS.map((kind) => `${selector(kind)} { ${property}: ${kindColor(kind)}; }`).join("\n");
664
+ }
665
+
666
+ // src/tree.ts
667
+ var TREE_KINDS = /* @__PURE__ */ new Set([
668
+ "component",
669
+ "control",
670
+ "router",
671
+ "handler",
672
+ // A component a plugin recognised as providing a context. Still a tag the author wrote,
673
+ // so leaving it out took it and everything it rendered out of the tree.
674
+ "provider"
675
+ ]);
676
+ var TREE_CSS = `
677
+ .fx-tree-row {
678
+ display: flex; align-items: center; gap: 4px;
679
+ padding: 1px 6px 1px calc(6px + var(--depth, 0) * 12px);
680
+ font-family: ui-monospace, monospace; font-size: 11.5px;
681
+ color: var(--fx-label, #e7e9ee); cursor: pointer; white-space: nowrap;
682
+ }
683
+ .fx-tree-row:hover { background: var(--fx-hover, #1b1e26); }
684
+ .fx-tree-row.on { background: var(--fx-sel, #232838); }
685
+ .fx-tree-twist {
686
+ width: 12px; padding: 0; border: 0; background: none; cursor: pointer;
687
+ color: var(--fx-muted, #8a90a2); font-size: 9px; line-height: 1;
688
+ }
689
+ .fx-tree-plugin {
690
+ font-size: 9px; color: var(--fx-accent, #5eead4);
691
+ border: 1px solid var(--fx-line, #262a35); border-radius: 6px; padding: 0 4px;
692
+ max-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
693
+ }
694
+ .fx-tree-lazy {
695
+ font-size: 9px; letter-spacing: .04em; color: var(--fx-router, #7dd3fc);
696
+ border: 1px solid var(--fx-line, #262a35); border-radius: 6px; padding: 0 4px;
697
+ }
698
+ .fx-tree-count {
699
+ font-size: 9.5px; color: var(--fx-muted, #8a90a2);
700
+ border: 1px solid var(--fx-line, #262a35); border-radius: 6px; padding: 0 4px;
701
+ }
702
+ ${kindRules((k) => `.fx-tree-row.n-${k} .fx-tree-tag`, "color")}
703
+ .fx-tree-bar {
704
+ display: flex; align-items: center; gap: 5px; padding: 2px 8px 5px;
705
+ font-size: 10.5px; color: var(--fx-muted, #8a90a2); cursor: pointer; user-select: none;
706
+ }
707
+ .fx-tree-bar:hover { color: var(--fx-label, #e7e9ee); }
708
+ .fx-tree-bar input { width: 11px; height: 11px; margin: 0; accent-color: var(--fx-accent, #5eead4); }
709
+ .fx-tree-row.n-element:hover .fx-tree-tag { color: var(--fx-label, #e7e9ee); }
710
+ /* A declared value is listed inside the tag that made it, so it reads quieter than a tag. */
711
+ .fx-tree-row.is-value .fx-tree-tag { font-style: italic; opacity: .85; }
712
+ .fx-tree-search {
713
+ width: calc(100% - 16px); margin: 0 8px 4px; padding: 2px 6px;
714
+ font: inherit; font-size: 10.5px;
715
+ color: var(--fx-label, #e7e9ee); background: var(--fx-bg, #0f1115);
716
+ border: 1px solid var(--fx-line, #262a35); border-radius: 4px;
717
+ }
718
+ .fx-tree-search:focus { outline: none; border-color: var(--fx-accent, #5eead4); }
719
+ .fx-tree-empty { display: block; padding: 8px; font-size: 11px; color: var(--fx-muted, #8a90a2); }
720
+ /* A prop reads as what was written on the tag, so it is quieter than the tag itself. */
721
+ /* The row that folds a tag's props away. Dashed, because it stands for something written
722
+ rather than something rendered. */
723
+ .fx-tree-row.is-props .fx-tree-tag {
724
+ display: inline-flex; align-items: center; gap: 5px;
725
+ padding: 0 5px; border: 1px dashed var(--fx-line, #262a35); border-radius: 3px;
726
+ color: var(--fx-muted, #8a90a2); font-size: 10px; letter-spacing: .04em; text-transform: uppercase;
727
+ }
728
+ .fx-tree-row.is-props:hover .fx-tree-tag { border-color: var(--fx-accent, #5eead4); color: var(--fx-label, #e7e9ee); }
729
+ .fx-tree-props-count { opacity: .7; }
730
+
731
+ /* A prop is what was written on the tag above it, so it reads as an aside: the name in a
732
+ quiet chip, the value beside it, and no marker pretending it opens. */
733
+ .fx-tree-row.is-prop .fx-tree-tag { display: inline-flex; align-items: baseline; gap: 5px; min-width: 0; }
734
+ .fx-tree-prop-name {
735
+ flex: none; padding: 0 4px; border-radius: 3px;
736
+ background: var(--fx-chip, rgba(138, 144, 162, .14));
737
+ color: var(--fx-muted, #8a90a2); font-size: 10px; letter-spacing: .02em;
738
+ }
739
+ .fx-tree-row.is-prop:hover .fx-tree-prop-name { color: var(--fx-label, #e7e9ee); }
740
+ .fx-tree-prop-value {
741
+ min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
742
+ color: var(--fx-label, #e7e9ee); opacity: .75;
743
+ }
744
+ `;
745
+
746
+ // src/observe.ts
747
+ function stampDocumentOrder(graph, rendered) {
748
+ const roots = [];
749
+ for (const node of graph.values()) {
750
+ if (!TREE_KINDS.has(node.kind)) continue;
751
+ const element = rendered(node.id)[0];
752
+ if (element?.isConnected) roots.push({ id: node.id, element });
753
+ }
754
+ roots.sort((a, b) => a.element.compareDocumentPosition(b.element) & 4 ? -1 : 1);
755
+ for (const node of graph.values()) node.order = void 0;
756
+ roots.forEach(({ id }, at) => {
757
+ const node = graph.get(id);
758
+ if (node) node.order = at;
759
+ });
760
+ }
761
+ var REMOVED_LOG = 500;
762
+ var KINDS2 = {
763
+ signal: "state",
764
+ memo: "memo",
765
+ effect: "effect",
766
+ store: "store",
767
+ resource: "resource"
768
+ };
769
+ var HISTORY_VALUE = 80;
770
+ var SET_SITES = 8;
771
+ var PREFIX = {
772
+ state: "s",
773
+ memo: "m",
774
+ effect: "e",
775
+ store: "st",
776
+ resource: "r",
777
+ component: "c",
778
+ // Only the static graph makes these: at runtime control flow is a named memo, and a route
779
+ // handler never reaches a browser.
780
+ control: "x",
781
+ router: "rt",
782
+ handler: "h",
783
+ // Never produced here: these are cards a view draws, not nodes anything creates.
784
+ group: "g",
785
+ binding: "b",
786
+ setter: "w",
787
+ // A plugin's, and it names them itself.
788
+ directive: "d",
789
+ context: "ctx",
790
+ provider: "p"
791
+ };
792
+ function readProps(props) {
793
+ const at = propsLocations(props);
794
+ const out = [];
795
+ for (const name of Object.keys(props)) {
796
+ if (name === "children") continue;
797
+ const where = at?.[name];
798
+ const report = {
799
+ name,
800
+ ...where ? { at: { line: where[0], column: where[1] } } : {}
801
+ };
802
+ try {
803
+ report.value = formatValue(untrack2(() => props[name]), 200);
804
+ } catch {
805
+ }
806
+ out.push(report);
807
+ }
808
+ return out.length ? out : void 0;
809
+ }
810
+ function observeGraph(options = {}) {
811
+ installSourceMarker();
812
+ const origin = options.origin ?? (typeof location === "object" ? location.origin : void 0);
813
+ const graph = /* @__PURE__ */ new Map();
814
+ const claims = (node, value) => {
815
+ for (const dep of node.deps) {
816
+ const other = graph.get(dep);
817
+ if (other?.read && other.read() === value) return false;
818
+ }
819
+ return true;
820
+ };
821
+ const ids = /* @__PURE__ */ new WeakMap();
822
+ const through = /* @__PURE__ */ new WeakMap();
823
+ const internals = /* @__PURE__ */ new Set();
824
+ const targets = /* @__PURE__ */ new WeakMap();
825
+ const handed = /* @__PURE__ */ new Map();
826
+ const bindingSites = /* @__PURE__ */ new WeakMap();
827
+ const boundTo = /* @__PURE__ */ new WeakMap();
828
+ const drivenBy = /* @__PURE__ */ new WeakMap();
829
+ let reaches = /* @__PURE__ */ new Map();
830
+ let described = null;
831
+ const rendered = /* @__PURE__ */ new Map();
832
+ const elementsIn = (value) => {
833
+ if (value instanceof Element) return [value];
834
+ if (value instanceof DocumentFragment) return [...value.children];
835
+ if (Array.isArray(value)) return value.flatMap(elementsIn);
836
+ return [];
837
+ };
838
+ const cores = /* @__PURE__ */ new Map();
839
+ const setters = /* @__PURE__ */ new Map();
840
+ const runsAtLastSample = /* @__PURE__ */ new Map();
841
+ const unnamed = /* @__PURE__ */ new Set();
842
+ const recording = (options.history ?? 200) > 0;
843
+ const wantSites = options.bindingSources ?? true;
844
+ const mapped = (location2) => {
845
+ if (!options.mapSource) return location2;
846
+ try {
847
+ const found = options.mapSource(location2);
848
+ return found ? { ...location2, ...found, compiled: { line: location2.line, column: location2.column } } : location2;
849
+ } catch {
850
+ return location2;
851
+ }
852
+ };
853
+ const history = createHistory(options.history ?? 200);
854
+ let seq = 0;
855
+ const raws = /* @__PURE__ */ new Map();
856
+ const readRaw = (node) => {
857
+ if (!node.read) return void 0;
858
+ try {
859
+ return untrack2(() => node.read());
860
+ } catch {
861
+ return void 0;
862
+ }
863
+ };
864
+ const removed = [];
865
+ let revision = 0;
866
+ const revs = /* @__PURE__ */ new Map();
867
+ let drained = 0;
868
+ const onDirective = [];
869
+ const onContext = [];
870
+ const retyped = /* @__PURE__ */ new Map();
871
+ const contextSites = /* @__PURE__ */ new Map();
872
+ const elementExtra = /* @__PURE__ */ new WeakMap();
873
+ const ancestor = (parents) => {
874
+ if (!parents) return void 0;
875
+ for (let i = parents.length - 1; i >= 0; i--) {
876
+ const key = parents[i];
877
+ const found = ids.get(key) ?? through.get(key);
878
+ if (found !== void 0) return found;
879
+ }
880
+ return void 0;
881
+ };
882
+ const stop = (options.observe ?? observeReactiveNodes)({
883
+ created(report) {
884
+ const core = report.node;
885
+ if (!core || typeof core !== "object") return;
886
+ if (report.internal) {
887
+ const owner2 = ancestor(report.parents);
888
+ if (owner2 !== void 0) through.set(core, owner2);
889
+ internals.add(core);
890
+ if (wantSites) {
891
+ const site2 = currentCallSite(origin);
892
+ if (site2) bindingSites.set(core, mapped(site2.location));
893
+ }
894
+ return;
895
+ }
896
+ const id = seq++;
897
+ const handle = report.handle;
898
+ const mark = takeReactiveSource();
899
+ const base = KINDS2[report.kind];
900
+ const marked = base === "memo" && mark?.name ? KIND_OF_BUILTIN[mark.name] : void 0;
901
+ const kind = (report.name ? KIND_OF_BUILTIN[report.name] : void 0) ?? marked ?? base;
902
+ const named = report.name ?? mark?.name;
903
+ if (!named) unnamed.add(id);
904
+ const site = mark?.location ? void 0 : currentCallSite(origin);
905
+ const node = {
906
+ id,
907
+ label: named ?? `${PREFIX[kind]}${id}`,
908
+ kind,
909
+ // Only a compiler mark means the author wrote this and gave it that name. A name
910
+ // the runtime supplies, such as `Show.when`, `mapArray` or `row index`, describes framework
911
+ // machinery, and so does no name at all.
912
+ authored: mark?.name != null,
913
+ deps: /* @__PURE__ */ new Set(),
914
+ // A write edge needs to know which consumer was running when a setter fired, and
915
+ // nothing observed records that. Reads carry the graph; writes do not.
916
+ writes: /* @__PURE__ */ new Set(),
917
+ runs: 0,
918
+ lastPulse: 0,
919
+ core,
920
+ // The application's line is where it was asked for; a package's is where it was made,
921
+ // and for a node made while that package loaded it is the only thing there is.
922
+ ...mark?.location ? { source: mark.location } : {},
923
+ // Mapped, unlike `mark`: these two came off a stack, so they are positions in the
924
+ // compiled file until a host translates them.
925
+ ...site && !site.external ? { usedAt: mapped(site.location) } : {},
926
+ ...site && site.external && !mark?.location ? { source: mapped(site.location) } : {}
927
+ };
928
+ const owner = ancestor(report.parents);
929
+ if (owner !== void 0) node.createdIn = owner;
930
+ const originId = report.origin ? ids.get(report.origin) : void 0;
931
+ const from = originId !== void 0 ? graph.get(originId) : void 0;
932
+ if (from) {
933
+ node.createdIn = originId;
934
+ if (from.source) node.source = from.source;
935
+ }
936
+ if (kind !== "effect" && typeof handle === "function") {
937
+ const read = handle;
938
+ node.read = () => untrack2(read);
939
+ }
940
+ ids.set(core, id);
941
+ if (handle && (typeof handle === "object" || typeof handle === "function")) {
942
+ ids.set(handle, id);
943
+ }
944
+ cores.set(id, core);
945
+ if (typeof report.set === "function") setters.set(id, report.set);
946
+ graph.set(id, node);
947
+ history.created(id, node.createdIn, node.label);
948
+ },
949
+ ran(target) {
950
+ const id = ids.get(target);
951
+ if (id !== void 0) {
952
+ const node = graph.get(id);
953
+ if (node) node.runs++;
954
+ history.ran(id);
955
+ }
956
+ },
957
+ wrote(target, by) {
958
+ const writer = by ? ids.get(by) : void 0;
959
+ const written = ids.get(target);
960
+ if (written === void 0) return;
961
+ if (wantSites && writer === void 0) {
962
+ const node = graph.get(written);
963
+ const site = currentCallSite(origin);
964
+ if (node && site) {
965
+ const seen = node.setAt ??= [];
966
+ const at = mapped(site.location);
967
+ if (!seen.some((s) => s.file === at.file && s.line === at.line && s.column === at.column)) {
968
+ if (seen.length >= SET_SITES) seen.shift();
969
+ seen.push(at);
970
+ }
971
+ }
972
+ }
973
+ if (recording) {
974
+ const node = graph.get(written);
975
+ const after = node ? readRaw(node) : void 0;
976
+ const before = raws.get(written);
977
+ if (node) raws.set(written, after);
978
+ const shown = node?.detail ?? node?.value;
979
+ history.wrote(written, writer, {
980
+ from: shown === void 0 ? void 0 : shorten(shown, HISTORY_VALUE),
981
+ to: node ? shorten(formatValue(after, HISTORY_VALUE * 2), HISTORY_VALUE) : void 0,
982
+ // Which row was added is the fact a reader is after.
983
+ change: diffValues(before, after),
984
+ label: node?.label
985
+ });
986
+ }
987
+ if (writer === void 0 || writer === written) return;
988
+ graph.get(writer)?.writes.add(written);
989
+ },
990
+ directive(node, target, kind, name, accessor) {
991
+ if (!onDirective.length) return;
992
+ const core = node;
993
+ const owner = core && typeof core === "object" ? ids.get(core) : void 0;
994
+ const at = holeLocation(accessor);
995
+ const report = {
996
+ owner: owner !== void 0 ? through.get(core) ?? owner : void 0,
997
+ kind,
998
+ name,
999
+ element: describeNode(target),
1000
+ target,
1001
+ ...at ? { at } : {}
1002
+ };
1003
+ for (const fn of onDirective) {
1004
+ try {
1005
+ fn(report);
1006
+ } catch {
1007
+ }
1008
+ }
1009
+ },
1010
+ contextCreated(id) {
1011
+ if (!onContext.length || contextSites.has(id)) return;
1012
+ const site = currentCallSite(origin);
1013
+ if (site) contextSites.set(id, mapped(site.location));
1014
+ },
1015
+ context(node, id, kind, value) {
1016
+ if (!onContext.length) return;
1017
+ const core = node;
1018
+ const owner = core && typeof core === "object" ? ids.get(core) : void 0;
1019
+ const report = {
1020
+ owner: owner !== void 0 ? through.get(core) ?? owner : void 0,
1021
+ id,
1022
+ name: id.description || "context",
1023
+ ...owner !== void 0 ? { ownerLabel: graph.get(owner)?.label } : {},
1024
+ // Where the context itself was written. The provider's line is a fallback, and it
1025
+ // reads as a lie when two providers share one context.
1026
+ ...contextSites.get(id) ?? graph.get(owner ?? -1)?.source ? { at: contextSites.get(id) ?? graph.get(owner ?? -1)?.source } : {},
1027
+ kind,
1028
+ ...value === void 0 ? {} : { value: formatValue(value) }
1029
+ };
1030
+ for (const fn of onContext) {
1031
+ try {
1032
+ fn(report);
1033
+ } catch {
1034
+ }
1035
+ }
1036
+ },
1037
+ props(node, props) {
1038
+ const core = node;
1039
+ if (!core || typeof core !== "object" || !props) return;
1040
+ const id = ids.get(core);
1041
+ if (id !== void 0) handed.set(id, props);
1042
+ },
1043
+ bound(node, target, kind, name, accessor) {
1044
+ const core = node;
1045
+ if (!core || typeof core !== "object") return;
1046
+ const hole = holeLocation(accessor);
1047
+ const site = hole ? { ...hole, file: bindingSites.get(core)?.file } : bindingSites.get(core);
1048
+ const box = target instanceof Element ? target : target?.parentElement ?? void 0;
1049
+ if (box) boundTo.set(core, new WeakRef(box));
1050
+ targets.set(core, {
1051
+ kind,
1052
+ element: describeNode(target),
1053
+ ...name ? { name } : {},
1054
+ ...site ? { source: site } : {}
1055
+ });
1056
+ },
1057
+ moved(target, origin2, name) {
1058
+ const node = graph.get(ids.get(target) ?? -1);
1059
+ if (!node) return;
1060
+ const owner = ids.get(origin2);
1061
+ if (owner !== void 0 && graph.has(owner)) node.createdIn = owner;
1062
+ if (name) {
1063
+ node.label = name;
1064
+ unnamed.delete(node.id);
1065
+ }
1066
+ },
1067
+ disposed(target) {
1068
+ internals.delete(target);
1069
+ const id = ids.get(target);
1070
+ if (id === void 0) return;
1071
+ removed.push({ id, rev: ++revision });
1072
+ revs.delete(id);
1073
+ if (removed.length > REMOVED_LOG) removed.splice(0, removed.length - REMOVED_LOG);
1074
+ history.disposed(id, graph.get(id)?.label);
1075
+ graph.delete(id);
1076
+ handed.delete(id);
1077
+ raws.delete(id);
1078
+ cores.delete(id);
1079
+ rendered.delete(id);
1080
+ setters.delete(id);
1081
+ runsAtLastSample.delete(id);
1082
+ unnamed.delete(id);
1083
+ }
1084
+ });
1085
+ const edges = (id, node) => {
1086
+ const sources = cores.get(id)?.sources;
1087
+ node.deps.clear();
1088
+ if (!sources) return;
1089
+ for (const source of sources) {
1090
+ const dep = ids.get(source);
1091
+ if (dep !== void 0 && dep !== id) node.deps.add(dep);
1092
+ }
1093
+ };
1094
+ const api = {
1095
+ graph,
1096
+ sample() {
1097
+ described = null;
1098
+ const changed = [];
1099
+ const moved = (id) => {
1100
+ changed.push(id);
1101
+ revs.set(id, ++revision);
1102
+ };
1103
+ const now = Date.now();
1104
+ const bindings = /* @__PURE__ */ new Map();
1105
+ const drives = /* @__PURE__ */ new Map();
1106
+ const seen = /* @__PURE__ */ new Map();
1107
+ for (const internal of internals) {
1108
+ const target = targets.get(internal);
1109
+ const element = boundTo.get(internal);
1110
+ for (const source of internal.sources ?? []) {
1111
+ const id = ids.get(source);
1112
+ if (id === void 0) continue;
1113
+ bindings.set(id, (bindings.get(id) ?? 0) + 1);
1114
+ if (element) {
1115
+ const found = element.deref();
1116
+ if (found && !drivenBy.has(found)) drivenBy.set(found, id);
1117
+ const held = seen.get(id);
1118
+ if (held) held.push(element);
1119
+ else seen.set(id, [element]);
1120
+ }
1121
+ if (!target) continue;
1122
+ const list = drives.get(id);
1123
+ if (list) list.push(target);
1124
+ else drives.set(id, [target]);
1125
+ }
1126
+ }
1127
+ reaches = seen;
1128
+ for (const node of graph.values()) {
1129
+ const count = bindings.get(node.id) ?? 0;
1130
+ if (node.bindings !== count) node.bindings = count || void 0;
1131
+ const list = drives.get(node.id);
1132
+ node.targets = list?.length ? list : void 0;
1133
+ }
1134
+ for (const node of graph.values()) {
1135
+ if (!node.read) {
1136
+ edges(node.id, node);
1137
+ const seen2 = runsAtLastSample.get(node.id);
1138
+ if (seen2 === void 0 || node.runs > seen2) {
1139
+ node.lastPulse = now;
1140
+ moved(node.id);
1141
+ }
1142
+ runsAtLastSample.set(node.id, node.runs);
1143
+ continue;
1144
+ }
1145
+ const raw = node.read();
1146
+ if (recording) raws.set(node.id, raw);
1147
+ edges(node.id, node);
1148
+ const stamped = raw?.$name;
1149
+ if (node.kind === "memo" && typeof stamped === "string" && stamped && claims(node, raw)) {
1150
+ node.kind = KIND_OF_BUILTIN[stamped] ?? "component";
1151
+ node.declaredAt = componentSource(stamped);
1152
+ node.source ??= node.declaredAt;
1153
+ if (raw.$lazy) node.lazy = true;
1154
+ if (unnamed.has(node.id)) {
1155
+ node.label = stamped;
1156
+ unnamed.delete(node.id);
1157
+ }
1158
+ }
1159
+ if (TREE_KINDS.has(node.kind)) {
1160
+ const found = elementsIn(raw);
1161
+ if (found.length) rendered.set(node.id, found.map((el) => new WeakRef(el)));
1162
+ const given = handed.get(node.id);
1163
+ if (given) node.props = readProps(given);
1164
+ }
1165
+ const detail = formatValue(raw);
1166
+ const next = shorten(detail);
1167
+ if (next !== node.value || detail !== node.detail) {
1168
+ node.detail = detail;
1169
+ node.value = next;
1170
+ if (node.kind === "state") node.runs++;
1171
+ node.lastPulse = now;
1172
+ moved(node.id);
1173
+ }
1174
+ runsAtLastSample.set(node.id, node.runs);
1175
+ }
1176
+ for (const [id, kind] of retyped) {
1177
+ const node = graph.get(id);
1178
+ if (node?.kind === "component" && TREE_KINDS.has(kind)) node.kind = kind;
1179
+ }
1180
+ const unnumbered = () => {
1181
+ for (const node of graph.values()) {
1182
+ if (TREE_KINDS.has(node.kind) && node.order === void 0) return true;
1183
+ }
1184
+ return false;
1185
+ };
1186
+ if (changed.length || unnumbered()) stampDocumentOrder(graph, (of) => api.renderedFor(of));
1187
+ return changed;
1188
+ },
1189
+ get revision() {
1190
+ return revision;
1191
+ },
1192
+ takeRemoved() {
1193
+ const out = removed.filter((entry) => entry.rev > drained).map((entry) => entry.id);
1194
+ drained = revision;
1195
+ return out;
1196
+ },
1197
+ changedSince(rev) {
1198
+ const out = [];
1199
+ for (const [id, at] of revs) if (at > rev) out.push(id);
1200
+ return out;
1201
+ },
1202
+ removedSince(rev) {
1203
+ return removed.filter((entry) => entry.rev > rev).map((entry) => entry.id);
1204
+ },
1205
+ outrun(rev) {
1206
+ return removed.length >= REMOVED_LOG && rev < removed[0].rev;
1207
+ },
1208
+ elementsFor(id) {
1209
+ const live = [];
1210
+ const collect = (from, where = reaches) => {
1211
+ for (const ref of where.get(from) ?? []) {
1212
+ const found = ref.deref();
1213
+ if (found?.isConnected && !live.includes(found)) live.push(found);
1214
+ }
1215
+ };
1216
+ collect(id, rendered);
1217
+ if (live.length) return live;
1218
+ collect(id);
1219
+ if (!live.length) {
1220
+ const inside = (node) => {
1221
+ const seen = /* @__PURE__ */ new Set();
1222
+ let at = node.createdIn;
1223
+ while (at !== void 0 && !seen.has(at)) {
1224
+ if (at === id) return true;
1225
+ seen.add(at);
1226
+ at = graph.get(at)?.createdIn;
1227
+ }
1228
+ return false;
1229
+ };
1230
+ for (const node of graph.values()) if (inside(node)) collect(node.id);
1231
+ }
1232
+ return live;
1233
+ },
1234
+ setValue(id, next) {
1235
+ const node = graph.get(id);
1236
+ const write = setters.get(id);
1237
+ if (!node || node.kind !== "state" || !write) return false;
1238
+ write(next);
1239
+ return true;
1240
+ },
1241
+ renderedFor(id) {
1242
+ const live = [];
1243
+ for (const ref of rendered.get(id) ?? []) {
1244
+ const found = ref.deref();
1245
+ if (found?.isConnected && !live.includes(found)) live.push(found);
1246
+ }
1247
+ return live;
1248
+ },
1249
+ domFor(id) {
1250
+ described ??= describeAll(
1251
+ graph,
1252
+ (of) => api.renderedFor(of),
1253
+ (of) => api.elementsFor(of),
1254
+ TREE_KINDS,
1255
+ (element) => elementExtra.get(element)
1256
+ );
1257
+ return described.get(id) ?? [];
1258
+ },
1259
+ nodeAt(element) {
1260
+ const depth = tagDepths(graph, TREE_KINDS);
1261
+ const rendering = /* @__PURE__ */ new Map();
1262
+ for (const node of graph.values()) {
1263
+ if (!TREE_KINDS.has(node.kind)) continue;
1264
+ for (const found of api.renderedFor(node.id)) {
1265
+ const held = rendering.get(found);
1266
+ if (held === void 0 || (depth.get(node.id) ?? 0) > (depth.get(held) ?? 0)) {
1267
+ rendering.set(found, node.id);
1268
+ }
1269
+ }
1270
+ }
1271
+ for (let at = element; at; at = at.parentElement) {
1272
+ const owner = rendering.get(at);
1273
+ if (owner !== void 0 && graph.has(owner)) return owner;
1274
+ }
1275
+ for (let at = element; at; at = at.parentElement) {
1276
+ const id = drivenBy.get(at);
1277
+ if (id !== void 0 && graph.has(id)) return id;
1278
+ }
1279
+ return void 0;
1280
+ },
1281
+ takeEvents() {
1282
+ return history.take();
1283
+ },
1284
+ detach() {
1285
+ stop();
1286
+ internals.clear();
1287
+ graph.clear();
1288
+ cores.clear();
1289
+ rendered.clear();
1290
+ setters.clear();
1291
+ runsAtLastSample.clear();
1292
+ unnamed.clear();
1293
+ raws.clear();
1294
+ history.clear();
1295
+ }
1296
+ };
1297
+ for (const plugin of devtoolsPlugins()) {
1298
+ if (!plugin.collect) continue;
1299
+ const context = {
1300
+ onDirective: (fn) => onDirective.push(fn),
1301
+ onContext: (fn) => onContext.push(fn),
1302
+ annotate: (id, data) => {
1303
+ const node = graph.get(id);
1304
+ if (node) (node.extra ??= {})[plugin.name] = data;
1305
+ },
1306
+ annotation: (id) => graph.get(id)?.extra?.[plugin.name],
1307
+ // Remembered, not applied here. A component is still a memo when its context is
1308
+ // provided, and the promotion that gives it its name only runs while it is one.
1309
+ retype: (id, kind) => retyped.set(id, kind),
1310
+ annotateElement: (target, data) => {
1311
+ if (!target || typeof target !== "object") return;
1312
+ const held = elementExtra.get(target) ?? {};
1313
+ held[plugin.name] = data;
1314
+ elementExtra.set(target, held);
1315
+ },
1316
+ elementAnnotation: (target) => target && typeof target === "object" ? elementExtra.get(target)?.[plugin.name] : void 0,
1317
+ add: (spec) => {
1318
+ const id = seq++;
1319
+ graph.set(id, {
1320
+ id,
1321
+ label: spec.label,
1322
+ kind: spec.kind,
1323
+ deps: new Set(spec.deps ?? []),
1324
+ writes: /* @__PURE__ */ new Set(),
1325
+ runs: 0,
1326
+ lastPulse: 0,
1327
+ authored: spec.authored ?? false,
1328
+ ...spec.createdIn !== void 0 ? { createdIn: spec.createdIn } : {},
1329
+ ...spec.value !== void 0 ? { value: spec.value } : {},
1330
+ ...spec.detail !== void 0 ? { detail: spec.detail } : {},
1331
+ ...spec.source ? { source: spec.source } : {}
1332
+ });
1333
+ return id;
1334
+ }
1335
+ };
1336
+ try {
1337
+ plugin.collect(context);
1338
+ } catch {
1339
+ }
1340
+ }
1341
+ return api;
1342
+ }
1343
+ export {
1344
+ observeGraph
1345
+ };