@fluixi/devtools 0.2.0-alpha.2 → 0.2.0-alpha.3
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.d.ts +22 -0
- package/dist/callsite.d.ts.map +1 -0
- package/dist/callsite.js +82 -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/copy.d.ts +9 -0
- package/dist/copy.d.ts.map +1 -0
- package/dist/copy.js +52 -0
- package/dist/describe.d.ts +3 -0
- package/dist/describe.d.ts.map +1 -0
- package/dist/describe.js +47 -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/diff.d.ts +31 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +132 -0
- package/dist/dom.d.ts +81 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +269 -0
- package/dist/export.d.ts +55 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +90 -0
- package/dist/float.d.ts +54 -0
- package/dist/float.d.ts.map +1 -0
- package/dist/float.js +256 -0
- package/dist/format.d.ts +16 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +188 -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/highlight.d.ts +56 -0
- package/dist/highlight.d.ts.map +1 -0
- package/dist/highlight.js +154 -0
- package/dist/history.d.ts +59 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +85 -0
- package/dist/hook.d.ts +60 -0
- package/dist/hook.d.ts.map +1 -0
- package/dist/hook.js +142 -0
- package/dist/index.d.ts +44 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -1
- package/dist/inspector.d.ts +60 -0
- package/dist/inspector.d.ts.map +1 -0
- package/dist/inspector.js +415 -0
- package/dist/instrument.d.ts +147 -5
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.js +164 -44
- 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/menu.d.ts +37 -0
- package/dist/menu.d.ts.map +1 -0
- package/dist/menu.js +125 -0
- package/dist/observe.d.ts +80 -0
- package/dist/observe.d.ts.map +1 -0
- package/dist/observe.js +893 -0
- package/dist/palette.d.ts +11 -0
- package/dist/palette.d.ts.map +1 -0
- package/dist/palette.js +27 -0
- package/dist/path.d.ts +19 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +49 -0
- package/dist/plugin.d.ts +121 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +74 -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/directives.d.ts +25 -0
- package/dist/plugins/directives.d.ts.map +1 -0
- package/dist/plugins/directives.js +78 -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/source.d.ts +72 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +153 -0
- package/dist/timeline.d.ts +30 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +271 -0
- package/dist/tree.d.ts +128 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +645 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- 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/wire.d.ts +100 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +93 -0
- package/package.json +7 -2
package/dist/float.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Taking a pane out of the layout and putting it back.
|
|
3
|
+
*
|
|
4
|
+
* The inspector, the timeline and the tree each live in a fixed corner of the panel. Reading
|
|
5
|
+
* one of them properly means giving it room, and the room has to come from the others. So
|
|
6
|
+
* each carries a button that lifts it into a card over everything, and the same button puts
|
|
7
|
+
* it back exactly where it was.
|
|
8
|
+
*
|
|
9
|
+
* The pane element itself is moved rather than re-rendered. Whatever drew into it keeps its
|
|
10
|
+
* handle on it, its listeners, and its scroll position — a floated timeline is the same
|
|
11
|
+
* timeline, not a copy of one.
|
|
12
|
+
*/
|
|
13
|
+
/** So a card cannot be dragged or resized into something unreadable. */
|
|
14
|
+
const MIN_W = 240;
|
|
15
|
+
const MIN_H = 160;
|
|
16
|
+
/**
|
|
17
|
+
* Pointer capture, for the reason the splitters need it: without it the events stop arriving
|
|
18
|
+
* once the pointer crosses the graph or an iframe, the release is never seen, and the card
|
|
19
|
+
* keeps following the mouse.
|
|
20
|
+
*/
|
|
21
|
+
function draggable(handle,
|
|
22
|
+
/** Total travel since the press, not the step since the last event. */
|
|
23
|
+
onMove, onStart) {
|
|
24
|
+
handle.addEventListener('pointerdown', (event) => {
|
|
25
|
+
if (event.button !== 0)
|
|
26
|
+
return;
|
|
27
|
+
// Capture retargets the click that follows onto the handle, so a button inside it would
|
|
28
|
+
// never hear its own click.
|
|
29
|
+
if (event.target.closest('button'))
|
|
30
|
+
return;
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
handle.setPointerCapture(event.pointerId);
|
|
33
|
+
const fromX = event.clientX;
|
|
34
|
+
const fromY = event.clientY;
|
|
35
|
+
onStart?.();
|
|
36
|
+
// Measured from where the press began. Adding up each step instead loses whatever a
|
|
37
|
+
// clamp refused, so dragging back out of a minimum grows the card immediately rather
|
|
38
|
+
// than when the pointer returns to where it stopped shrinking.
|
|
39
|
+
const move = (e) => onMove(e.clientX - fromX, e.clientY - fromY);
|
|
40
|
+
const stop = () => {
|
|
41
|
+
handle.removeEventListener('pointermove', move);
|
|
42
|
+
handle.removeEventListener('pointerup', stop);
|
|
43
|
+
handle.removeEventListener('pointercancel', stop);
|
|
44
|
+
handle.removeEventListener('lostpointercapture', stop);
|
|
45
|
+
if (handle.hasPointerCapture(event.pointerId))
|
|
46
|
+
handle.releasePointerCapture(event.pointerId);
|
|
47
|
+
};
|
|
48
|
+
handle.addEventListener('pointermove', move);
|
|
49
|
+
handle.addEventListener('pointerup', stop);
|
|
50
|
+
handle.addEventListener('pointercancel', stop);
|
|
51
|
+
handle.addEventListener('lostpointercapture', stop);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Enough of a cascade that two cards opened together do not sit on top of each other. */
|
|
55
|
+
let opened = 0;
|
|
56
|
+
/**
|
|
57
|
+
* The moving and resizing, on its own.
|
|
58
|
+
*
|
|
59
|
+
* Both a floated pane and the value pane want the same card, and writing the drag twice is
|
|
60
|
+
* how two of them end up behaving differently.
|
|
61
|
+
*/
|
|
62
|
+
export function floatCard(doc, title, at) {
|
|
63
|
+
const card = doc.createElement('div');
|
|
64
|
+
card.className = 'fx-float';
|
|
65
|
+
const spot = at ?? {
|
|
66
|
+
x: 60 + (opened % 4) * 28,
|
|
67
|
+
y: 60 + (opened % 4) * 28,
|
|
68
|
+
width: 360,
|
|
69
|
+
height: 280,
|
|
70
|
+
};
|
|
71
|
+
opened += 1;
|
|
72
|
+
const bar = doc.createElement('div');
|
|
73
|
+
bar.className = 'fx-float-bar';
|
|
74
|
+
const name = doc.createElement('span');
|
|
75
|
+
name.textContent = title;
|
|
76
|
+
bar.append(name);
|
|
77
|
+
card.append(bar);
|
|
78
|
+
// Kept here rather than read back from the element each move. offsetWidth is what the
|
|
79
|
+
// card ended up at, so once a size clamps, every further move measures from the clamp
|
|
80
|
+
// and the card grows when the pointer is still travelling the other way.
|
|
81
|
+
let x = spot.x;
|
|
82
|
+
let y = spot.y;
|
|
83
|
+
let w = spot.width;
|
|
84
|
+
let h = spot.height;
|
|
85
|
+
const apply = () => {
|
|
86
|
+
const view = doc.defaultView;
|
|
87
|
+
const vw = view?.innerWidth ?? w;
|
|
88
|
+
const vh = view?.innerHeight ?? h;
|
|
89
|
+
w = Math.min(Math.max(MIN_W, w), vw);
|
|
90
|
+
h = Math.min(Math.max(MIN_H, h), vh);
|
|
91
|
+
// Fully on screen: a card wider than the window puts its own resize handles past the
|
|
92
|
+
// edge, and then there is no way to make it smaller again.
|
|
93
|
+
x = Math.min(Math.max(0, x), Math.max(0, vw - w));
|
|
94
|
+
y = Math.min(Math.max(0, y), Math.max(0, vh - h));
|
|
95
|
+
card.style.left = `${x}px`;
|
|
96
|
+
card.style.top = `${y}px`;
|
|
97
|
+
card.style.width = `${w}px`;
|
|
98
|
+
card.style.height = `${h}px`;
|
|
99
|
+
};
|
|
100
|
+
// Every edge and corner, so a card against one side of the window can still be resized
|
|
101
|
+
// from the other.
|
|
102
|
+
let from = { x, y, w, h };
|
|
103
|
+
const remember = () => {
|
|
104
|
+
from = { x, y, w, h };
|
|
105
|
+
};
|
|
106
|
+
for (const edge of ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw']) {
|
|
107
|
+
const handle = doc.createElement('div');
|
|
108
|
+
handle.className = `fx-float-h fx-float-${edge}`;
|
|
109
|
+
card.append(handle);
|
|
110
|
+
draggable(handle, (dx, dy) => {
|
|
111
|
+
if (edge.includes('e'))
|
|
112
|
+
w = from.w + dx;
|
|
113
|
+
if (edge.includes('s'))
|
|
114
|
+
h = from.h + dy;
|
|
115
|
+
// A top or left edge leaves the opposite one where it is, so the card only moves
|
|
116
|
+
// by as much as the size actually gave.
|
|
117
|
+
if (edge.includes('w')) {
|
|
118
|
+
w = Math.max(MIN_W, from.w - dx);
|
|
119
|
+
x = from.x + from.w - w;
|
|
120
|
+
}
|
|
121
|
+
if (edge.includes('n')) {
|
|
122
|
+
h = Math.max(MIN_H, from.h - dy);
|
|
123
|
+
y = from.y + from.h - h;
|
|
124
|
+
}
|
|
125
|
+
apply();
|
|
126
|
+
}, remember);
|
|
127
|
+
}
|
|
128
|
+
draggable(bar, (dx, dy) => {
|
|
129
|
+
x = from.x + dx;
|
|
130
|
+
y = from.y + dy;
|
|
131
|
+
apply();
|
|
132
|
+
}, remember);
|
|
133
|
+
apply();
|
|
134
|
+
return { card, bar, remove: () => card.remove() };
|
|
135
|
+
}
|
|
136
|
+
export function makeFloatable(pane, options) {
|
|
137
|
+
const doc = pane.ownerDocument;
|
|
138
|
+
let card = null;
|
|
139
|
+
/** Where the pane came from, so it goes back there rather than at the end. */
|
|
140
|
+
let home = null;
|
|
141
|
+
const button = doc.createElement('button');
|
|
142
|
+
button.type = 'button';
|
|
143
|
+
button.className = 'fx-float-btn';
|
|
144
|
+
button.textContent = '⇱';
|
|
145
|
+
button.title = `Float ${options.title}`;
|
|
146
|
+
pane.classList.add('fx-floatable');
|
|
147
|
+
pane.append(button);
|
|
148
|
+
/**
|
|
149
|
+
* The panes rewrite their own contents — the inspector replaces its markup whenever the
|
|
150
|
+
* selection changes — which takes the button with it. Rather than asking each of them to
|
|
151
|
+
* leave it alone, it is put back when it goes.
|
|
152
|
+
*/
|
|
153
|
+
const keep = new MutationObserver(() => {
|
|
154
|
+
if (button.parentNode !== pane)
|
|
155
|
+
pane.append(button);
|
|
156
|
+
});
|
|
157
|
+
keep.observe(pane, { childList: true });
|
|
158
|
+
function dock() {
|
|
159
|
+
if (!card || !home)
|
|
160
|
+
return;
|
|
161
|
+
home.parent.insertBefore(pane, home.before);
|
|
162
|
+
card.remove();
|
|
163
|
+
card = null;
|
|
164
|
+
home = null;
|
|
165
|
+
pane.classList.remove('fx-floated');
|
|
166
|
+
button.textContent = '⇱';
|
|
167
|
+
button.title = `Float ${options.title}`;
|
|
168
|
+
options.onChange?.(false);
|
|
169
|
+
}
|
|
170
|
+
function float() {
|
|
171
|
+
if (card || !pane.parentNode)
|
|
172
|
+
return;
|
|
173
|
+
home = { parent: pane.parentNode, before: pane.nextSibling };
|
|
174
|
+
const built = floatCard(doc, options.title, options.at);
|
|
175
|
+
card = built.card;
|
|
176
|
+
card.append(pane);
|
|
177
|
+
doc.body.appendChild(card);
|
|
178
|
+
pane.classList.add('fx-floated');
|
|
179
|
+
button.textContent = '⇲';
|
|
180
|
+
button.title = `Put ${options.title} back`;
|
|
181
|
+
options.onChange?.(true);
|
|
182
|
+
}
|
|
183
|
+
button.addEventListener('click', (event) => {
|
|
184
|
+
event.stopPropagation();
|
|
185
|
+
if (card)
|
|
186
|
+
dock();
|
|
187
|
+
else
|
|
188
|
+
float();
|
|
189
|
+
});
|
|
190
|
+
return {
|
|
191
|
+
get floating() {
|
|
192
|
+
return card !== null;
|
|
193
|
+
},
|
|
194
|
+
toggle() {
|
|
195
|
+
if (card)
|
|
196
|
+
dock();
|
|
197
|
+
else
|
|
198
|
+
float();
|
|
199
|
+
return card !== null;
|
|
200
|
+
},
|
|
201
|
+
dock,
|
|
202
|
+
destroy() {
|
|
203
|
+
keep.disconnect();
|
|
204
|
+
dock();
|
|
205
|
+
button.remove();
|
|
206
|
+
pane.classList.remove('fx-floatable');
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
export const FLOAT_CSS = `
|
|
211
|
+
.fx-floatable { position: relative; }
|
|
212
|
+
.fx-float-btn {
|
|
213
|
+
position: absolute; top: 2px; right: 2px; z-index: 5;
|
|
214
|
+
width: 18px; height: 18px; padding: 0; line-height: 1;
|
|
215
|
+
font-size: 11px; color: var(--fx-muted, #8a90a2);
|
|
216
|
+
background: var(--fx-panel, #14161c);
|
|
217
|
+
border: 1px solid var(--fx-line, #262a35); border-radius: 4px;
|
|
218
|
+
cursor: pointer; opacity: 0; transition: opacity .12s;
|
|
219
|
+
}
|
|
220
|
+
.fx-floatable:hover .fx-float-btn, .fx-float-btn:focus-visible { opacity: 1; }
|
|
221
|
+
.fx-float-btn:hover { color: var(--fx-accent, #5eead4); border-color: var(--fx-accent, #5eead4); }
|
|
222
|
+
|
|
223
|
+
.fx-float {
|
|
224
|
+
position: fixed; z-index: 60; display: flex; flex-direction: column;
|
|
225
|
+
background: var(--fx-panel, #14161c);
|
|
226
|
+
border: 1px solid var(--fx-line, #262a35); border-radius: 8px;
|
|
227
|
+
box-shadow: 0 12px 34px rgba(0, 0, 0, .5); overflow: hidden;
|
|
228
|
+
}
|
|
229
|
+
.fx-float-bar {
|
|
230
|
+
display: flex; align-items: center; padding: 4px 8px; cursor: move;
|
|
231
|
+
font-size: 11px; color: var(--fx-muted, #8a90a2);
|
|
232
|
+
background: var(--fx-bg, #0f1115); border-bottom: 1px solid var(--fx-line, #262a35);
|
|
233
|
+
user-select: none;
|
|
234
|
+
}
|
|
235
|
+
/* The pane fills the card, whatever it was sized to in the layout it came from. */
|
|
236
|
+
.fx-float > .fx-floated {
|
|
237
|
+
flex: 1; min-height: 0; width: auto; height: auto; overflow: auto;
|
|
238
|
+
}
|
|
239
|
+
/* Edges and corners. Thin, and over everything, so a card can be resized from whichever
|
|
240
|
+
side is reachable. */
|
|
241
|
+
.fx-float-h { position: absolute; z-index: 1; }
|
|
242
|
+
.fx-float-n, .fx-float-s { left: 8px; right: 8px; height: 6px; cursor: ns-resize; }
|
|
243
|
+
.fx-float-e, .fx-float-w { top: 8px; bottom: 8px; width: 6px; cursor: ew-resize; }
|
|
244
|
+
.fx-float-n { top: -3px; }
|
|
245
|
+
.fx-float-s { bottom: -3px; }
|
|
246
|
+
.fx-float-e { right: -3px; }
|
|
247
|
+
.fx-float-w { left: -3px; }
|
|
248
|
+
.fx-float-ne, .fx-float-nw, .fx-float-se, .fx-float-sw { width: 14px; height: 14px; }
|
|
249
|
+
.fx-float-nw { top: -3px; left: -3px; cursor: nwse-resize; }
|
|
250
|
+
.fx-float-ne { top: -3px; right: -3px; cursor: nesw-resize; }
|
|
251
|
+
.fx-float-sw { bottom: -3px; left: -3px; cursor: nesw-resize; }
|
|
252
|
+
.fx-float-se {
|
|
253
|
+
bottom: 0; right: 0; cursor: nwse-resize;
|
|
254
|
+
background: linear-gradient(135deg, transparent 50%, var(--fx-line, #262a35) 50%);
|
|
255
|
+
}
|
|
256
|
+
`;
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A printable rendering of any value. Never throws, never leaves `[object Object]`.
|
|
3
|
+
*
|
|
4
|
+
* `limit` caps the result; what is cut ends in an ellipsis.
|
|
5
|
+
*/
|
|
6
|
+
export declare function formatValue(value: unknown, limit?: number): string;
|
|
7
|
+
/** The short form a node card has room for. */
|
|
8
|
+
export declare function shorten(text: string, limit?: number): string;
|
|
9
|
+
/**
|
|
10
|
+
* The same text across lines, so a value too wide to read is readable.
|
|
11
|
+
*
|
|
12
|
+
* Works on what `formatValue` produced rather than on the value itself: a panel elsewhere
|
|
13
|
+
* has only the string. Quotes are tracked so a brace inside one is left alone.
|
|
14
|
+
*/
|
|
15
|
+
export declare function indentValue(text: string, pad?: string): string;
|
|
16
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAoHA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAM,GAAG,MAAM,CAQ/D;AAED,+CAA+C;AAC/C,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,MAAM,CAExD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAO,GAAG,MAAM,CA4C5D"}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// Turning a live value into something a graph can print.
|
|
2
|
+
//
|
|
3
|
+
// Not JSON.stringify: a signal holds whatever the author put in it — a Map, a Date, a class
|
|
4
|
+
// instance, a dom node, something that refers to itself, a getter that throws — and
|
|
5
|
+
// stringify answers `{}`, `[object Object]` or an exception for most of that. This walks the
|
|
6
|
+
// value instead, so every case has an answer and none of them is a crash.
|
|
7
|
+
//
|
|
8
|
+
// It is a label, not a serializer. Depth and length are capped, and what is cut says so.
|
|
9
|
+
const MAX_DEPTH = 3;
|
|
10
|
+
const MAX_ENTRIES = 8;
|
|
11
|
+
/** A dom node, without assuming this realm's Node — an iframe's is a different class. */
|
|
12
|
+
function domName(v) {
|
|
13
|
+
if (typeof v?.nodeType !== 'number' || typeof v.nodeName !== 'string')
|
|
14
|
+
return null;
|
|
15
|
+
return v.tagName ? `<${v.tagName.toLowerCase()}>` : `#${v.nodeName.toLowerCase()}`;
|
|
16
|
+
}
|
|
17
|
+
function fnName(v) {
|
|
18
|
+
return v.name ? `ƒ ${v.name}()` : 'ƒ';
|
|
19
|
+
}
|
|
20
|
+
/** The constructor's name, when it says more than "Object" would. */
|
|
21
|
+
function className(v) {
|
|
22
|
+
const name = v.constructor?.name;
|
|
23
|
+
return name && name !== 'Object' ? `${name} ` : '';
|
|
24
|
+
}
|
|
25
|
+
function primitive(v) {
|
|
26
|
+
if (v === null)
|
|
27
|
+
return 'null';
|
|
28
|
+
switch (typeof v) {
|
|
29
|
+
case 'undefined':
|
|
30
|
+
return 'undefined';
|
|
31
|
+
case 'string':
|
|
32
|
+
return JSON.stringify(v);
|
|
33
|
+
case 'number':
|
|
34
|
+
case 'boolean':
|
|
35
|
+
return String(v);
|
|
36
|
+
case 'bigint':
|
|
37
|
+
return `${v}n`;
|
|
38
|
+
case 'symbol':
|
|
39
|
+
return v.toString();
|
|
40
|
+
case 'function':
|
|
41
|
+
return fnName(v);
|
|
42
|
+
default:
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function walk(value, depth, seen) {
|
|
47
|
+
const simple = primitive(value);
|
|
48
|
+
if (simple !== null)
|
|
49
|
+
return simple;
|
|
50
|
+
const object = value;
|
|
51
|
+
if (seen.has(object))
|
|
52
|
+
return '[circular]';
|
|
53
|
+
const dom = domName(object);
|
|
54
|
+
if (dom)
|
|
55
|
+
return dom;
|
|
56
|
+
if (object instanceof Date)
|
|
57
|
+
return Number.isNaN(object.getTime()) ? 'Invalid Date' : object.toISOString();
|
|
58
|
+
if (object instanceof RegExp)
|
|
59
|
+
return String(object);
|
|
60
|
+
if (object instanceof Error)
|
|
61
|
+
return `${object.name}: ${object.message}`;
|
|
62
|
+
if (object instanceof Promise)
|
|
63
|
+
return 'Promise';
|
|
64
|
+
if (ArrayBuffer.isView(object)) {
|
|
65
|
+
return `${className(object).trim()}(${object.length ?? ''})`;
|
|
66
|
+
}
|
|
67
|
+
if (depth >= MAX_DEPTH)
|
|
68
|
+
return Array.isArray(object) ? '[…]' : '{…}';
|
|
69
|
+
seen.add(object);
|
|
70
|
+
try {
|
|
71
|
+
if (Array.isArray(object)) {
|
|
72
|
+
const shown = object.slice(0, MAX_ENTRIES).map((item) => walk(item, depth + 1, seen));
|
|
73
|
+
if (object.length > MAX_ENTRIES)
|
|
74
|
+
shown.push('…');
|
|
75
|
+
return `[${shown.join(', ')}]`;
|
|
76
|
+
}
|
|
77
|
+
if (object instanceof Map) {
|
|
78
|
+
const shown = [];
|
|
79
|
+
for (const [k, v] of object) {
|
|
80
|
+
if (shown.length === MAX_ENTRIES) {
|
|
81
|
+
shown.push('…');
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
shown.push(`${walk(k, depth + 1, seen)} => ${walk(v, depth + 1, seen)}`);
|
|
85
|
+
}
|
|
86
|
+
return `Map(${object.size}) {${shown.join(', ')}}`;
|
|
87
|
+
}
|
|
88
|
+
if (object instanceof Set) {
|
|
89
|
+
const shown = [];
|
|
90
|
+
for (const item of object) {
|
|
91
|
+
if (shown.length === MAX_ENTRIES) {
|
|
92
|
+
shown.push('…');
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
shown.push(walk(item, depth + 1, seen));
|
|
96
|
+
}
|
|
97
|
+
return `Set(${object.size}) {${shown.join(', ')}}`;
|
|
98
|
+
}
|
|
99
|
+
const keys = Object.keys(object);
|
|
100
|
+
const shown = keys.slice(0, MAX_ENTRIES).map((key) => {
|
|
101
|
+
// A getter can throw, and a proxy can refuse. Neither is worth losing the whole value.
|
|
102
|
+
let read;
|
|
103
|
+
try {
|
|
104
|
+
read = walk(object[key], depth + 1, seen);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
read = '[unreadable]';
|
|
108
|
+
}
|
|
109
|
+
return `${key}: ${read}`;
|
|
110
|
+
});
|
|
111
|
+
if (keys.length > MAX_ENTRIES)
|
|
112
|
+
shown.push('…');
|
|
113
|
+
return `${className(object)}{${shown.join(', ')}}`;
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
seen.delete(object);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* A printable rendering of any value. Never throws, never leaves `[object Object]`.
|
|
121
|
+
*
|
|
122
|
+
* `limit` caps the result; what is cut ends in an ellipsis.
|
|
123
|
+
*/
|
|
124
|
+
export function formatValue(value, limit = 240) {
|
|
125
|
+
let text;
|
|
126
|
+
try {
|
|
127
|
+
text = walk(value, 0, new Set());
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
text = '[unreadable]';
|
|
131
|
+
}
|
|
132
|
+
return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
|
|
133
|
+
}
|
|
134
|
+
/** The short form a node card has room for. */
|
|
135
|
+
export function shorten(text, limit = 18) {
|
|
136
|
+
return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The same text across lines, so a value too wide to read is readable.
|
|
140
|
+
*
|
|
141
|
+
* Works on what `formatValue` produced rather than on the value itself: a panel elsewhere
|
|
142
|
+
* has only the string. Quotes are tracked so a brace inside one is left alone.
|
|
143
|
+
*/
|
|
144
|
+
export function indentValue(text, pad = ' ') {
|
|
145
|
+
const out = [];
|
|
146
|
+
let depth = 0;
|
|
147
|
+
let quote = null;
|
|
148
|
+
let line = '';
|
|
149
|
+
const flush = () => {
|
|
150
|
+
if (line.trim())
|
|
151
|
+
out.push(pad.repeat(depth) + line.trim());
|
|
152
|
+
line = '';
|
|
153
|
+
};
|
|
154
|
+
for (let i = 0; i < text.length; i++) {
|
|
155
|
+
const c = text[i];
|
|
156
|
+
if (quote) {
|
|
157
|
+
line += c;
|
|
158
|
+
if (c === quote && text[i - 1] !== '\\')
|
|
159
|
+
quote = null;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (c === '"' || c === "'") {
|
|
163
|
+
quote = c;
|
|
164
|
+
line += c;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (c === '{' || c === '[') {
|
|
168
|
+
line += c;
|
|
169
|
+
flush();
|
|
170
|
+
depth++;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (c === '}' || c === ']') {
|
|
174
|
+
flush();
|
|
175
|
+
depth = Math.max(0, depth - 1);
|
|
176
|
+
line = c;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (c === ',') {
|
|
180
|
+
line += c;
|
|
181
|
+
flush();
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
line += c;
|
|
185
|
+
}
|
|
186
|
+
flush();
|
|
187
|
+
return out.join('\n');
|
|
188
|
+
}
|
package/dist/graph-view.d.ts
CHANGED
|
@@ -1,12 +1,89 @@
|
|
|
1
|
-
import type { RNode } from './instrument.js';
|
|
1
|
+
import type { RNode, NodeKind } from './instrument.js';
|
|
2
|
+
/** Which way a dependency runs on screen: left to right, or top to bottom. */
|
|
3
|
+
export type GraphDirection = 'horizontal' | 'vertical';
|
|
2
4
|
export interface GraphViewOptions {
|
|
3
5
|
onSelect?: (node: RNode | null) => void;
|
|
6
|
+
/**
|
|
7
|
+
* Double-clicking a card. Given the node so the host can pick which of its locations to
|
|
8
|
+
* open: a component has one for where it was declared and another for where it was used.
|
|
9
|
+
*/
|
|
10
|
+
onReveal?: (node: RNode) => void;
|
|
11
|
+
/**
|
|
12
|
+
* A wide panel reads left to right; a tall narrow one, which is what a side panel usually
|
|
13
|
+
* is, reads top to bottom.
|
|
14
|
+
* @default 'horizontal'
|
|
15
|
+
*/
|
|
16
|
+
direction?: GraphDirection;
|
|
17
|
+
/**
|
|
18
|
+
* Fold a store's key signals into the store card until it is opened. A cart with four
|
|
19
|
+
* items is a dozen signals nobody asked to see, and the store already shows their value.
|
|
20
|
+
* @default true
|
|
21
|
+
*/
|
|
22
|
+
collapseStores?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Put what a dependency made, and the application never asked for, behind one card. A
|
|
25
|
+
* router's revalidation counter is real state, and it is not what anyone opened the graph
|
|
26
|
+
* to look at.
|
|
27
|
+
* @default true
|
|
28
|
+
*/
|
|
29
|
+
groupExternal?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Draw a card for each place a signal reaches the document, and each place it is set from.
|
|
32
|
+
*
|
|
33
|
+
* Off by default: one card per attribute and text hole is more cards than nodes on most
|
|
34
|
+
* pages, and the graph is about the nodes. On, it is the whole chain: handler, signal,
|
|
35
|
+
* memo, element.
|
|
36
|
+
* @default false
|
|
37
|
+
*/
|
|
38
|
+
showBindings?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Draw only these kinds. Omitted, or null, draws every kind, which is what an empty
|
|
41
|
+
* filter means to a reader: no filter, rather than nothing to see.
|
|
42
|
+
*/
|
|
43
|
+
kinds?: Iterable<NodeKind> | null;
|
|
44
|
+
/** The project a path is shown relative to. See the inspector's option of the same name. */
|
|
45
|
+
root?: string;
|
|
4
46
|
}
|
|
5
47
|
export interface GraphView {
|
|
6
48
|
update(graph: Map<number, RNode>, now?: number): void;
|
|
7
49
|
clear(): void;
|
|
8
50
|
select(id: number | null): void;
|
|
51
|
+
/** Multiply the zoom, about the middle of the view. */
|
|
52
|
+
zoomBy(factor: number): void;
|
|
53
|
+
/** Back to 1:1, everything in frame. */
|
|
54
|
+
resetView(): void;
|
|
55
|
+
/** Open or fold a node that has children, such as a store. Returns what it now is. */
|
|
56
|
+
toggle(id: number): boolean;
|
|
57
|
+
/** Lay the graph out along the other axis. */
|
|
58
|
+
setDirection(direction: GraphDirection): void;
|
|
59
|
+
readonly direction: GraphDirection;
|
|
60
|
+
/** Show what dependencies made as one card, or as themselves. */
|
|
61
|
+
setGroupExternal(group: boolean): void;
|
|
62
|
+
readonly groupExternal: boolean;
|
|
63
|
+
/** Show the document and the handlers as cards around the signals. */
|
|
64
|
+
setShowBindings(show: boolean): void;
|
|
65
|
+
readonly showBindings: boolean;
|
|
66
|
+
/** Draw only these kinds; null for all of them. */
|
|
67
|
+
setKinds(kinds: Iterable<NodeKind> | null): void;
|
|
68
|
+
readonly kinds: ReadonlySet<NodeKind> | null;
|
|
69
|
+
/**
|
|
70
|
+
* The graph as drawn, cards included.
|
|
71
|
+
*
|
|
72
|
+
* What a host should resolve a selected id against: the view invents nodes the host's own
|
|
73
|
+
* map has never heard of.
|
|
74
|
+
*/
|
|
75
|
+
readonly shown: Map<number, RNode>;
|
|
76
|
+
/** Hold a set lit until called again. Hovering works over it and returns here. */
|
|
77
|
+
focus(ids: readonly number[] | null): void;
|
|
9
78
|
}
|
|
10
79
|
export declare function createGraphView(svg: SVGSVGElement, options?: GraphViewOptions): GraphView;
|
|
11
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Default theme.
|
|
82
|
+
*
|
|
83
|
+
* Every colour reads a custom property first, so a host that defines them, a page with a
|
|
84
|
+
* light mode say, restyles the graph without replacing this. The fallbacks are the dark
|
|
85
|
+
* values it always had. The four node hues stay as they are: here colour is the distinction
|
|
86
|
+
* being drawn, not decoration.
|
|
87
|
+
*/
|
|
88
|
+
export declare const DEVTOOLS_CSS: string;
|
|
12
89
|
//# sourceMappingURL=graph-view.d.ts.map
|
package/dist/graph-view.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-view.d.ts","sourceRoot":"","sources":["../src/graph-view.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"graph-view.d.ts","sourceRoot":"","sources":["../src/graph-view.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAgHvD,8EAA8E;AAC9E,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,UAAU,CAAC;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,CAAC;IACxC;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC;;;;OAIG;IACH,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,KAAK,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IAClC,4FAA4F;IAC5F,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtD,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IAChC,uDAAuD;IACvD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,wCAAwC;IACxC,SAAS,IAAI,IAAI,CAAC;IAClB,sFAAsF;IACtF,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B,8CAA8C;IAC9C,YAAY,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9C,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,iEAAiE;IACjE,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,sEAAsE;IACtE,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,mDAAmD;IACnD,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;IACjD,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IAC7C;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACnC,kFAAkF;IAClF,KAAK,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;CAC5C;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS,CAuzB7F;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,QAgDxB,CAAC"}
|