@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,90 @@
1
+ // src/source.ts
2
+ var MARKER = "__fx_source";
3
+ var COMPONENT_MARKER = "__fx_component";
4
+ var HOLE_MARKER = "__fx_hole";
5
+ var PROPS_MARKER = "__fx_props";
6
+ var MARKS = "__FLUIXI_DEVTOOLS_MARKS__";
7
+ var host = globalThis;
8
+ var shared = host[MARKS] ?? {
9
+ pending: null,
10
+ expiring: false,
11
+ components: /* @__PURE__ */ new Map()
12
+ };
13
+ host[MARKS] = shared;
14
+ function markReactiveSource(file, line, column, name) {
15
+ const location = locate(file, line, column);
16
+ if (shared.pending && location.external && !shared.pending.location?.external) return;
17
+ shared.pending = { name, location };
18
+ shared.expiring = false;
19
+ }
20
+ function locate(file, line, column) {
21
+ const external = !!file && (/^([a-zA-Z]:)?[\\/]/.test(file) || file.includes("node_modules/"));
22
+ return external ? { file, line, column, external } : { file, line, column };
23
+ }
24
+ function markComponentSource(name, file, line, column) {
25
+ const seen = shared.components.get(name);
26
+ const next = locate(file, line, column);
27
+ if (seen === void 0) {
28
+ shared.components.set(name, next);
29
+ return;
30
+ }
31
+ if (!seen || seen.file !== file || seen.line !== line) shared.components.set(name, null);
32
+ }
33
+ function componentSource(name) {
34
+ return shared.components.get(name) ?? void 0;
35
+ }
36
+ function takeReactiveSource() {
37
+ const mark = shared.pending;
38
+ if (mark) {
39
+ if (mark.name !== void 0) shared.pending = { location: mark.location };
40
+ if (!shared.expiring) {
41
+ shared.expiring = true;
42
+ queueMicrotask(() => {
43
+ shared.expiring = false;
44
+ shared.pending = null;
45
+ });
46
+ }
47
+ }
48
+ return mark;
49
+ }
50
+ var HOLES = /* @__PURE__ */ new WeakMap();
51
+ var HOLE_TAG = "__fx_at";
52
+ function markHole(accessor, line, column, name) {
53
+ if (accessor && (typeof accessor === "object" || typeof accessor === "function")) {
54
+ const at = name === void 0 ? { line, column } : { line, column, name };
55
+ HOLES.set(accessor, at);
56
+ try {
57
+ Object.defineProperty(accessor, HOLE_TAG, { value: at, configurable: true });
58
+ } catch {
59
+ }
60
+ }
61
+ shared.pending = null;
62
+ return accessor;
63
+ }
64
+ var PROPS = /* @__PURE__ */ new WeakMap();
65
+ function markProps(props, at) {
66
+ if (props && typeof props === "object") PROPS.set(props, at);
67
+ return props;
68
+ }
69
+ function propsLocations(props) {
70
+ return props && typeof props === "object" ? PROPS.get(props) : void 0;
71
+ }
72
+ function holeLocation(accessor) {
73
+ if (!accessor || typeof accessor !== "object" && typeof accessor !== "function") return void 0;
74
+ return HOLES.get(accessor) ?? accessor[HOLE_TAG];
75
+ }
76
+ function installSourceMarker() {
77
+ if (typeof host[MARKER] !== "function") host[MARKER] = markReactiveSource;
78
+ if (typeof host[COMPONENT_MARKER] !== "function") host[COMPONENT_MARKER] = markComponentSource;
79
+ if (typeof host[HOLE_MARKER] !== "function") host[HOLE_MARKER] = markHole;
80
+ if (typeof host[PROPS_MARKER] !== "function") host[PROPS_MARKER] = markProps;
81
+ }
82
+ export {
83
+ componentSource,
84
+ holeLocation,
85
+ installSourceMarker,
86
+ markComponentSource,
87
+ markReactiveSource,
88
+ propsLocations,
89
+ takeReactiveSource
90
+ };
@@ -0,0 +1,350 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/timeline.ts
21
+ var timeline_exports = {};
22
+ __export(timeline_exports, {
23
+ TIMELINE_CSS: () => TIMELINE_CSS,
24
+ createTimeline: () => createTimeline
25
+ });
26
+ module.exports = __toCommonJS(timeline_exports);
27
+
28
+ // src/history.ts
29
+ function toCommits(events) {
30
+ const byNumber = /* @__PURE__ */ new Map();
31
+ for (const event of events) {
32
+ const bucket = byNumber.get(event.commit);
33
+ if (bucket) bucket.push(event);
34
+ else byNumber.set(event.commit, [event]);
35
+ }
36
+ return [...byNumber.entries()].sort((a, b) => a[0] - b[0]).map(([commit, group]) => ({
37
+ commit,
38
+ t: group[0].t,
39
+ events: group,
40
+ cause: group.find((e) => e.kind === "write" && e.by === void 0)
41
+ }));
42
+ }
43
+
44
+ // src/copy.ts
45
+ function confirmOn(button, said = "copied") {
46
+ if (!button) return;
47
+ const was = button.textContent;
48
+ button.textContent = said;
49
+ setTimeout(() => {
50
+ button.textContent = was;
51
+ }, 1200);
52
+ }
53
+ function fallback(text, button) {
54
+ try {
55
+ const doc = button?.ownerDocument ?? document;
56
+ const area = doc.createElement("textarea");
57
+ area.value = text;
58
+ area.setAttribute("readonly", "");
59
+ area.style.position = "fixed";
60
+ area.style.opacity = "0";
61
+ doc.body.appendChild(area);
62
+ area.select();
63
+ doc.execCommand("copy");
64
+ area.remove();
65
+ confirmOn(button);
66
+ return true;
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+ async function copyText(text, button) {
72
+ const clipboard = globalThis.navigator?.clipboard;
73
+ if (clipboard?.writeText) {
74
+ try {
75
+ await clipboard.writeText(text);
76
+ confirmOn(button);
77
+ return true;
78
+ } catch {
79
+ }
80
+ }
81
+ return fallback(text, button);
82
+ }
83
+
84
+ // src/change-view.ts
85
+ var escape = (text) => text.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
86
+ var SIGN = { added: "+", removed: "−", changed: "~" };
87
+ function pretty(json) {
88
+ if (!json) return void 0;
89
+ try {
90
+ return JSON.stringify(JSON.parse(json), null, 2);
91
+ } catch {
92
+ return void 0;
93
+ }
94
+ }
95
+ function sideOf(text, json, cls) {
96
+ const block = pretty(json);
97
+ if (!block) return `<span class="ch-${cls}">${escape(text ?? "")}</span>`;
98
+ return `<div class="ch-block ch-${cls}"><button type="button" class="ch-copy" title="Copy" data-copy="${escape(block)}">copy</button><pre class="ch-json">${escape(block)}</pre></div>`;
99
+ }
100
+ function changeRows(change2, limit = Infinity) {
101
+ if (!change2?.entries?.length) return "";
102
+ const shown = change2.entries.slice(0, limit);
103
+ const hidden = change2.entries.length - shown.length + (change2.more ?? 0);
104
+ const rows = shown.map((entry) => {
105
+ const from = sideOf(entry.from, entry.fromJson, "from");
106
+ const to = sideOf(entry.to, entry.toJson, "to");
107
+ const sides = entry.op === "changed" ? `${from}<span class="ch-arrow">→</span>${to}` : entry.op === "added" ? to : from;
108
+ const stacked = entry.fromJson || entry.toJson ? " ch-stacked" : "";
109
+ return `<div class="ch-row ch-${entry.op}${stacked}"><span class="ch-sign">${SIGN[entry.op] ?? "·"}</span><span class="ch-key">${escape(entry.key)}</span><span class="ch-sides">${sides}</span></div>`;
110
+ }).join("");
111
+ const size = change2.length ? `<div class="ch-size">${change2.length.from} → ${change2.length.to} items</div>` : "";
112
+ return `<div class="ch-list">${size}${rows}${hidden > 0 ? `<div class="ch-more">and ${hidden} more</div>` : ""}</div>`;
113
+ }
114
+ function handleCopyClick(event) {
115
+ const button = event.target?.closest?.(".ch-copy");
116
+ if (!button) return false;
117
+ void copyText(button.dataset.copy ?? "", button);
118
+ return true;
119
+ }
120
+ var CHANGE_CSS = `
121
+ .ch-list { margin-top: 4px; font-family: ui-monospace, monospace; font-size: 11px; }
122
+ .ch-size { color: var(--fx-muted, #8a90a2); margin-bottom: 2px; }
123
+ .ch-row { display: flex; gap: 6px; align-items: baseline; padding: 1px 0; }
124
+ .ch-row.ch-stacked { align-items: flex-start; padding: 3px 0; }
125
+ .ch-sign { flex: none; width: 9px; text-align: center; }
126
+ .ch-added .ch-sign { color: var(--state, #34d399); }
127
+ .ch-removed .ch-sign { color: var(--fx-write, #fb7185); }
128
+ .ch-changed .ch-sign { color: var(--effect, #fbbf24); }
129
+ .ch-key { flex: none; color: var(--fx-muted, #8a90a2); }
130
+ .ch-sides { min-width: 0; flex: 1; overflow-wrap: anywhere; }
131
+ .ch-stacked .ch-sides { display: flex; flex-direction: column; gap: 2px; }
132
+ .ch-from { color: var(--fx-muted, #8a90a2); }
133
+ .ch-row:not(.ch-stacked) .ch-from { text-decoration: line-through; }
134
+ .ch-arrow { color: var(--fx-muted, #8a90a2); margin: 0 4px; }
135
+ .ch-to { color: var(--fx-label, #e7e9ee); }
136
+ .ch-more { color: var(--fx-muted, #8a90a2); margin-top: 2px; }
137
+ /* In the corner: an object can be tall, and a button under it is a scroll away. */
138
+ .ch-block { position: relative; border-radius: 4px; border: 1px solid var(--fx-line, #ffffff14); }
139
+ .ch-block.ch-from { opacity: .72; }
140
+ .ch-json { margin: 0; padding: 5px 7px; overflow-x: auto; white-space: pre; font: inherit; }
141
+ .ch-copy {
142
+ position: absolute; top: 3px; right: 3px; padding: 1px 6px; cursor: pointer;
143
+ font: inherit; font-size: 9.5px; opacity: 0; transition: opacity .12s;
144
+ border: 1px solid var(--fx-line, #ffffff26); border-radius: 3px;
145
+ background: var(--fx-panel, #14161c); color: var(--fx-muted, #8a90a2);
146
+ }
147
+ .ch-block:hover .ch-copy, .ch-copy:focus { opacity: 1; }
148
+ .ch-copy:hover { color: var(--fx-label, #e7e9ee); }
149
+ `;
150
+
151
+ // src/timeline.ts
152
+ var escape2 = (text) => text.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
153
+ var clock = (t) => {
154
+ const d = new Date(t);
155
+ const pad = (n, width = 2) => String(n).padStart(width, "0");
156
+ return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;
157
+ };
158
+ function subject(commit, nameOf) {
159
+ if (!commit.cause) return commit.commit === 0 ? "first render" : "continued";
160
+ return nameOf(commit.cause);
161
+ }
162
+ function change(commit) {
163
+ const cause = commit.cause;
164
+ if (!cause || cause.from === void 0 || cause.to === void 0) return "";
165
+ return `${cause.from} → ${cause.to}`;
166
+ }
167
+ function detail(commit, nameOf) {
168
+ const lines = [`${subject(commit, nameOf)} ${change(commit)}`.trim()];
169
+ const named = (kind, verb) => {
170
+ const ids = commit.events.filter((e) => e.kind === kind && e !== commit.cause).map(nameOf);
171
+ if (ids.length) lines.push(`${verb}: ${[...new Set(ids)].join(", ")}`);
172
+ };
173
+ named("ran", "ran");
174
+ named("write", "written");
175
+ named("created", "made");
176
+ named("disposed", "gone");
177
+ return lines.join("\n");
178
+ }
179
+ function summary(commit) {
180
+ const counts = { ran: 0, created: 0, disposed: 0, write: 0 };
181
+ for (const event of commit.events) counts[event.kind]++;
182
+ const writes = counts.write - (commit.cause ? 1 : 0);
183
+ return [
184
+ counts.ran && `${counts.ran} ran`,
185
+ writes > 0 && `${writes} written`,
186
+ counts.created && `${counts.created} made`,
187
+ counts.disposed && `${counts.disposed} gone`
188
+ ].filter(Boolean).join(" · ");
189
+ }
190
+ function createTimeline(host, options = {}) {
191
+ const empty = options.empty ?? "nothing has happened yet";
192
+ const limit = options.limit ?? 100;
193
+ let events = [];
194
+ let seen = -1;
195
+ let selected = null;
196
+ let filter = "";
197
+ let signature = "";
198
+ let paused = false;
199
+ host.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>';
200
+ const input = host.querySelector(".tl-filter");
201
+ const list = host.querySelector(".tl-list");
202
+ const pause = host.querySelector(".tl-pause");
203
+ const all = () => toCommits(events).slice(-limit).reverse();
204
+ const idsOf = (commit) => {
205
+ const ids = /* @__PURE__ */ new Set();
206
+ for (const event of commit.events) {
207
+ ids.add(event.id);
208
+ if (event.by !== void 0) ids.add(event.by);
209
+ }
210
+ return [...ids];
211
+ };
212
+ const haystack = (commit, nameOf) => `${detail(commit, nameOf)} ${summary(commit)}`.toLowerCase();
213
+ const shown = (nameOf) => filter ? all().filter((c) => haystack(c, nameOf).includes(filter)) : all();
214
+ input.addEventListener("input", () => {
215
+ filter = input.value.trim().toLowerCase();
216
+ signature = "";
217
+ });
218
+ const showPaused = () => {
219
+ pause.textContent = paused ? "▶" : "❚❚";
220
+ pause.classList.toggle("on", paused);
221
+ pause.title = paused ? "Taking updates again" : "Stop taking new updates, so the list holds still while you read it";
222
+ };
223
+ pause.addEventListener("click", () => {
224
+ paused = !paused;
225
+ showPaused();
226
+ signature = "";
227
+ });
228
+ list.addEventListener("click", (e) => {
229
+ if (handleCopyClick(e)) return;
230
+ const row = e.target?.closest?.(".tl-commit");
231
+ if (!row) return;
232
+ const number = Number(row.dataset.commit);
233
+ if (e.target.classList.contains("tl-more")) {
234
+ const full = all().find((c) => c.commit === number);
235
+ if (full) options.onExpand?.(full);
236
+ return;
237
+ }
238
+ selected = selected === number ? null : number;
239
+ const commit = all().find((c) => c.commit === selected) ?? null;
240
+ options.onSelect?.(commit ? idsOf(commit) : [], commit);
241
+ signature = "";
242
+ });
243
+ return {
244
+ setPaused(next) {
245
+ paused = next;
246
+ showPaused();
247
+ signature = "";
248
+ },
249
+ get paused() {
250
+ return paused;
251
+ },
252
+ push(incoming) {
253
+ if (paused) return;
254
+ let added = false;
255
+ for (const event of incoming) {
256
+ if (event.seq <= seen) continue;
257
+ events.push(event);
258
+ seen = event.seq;
259
+ added = true;
260
+ }
261
+ if (!added) return;
262
+ if (events.length > limit * 40) events = events.slice(-limit * 40);
263
+ },
264
+ render(graph) {
265
+ const nameOf = (e) => graph.get(e.id)?.label ?? e.label ?? `#${e.id}`;
266
+ const rows = shown(nameOf);
267
+ const next = `${seen}|${selected ?? ""}|${filter}|${rows.length}|${graph.size}`;
268
+ if (next === signature) return;
269
+ signature = next;
270
+ if (!rows.length) {
271
+ list.innerHTML = `<span class="tl-empty">${escape2(filter ? "nothing matches" : empty)}</span>`;
272
+ return;
273
+ }
274
+ list.innerHTML = rows.map((commit) => {
275
+ const open = commit.commit === selected;
276
+ const did = summary(commit);
277
+ const moved = change(commit);
278
+ const parts = [
279
+ ["ran", "ran"],
280
+ ["write", "written"],
281
+ ["created", "made"],
282
+ ["disposed", "gone"]
283
+ ];
284
+ const body = parts.map(([kind, verb]) => {
285
+ const names = [
286
+ ...new Set(
287
+ commit.events.filter((e) => e.kind === kind && e !== commit.cause).map(nameOf)
288
+ )
289
+ ];
290
+ return names.length ? `<div class="tl-part"><span class="tl-verb">${verb}</span> ${escape2(names.join(", "))}</div>` : "";
291
+ }).join("");
292
+ return `<div class="tl-commit${open ? " selected" : ""}" data-commit="${commit.commit}" title="${escape2(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">${escape2(subject(commit, nameOf))}</span>` + (did ? `<span class="tl-did">${escape2(did)}</span>` : "") + "</div>" + // Open, the value gets the width and wraps.
293
+ (open ? `<div class="tl-body">${moved ? `<div class="tl-change">${escape2(moved)}</div>` : ""}${changeRows(commit.cause?.change, 6)}${body}${// Only worth offering when there is more than the row can hold.
294
+ options.onExpand && (commit.cause?.change || commit.events.length > 6) ? '<button type="button" class="tl-more">open in full</button>' : ""}</div>` : "") + "</div>";
295
+ }).join("");
296
+ },
297
+ selected() {
298
+ const commit = all().find((c) => c.commit === selected);
299
+ return commit ? idsOf(commit) : [];
300
+ },
301
+ clear() {
302
+ events = [];
303
+ seen = -1;
304
+ selected = null;
305
+ signature = "";
306
+ list.innerHTML = `<span class="tl-empty">${escape2(empty)}</span>`;
307
+ }
308
+ };
309
+ }
310
+ var TIMELINE_CSS = `
311
+ .tl-head { position: relative; display: flex; gap: 4px; padding: 0 .25rem; }
312
+ .tl-pause {
313
+ position: absolute; right: .4rem; top: 40%; transform: translateY(-50%);
314
+ flex: none; width: 26px; cursor: pointer; font: inherit; font-size: 10px;
315
+ border: 1px solid var(--fx-line, #ffffff1f); border-radius: 4px;
316
+ background: transparent; color: var(--fx-muted, #8a90a2);
317
+ }
318
+ .tl-pause:hover { color: var(--fx-label, #e7e9ee); }
319
+ .tl-pause.on { color: var(--fx-accent, #5eead4); border-color: var(--fx-accent, #5eead4); }
320
+ .tl-filter {
321
+ flex: 1; min-width: 0; box-sizing: border-box; padding: 3px 6px;
322
+ font: inherit; font-size: 11.5px; border-radius: 4px;
323
+ border: 1px solid var(--fx-line, #ffffff1f);
324
+ background: var(--fx-input, transparent); color: var(--fx-label, #e7e9ee);
325
+ padding-inline-end: 2rem;
326
+ }
327
+ .tl-empty { color: var(--fx-muted, #8a90a2); font-size: 12px; }
328
+ .tl-commit { padding: 4px 6px; border-radius: 4px; cursor: pointer; font-size: 11.5px; }
329
+ .tl-commit:hover { background: var(--fx-hover, #ffffff10); }
330
+ .tl-commit.selected { background: var(--fx-selected, #ffffff1a); }
331
+ .tl-line { display: flex; align-items: baseline; gap: 6px; }
332
+ .tl-caret { color: var(--fx-muted, #8a90a2); font-size: 9px; flex: none; }
333
+ .tl-time { font-family: ui-monospace, monospace; font-size: 10px; color: var(--fx-muted, #8a90a2); flex: none; }
334
+ .tl-name { color: var(--fx-label, #e7e9ee); font-family: ui-monospace, monospace; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
335
+ .tl-did { margin-left: auto; color: var(--fx-muted, #8a90a2); flex: none; }
336
+ .tl-body { margin: 3px 0 2px 15px; }
337
+ /* Wraps: an open entry exists to show the whole value. */
338
+ .tl-change {
339
+ font-family: ui-monospace, monospace; font-size: 11px;
340
+ color: var(--fx-accent, #5eead4); overflow-wrap: anywhere; white-space: pre-wrap;
341
+ }
342
+ .tl-part { margin-top: 2px; color: var(--fx-label, #e7e9ee); font-size: 11px; overflow-wrap: anywhere; }
343
+ .tl-verb { color: var(--fx-muted, #8a90a2); }
344
+ .tl-more {
345
+ margin-top: 5px; padding: 2px 8px; font: inherit; font-size: 10.5px; cursor: pointer;
346
+ border: 1px solid var(--fx-line, #ffffff1f); border-radius: 4px;
347
+ background: transparent; color: var(--fx-muted, #8a90a2);
348
+ }
349
+ .tl-more:hover { color: var(--fx-label, #e7e9ee); }
350
+ ${CHANGE_CSS}`;
@@ -0,0 +1,30 @@
1
+ import { type Commit, type GraphEvent } from './history.js';
2
+ import type { RNode } from './instrument.js';
3
+ export interface Timeline {
4
+ /** Fold in what a snapshot carried. Events already seen are ignored, so polling is safe. */
5
+ push(events: readonly GraphEvent[]): void;
6
+ /** Stop taking new events, or start again. Paused, what is on screen holds still. */
7
+ setPaused(paused: boolean): void;
8
+ readonly paused: boolean;
9
+ /** Draw. `graph` supplies the labels, which the events only carry ids for. */
10
+ render(graph: Map<number, RNode>): void;
11
+ /** Node ids in the selected commit, for a view to highlight. Empty when none is selected. */
12
+ selected(): number[];
13
+ clear(): void;
14
+ }
15
+ export interface TimelineOptions {
16
+ /** What to say before anything has happened. */
17
+ empty?: string;
18
+ /** How many commits to keep. */
19
+ limit?: number;
20
+ /** A commit was selected, or deselected with null. */
21
+ onSelect?(ids: number[], commit: Commit | null): void;
22
+ /** A side panel can say six rows changed; it cannot show six before-and-after objects. */
23
+ onExpand?(commit: Commit): void;
24
+ }
25
+ /** The graph first, since a node can be renamed; then the name the event carried. */
26
+ export type NameOf = (event: GraphEvent) => string;
27
+ export declare function createTimeline(host: HTMLElement, options?: TimelineOptions): Timeline;
28
+ /** Default styling for what createTimeline writes, in the shape DEVTOOLS_CSS is used. */
29
+ export declare const TIMELINE_CSS = "\n.tl-head { position: relative; display: flex; gap: 4px; padding: 0 .25rem; }\n.tl-pause {\n position: absolute; right: .4rem; top: 40%; transform: translateY(-50%);\n flex: none; width: 26px; cursor: pointer; font: inherit; font-size: 10px;\n border: 1px solid var(--fx-line, #ffffff1f); border-radius: 4px;\n background: transparent; color: var(--fx-muted, #8a90a2);\n}\n.tl-pause:hover { color: var(--fx-label, #e7e9ee); }\n.tl-pause.on { color: var(--fx-accent, #5eead4); border-color: var(--fx-accent, #5eead4); }\n.tl-filter {\n flex: 1; min-width: 0; box-sizing: border-box; padding: 3px 6px;\n font: inherit; font-size: 11.5px; border-radius: 4px;\n border: 1px solid var(--fx-line, #ffffff1f);\n background: var(--fx-input, transparent); color: var(--fx-label, #e7e9ee);\n padding-inline-end: 2rem;\n}\n.tl-empty { color: var(--fx-muted, #8a90a2); font-size: 12px; }\n.tl-commit { padding: 4px 6px; border-radius: 4px; cursor: pointer; font-size: 11.5px; }\n.tl-commit:hover { background: var(--fx-hover, #ffffff10); }\n.tl-commit.selected { background: var(--fx-selected, #ffffff1a); }\n.tl-line { display: flex; align-items: baseline; gap: 6px; }\n.tl-caret { color: var(--fx-muted, #8a90a2); font-size: 9px; flex: none; }\n.tl-time { font-family: ui-monospace, monospace; font-size: 10px; color: var(--fx-muted, #8a90a2); flex: none; }\n.tl-name { color: var(--fx-label, #e7e9ee); font-family: ui-monospace, monospace; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.tl-did { margin-left: auto; color: var(--fx-muted, #8a90a2); flex: none; }\n.tl-body { margin: 3px 0 2px 15px; }\n/* Wraps: an open entry exists to show the whole value. */\n.tl-change {\n font-family: ui-monospace, monospace; font-size: 11px;\n color: var(--fx-accent, #5eead4); overflow-wrap: anywhere; white-space: pre-wrap;\n}\n.tl-part { margin-top: 2px; color: var(--fx-label, #e7e9ee); font-size: 11px; overflow-wrap: anywhere; }\n.tl-verb { color: var(--fx-muted, #8a90a2); }\n.tl-more {\n margin-top: 5px; padding: 2px 8px; font: inherit; font-size: 10.5px; cursor: pointer;\n border: 1px solid var(--fx-line, #ffffff1f); border-radius: 4px;\n background: transparent; color: var(--fx-muted, #8a90a2);\n}\n.tl-more:hover { color: var(--fx-label, #e7e9ee); }\n\n.ch-list { margin-top: 4px; font-family: ui-monospace, monospace; font-size: 11px; }\n.ch-size { color: var(--fx-muted, #8a90a2); margin-bottom: 2px; }\n.ch-row { display: flex; gap: 6px; align-items: baseline; padding: 1px 0; }\n.ch-row.ch-stacked { align-items: flex-start; padding: 3px 0; }\n.ch-sign { flex: none; width: 9px; text-align: center; }\n.ch-added .ch-sign { color: var(--state, #34d399); }\n.ch-removed .ch-sign { color: var(--fx-write, #fb7185); }\n.ch-changed .ch-sign { color: var(--effect, #fbbf24); }\n.ch-key { flex: none; color: var(--fx-muted, #8a90a2); }\n.ch-sides { min-width: 0; flex: 1; overflow-wrap: anywhere; }\n.ch-stacked .ch-sides { display: flex; flex-direction: column; gap: 2px; }\n.ch-from { color: var(--fx-muted, #8a90a2); }\n.ch-row:not(.ch-stacked) .ch-from { text-decoration: line-through; }\n.ch-arrow { color: var(--fx-muted, #8a90a2); margin: 0 4px; }\n.ch-to { color: var(--fx-label, #e7e9ee); }\n.ch-more { color: var(--fx-muted, #8a90a2); margin-top: 2px; }\n/* In the corner: an object can be tall, and a button under it is a scroll away. */\n.ch-block { position: relative; border-radius: 4px; border: 1px solid var(--fx-line, #ffffff14); }\n.ch-block.ch-from { opacity: .72; }\n.ch-json { margin: 0; padding: 5px 7px; overflow-x: auto; white-space: pre; font: inherit; }\n.ch-copy {\n position: absolute; top: 3px; right: 3px; padding: 1px 6px; cursor: pointer;\n font: inherit; font-size: 9.5px; opacity: 0; transition: opacity .12s;\n border: 1px solid var(--fx-line, #ffffff26); border-radius: 3px;\n background: var(--fx-panel, #14161c); color: var(--fx-muted, #8a90a2);\n}\n.ch-block:hover .ch-copy, .ch-copy:focus { opacity: 1; }\n.ch-copy:hover { color: var(--fx-label, #e7e9ee); }\n";
30
+ //# sourceMappingURL=timeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"AAOA,OAAO,EAAa,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAEvE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,WAAW,QAAQ;IACvB,4FAA4F;IAC5F,IAAI,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,GAAG,IAAI,CAAC;IAC1C,qFAAqF;IACrF,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,8EAA8E;IAC9E,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC;IACxC,6FAA6F;IAC7F,QAAQ,IAAI,MAAM,EAAE,CAAC;IACrB,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,QAAQ,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IACtD,0FAA0F;IAC1F,QAAQ,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAWD,qFAAqF;AACrF,MAAM,MAAM,MAAM,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,MAAM,CAAC;AA6CnD,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,GAAE,eAAoB,GAAG,QAAQ,CAwLzF;AAED,yFAAyF;AACzF,eAAO,MAAM,YAAY,o8HAwCX,CAAC"}