@fluixi/devtools 0.2.0-alpha.2 → 0.2.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -2
- package/dist/callsite.cjs +72 -0
- package/dist/callsite.d.ts +22 -0
- package/dist/callsite.d.ts.map +1 -0
- package/dist/callsite.js +82 -0
- package/dist/callsite.mjs +51 -0
- package/dist/change-view.cjs +134 -0
- package/dist/change-view.d.ts +10 -0
- package/dist/change-view.d.ts.map +1 -0
- package/dist/change-view.js +102 -0
- package/dist/change-view.mjs +111 -0
- package/dist/copy.cjs +63 -0
- package/dist/copy.d.ts +9 -0
- package/dist/copy.d.ts.map +1 -0
- package/dist/copy.js +52 -0
- package/dist/copy.mjs +42 -0
- package/dist/describe.cjs +55 -0
- package/dist/describe.d.ts +3 -0
- package/dist/describe.d.ts.map +1 -0
- package/dist/describe.js +47 -0
- package/dist/describe.mjs +34 -0
- package/dist/detail-panel.cjs +307 -0
- package/dist/detail-panel.d.ts +17 -0
- package/dist/detail-panel.d.ts.map +1 -0
- package/dist/detail-panel.js +217 -0
- package/dist/detail-panel.mjs +284 -0
- package/dist/diff.cjs +225 -0
- package/dist/diff.d.ts +31 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +132 -0
- package/dist/diff.mjs +202 -0
- package/dist/dom.cjs +204 -0
- package/dist/dom.d.ts +81 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +269 -0
- package/dist/dom.mjs +183 -0
- package/dist/export.cjs +221 -0
- package/dist/export.d.ts +55 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +90 -0
- package/dist/export.mjs +209 -0
- package/dist/float.cjs +232 -0
- package/dist/float.d.ts +54 -0
- package/dist/float.d.ts.map +1 -0
- package/dist/float.js +256 -0
- package/dist/float.mjs +211 -0
- package/dist/format.cjs +175 -0
- package/dist/format.d.ts +16 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +188 -0
- package/dist/format.mjs +154 -0
- package/dist/graph-view.cjs +893 -0
- package/dist/graph-view.d.ts +79 -2
- package/dist/graph-view.d.ts.map +1 -1
- package/dist/graph-view.js +866 -50
- package/dist/graph-view.mjs +870 -0
- package/dist/highlight.cjs +140 -0
- package/dist/highlight.d.ts +56 -0
- package/dist/highlight.d.ts.map +1 -0
- package/dist/highlight.js +154 -0
- package/dist/highlight.mjs +119 -0
- package/dist/history.cjs +95 -0
- package/dist/history.d.ts +59 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +85 -0
- package/dist/history.mjs +74 -0
- package/dist/hook.cjs +1562 -0
- package/dist/hook.d.ts +60 -0
- package/dist/hook.d.ts.map +1 -0
- package/dist/hook.js +142 -0
- package/dist/hook.mjs +1553 -0
- package/dist/index.cjs +4552 -0
- package/dist/index.d.ts +44 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -1
- package/dist/index.mjs +4544 -0
- package/dist/inspector.cjs +688 -0
- package/dist/inspector.d.ts +60 -0
- package/dist/inspector.d.ts.map +1 -0
- package/dist/inspector.js +415 -0
- package/dist/inspector.mjs +676 -0
- package/dist/instrument.cjs +497 -0
- package/dist/instrument.d.ts +147 -5
- package/dist/instrument.d.ts.map +1 -1
- package/dist/instrument.js +164 -44
- package/dist/instrument.mjs +487 -0
- package/dist/kind-filter.cjs +172 -0
- package/dist/kind-filter.d.ts +39 -0
- package/dist/kind-filter.d.ts.map +1 -0
- package/dist/kind-filter.js +151 -0
- package/dist/kind-filter.mjs +151 -0
- package/dist/menu.cjs +141 -0
- package/dist/menu.d.ts +37 -0
- package/dist/menu.d.ts.map +1 -0
- package/dist/menu.js +125 -0
- package/dist/menu.mjs +120 -0
- package/dist/observe.cjs +1352 -0
- package/dist/observe.d.ts +80 -0
- package/dist/observe.d.ts.map +1 -0
- package/dist/observe.js +893 -0
- package/dist/observe.mjs +1345 -0
- package/dist/palette.cjs +52 -0
- package/dist/palette.d.ts +11 -0
- package/dist/palette.d.ts.map +1 -0
- package/dist/palette.js +27 -0
- package/dist/palette.mjs +31 -0
- package/dist/path.cjs +45 -0
- package/dist/path.d.ts +19 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +49 -0
- package/dist/path.mjs +24 -0
- package/dist/plugin.cjs +87 -0
- package/dist/plugin.d.ts +121 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +74 -0
- package/dist/plugin.mjs +66 -0
- package/dist/plugins/context.cjs +98 -0
- package/dist/plugins/context.d.ts +10 -0
- package/dist/plugins/context.d.ts.map +1 -0
- package/dist/plugins/context.js +80 -0
- package/dist/plugins/context.mjs +75 -0
- package/dist/plugins/directives.cjs +98 -0
- package/dist/plugins/directives.d.ts +25 -0
- package/dist/plugins/directives.d.ts.map +1 -0
- package/dist/plugins/directives.js +78 -0
- package/dist/plugins/directives.mjs +75 -0
- package/dist/plugins/index.cjs +162 -0
- package/dist/plugins/index.d.ts +7 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +9 -0
- package/dist/plugins/index.mjs +140 -0
- package/dist/source.cjs +111 -0
- package/dist/source.d.ts +72 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +153 -0
- package/dist/source.mjs +90 -0
- package/dist/timeline.cjs +350 -0
- package/dist/timeline.d.ts +30 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +271 -0
- package/dist/timeline.mjs +327 -0
- package/dist/tree.cjs +574 -0
- package/dist/tree.d.ts +128 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +645 -0
- package/dist/tree.mjs +551 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/value-pane.cjs +223 -0
- package/dist/value-pane.d.ts +10 -0
- package/dist/value-pane.d.ts.map +1 -0
- package/dist/value-pane.js +61 -0
- package/dist/value-pane.mjs +200 -0
- package/dist/wire.cjs +109 -0
- package/dist/wire.d.ts +100 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +93 -0
- package/dist/wire.mjs +89 -0
- package/package.json +17 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../src/tree.ts"],"names":[],"mappings":"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"}
|
package/dist/tree.js
ADDED
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
import { pluginBadge, pluginElementBadge } from './plugin.js';
|
|
2
|
+
import { kindRules } from './palette.js';
|
|
3
|
+
import { shorten } from './format.js';
|
|
4
|
+
/** Kinds that are tags in the source, and so have a place in a tree of them. */
|
|
5
|
+
export const TREE_KINDS = new Set([
|
|
6
|
+
'component',
|
|
7
|
+
'control',
|
|
8
|
+
'router',
|
|
9
|
+
'handler',
|
|
10
|
+
// A component a plugin recognised as providing a context. Still a tag the author wrote,
|
|
11
|
+
// so leaving it out took it and everything it rendered out of the tree.
|
|
12
|
+
'provider',
|
|
13
|
+
]);
|
|
14
|
+
/**
|
|
15
|
+
* Put the tags that rendered back into page order, leaving everything else untouched.
|
|
16
|
+
*
|
|
17
|
+
* Sorting by id alone reads as creation order, which is wrong for a keyed list: rebuilding a
|
|
18
|
+
* row hands the replacement the highest id, so the row you edited jumped to the bottom.
|
|
19
|
+
*
|
|
20
|
+
* Only the slots those tags already occupy are reshuffled. Sorting the whole list by `order`
|
|
21
|
+
* instead sent every value to the end of its band, so editing a todo moved the `left` memo
|
|
22
|
+
* as well, which had nothing to do with the change.
|
|
23
|
+
*/
|
|
24
|
+
export function inPageOrder(nodes) {
|
|
25
|
+
const slots = [];
|
|
26
|
+
const placed = [];
|
|
27
|
+
nodes.forEach((node, at) => {
|
|
28
|
+
if (node.order !== undefined) {
|
|
29
|
+
slots.push(at);
|
|
30
|
+
placed.push(node);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
if (placed.length < 2)
|
|
34
|
+
return nodes;
|
|
35
|
+
placed.sort((a, b) => a.order - b.order);
|
|
36
|
+
const out = nodes.slice();
|
|
37
|
+
slots.forEach((slot, i) => (out[slot] = placed[i]));
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
/** Cached per call: a plugin's badge is computed from the node and asked for twice. */
|
|
41
|
+
const badges = new WeakMap();
|
|
42
|
+
function badgeOf(node) {
|
|
43
|
+
if (!badges.has(node))
|
|
44
|
+
badges.set(node, pluginBadge(node));
|
|
45
|
+
return badges.get(node);
|
|
46
|
+
}
|
|
47
|
+
export function componentTree(graph, options = {}) {
|
|
48
|
+
const wanted = (node) => TREE_KINDS.has(node.kind) || (options.values === true && node.authored !== false);
|
|
49
|
+
const tagged = inPageOrder([...graph.values()].filter(wanted).sort((a, b) => a.id - b.id));
|
|
50
|
+
const made = new Map();
|
|
51
|
+
for (const node of tagged) {
|
|
52
|
+
made.set(node.id, {
|
|
53
|
+
id: node.id,
|
|
54
|
+
label: node.label,
|
|
55
|
+
kind: node.kind,
|
|
56
|
+
children: [],
|
|
57
|
+
...(TREE_KINDS.has(node.kind) ? {} : { value: true }),
|
|
58
|
+
...(node.lazy ? { lazy: true } : {}),
|
|
59
|
+
...(badgeOf(node) ? { badge: badgeOf(node) } : {}),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/** The nearest ancestor that is a tag, following what created what. */
|
|
63
|
+
const owner = (node) => {
|
|
64
|
+
const seen = new Set([node.id]);
|
|
65
|
+
let at = node.createdIn;
|
|
66
|
+
while (at !== undefined && !seen.has(at)) {
|
|
67
|
+
seen.add(at);
|
|
68
|
+
const found = made.get(at);
|
|
69
|
+
if (found)
|
|
70
|
+
return found;
|
|
71
|
+
at = graph.get(at)?.createdIn;
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
};
|
|
75
|
+
const roots = [];
|
|
76
|
+
for (const node of tagged) {
|
|
77
|
+
const parent = owner(node);
|
|
78
|
+
if (parent)
|
|
79
|
+
parent.children.push(made.get(node.id));
|
|
80
|
+
else
|
|
81
|
+
roots.push(made.get(node.id));
|
|
82
|
+
}
|
|
83
|
+
const term = options.filter?.trim().toLowerCase();
|
|
84
|
+
return term ? keep(roots, term) : roots;
|
|
85
|
+
}
|
|
86
|
+
/** The nodes naming `term`, each still under whatever held it. */
|
|
87
|
+
function keep(nodes, term) {
|
|
88
|
+
const out = [];
|
|
89
|
+
for (const node of nodes) {
|
|
90
|
+
const children = keep(node.children, term);
|
|
91
|
+
const hit = node.label.toLowerCase().includes(term);
|
|
92
|
+
if (hit || children.length)
|
|
93
|
+
out.push({ ...node, children: hit ? node.children : children });
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
/** Every id in the tree, for a caller that wants to open or shut all of it. */
|
|
98
|
+
export function idsIn(nodes) {
|
|
99
|
+
const out = [];
|
|
100
|
+
const walk = (list) => {
|
|
101
|
+
for (const node of list) {
|
|
102
|
+
out.push(node.id);
|
|
103
|
+
walk(node.children);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
walk(nodes);
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
const escape = (text) => text.replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]);
|
|
110
|
+
export function createComponentTree(host, options = {}) {
|
|
111
|
+
const empty = options.empty ?? 'nothing rendered yet';
|
|
112
|
+
/** Shut by the reader. Everything else is open, so a new subtree arrives visible. */
|
|
113
|
+
const shut = new Set();
|
|
114
|
+
let signature = '';
|
|
115
|
+
/** Prop groups already given their starting fold, so reopening one is not undone. */
|
|
116
|
+
const defaulted = new Set();
|
|
117
|
+
let marked = null;
|
|
118
|
+
/** Element rows have no id, so what is selected is tracked by row key. */
|
|
119
|
+
let markedKey = null;
|
|
120
|
+
let values = options.values === true;
|
|
121
|
+
let filter = '';
|
|
122
|
+
const doc = host.ownerDocument;
|
|
123
|
+
// In the pane rather than the toolbar: it belongs to the tree, and three hosts would
|
|
124
|
+
// otherwise each need a button of their own for it.
|
|
125
|
+
const bar = doc.createElement('label');
|
|
126
|
+
bar.className = 'fx-tree-bar';
|
|
127
|
+
const box = doc.createElement('input');
|
|
128
|
+
box.type = 'checkbox';
|
|
129
|
+
box.checked = values;
|
|
130
|
+
bar.append(box, doc.createTextNode('values'));
|
|
131
|
+
bar.title = 'List the signals and memos each tag declares';
|
|
132
|
+
const search = doc.createElement('input');
|
|
133
|
+
search.type = 'search';
|
|
134
|
+
search.className = 'fx-tree-search';
|
|
135
|
+
search.placeholder = 'filter by name';
|
|
136
|
+
search.addEventListener('input', () => {
|
|
137
|
+
filter = search.value;
|
|
138
|
+
signature = '';
|
|
139
|
+
});
|
|
140
|
+
const list = doc.createElement('div');
|
|
141
|
+
list.className = 'fx-tree-list';
|
|
142
|
+
host.replaceChildren(bar, search, list);
|
|
143
|
+
/** What was last drawn, so a control can redraw without waiting for the app to move. */
|
|
144
|
+
let last = null;
|
|
145
|
+
box.addEventListener('change', () => {
|
|
146
|
+
values = box.checked;
|
|
147
|
+
signature = '';
|
|
148
|
+
// Clearing the signature only marks it stale. On an idle page nothing calls render
|
|
149
|
+
// again, so ticking the box appeared to do nothing at all until something else changed.
|
|
150
|
+
if (last)
|
|
151
|
+
api.render(last.graph, last.selected);
|
|
152
|
+
});
|
|
153
|
+
const row = (node, depth) => {
|
|
154
|
+
const has = node.children.length > 0;
|
|
155
|
+
const open = has && !shut.has(node.key);
|
|
156
|
+
const twist = has ? (open ? '▾' : '▸') : node.prop ? '' : '·';
|
|
157
|
+
const tag = node.props
|
|
158
|
+
? `<span class="fx-tree-props-label">props</span><span class="fx-tree-props-count">${node.children.length}</span>`
|
|
159
|
+
: node.prop
|
|
160
|
+
? `<span class="fx-tree-prop-name">${escape(node.prop.name)}</span>${node.prop.value === undefined
|
|
161
|
+
? ''
|
|
162
|
+
: `<span class="fx-tree-prop-value">${escape(shorten(node.prop.value, 44))}</span>`}`
|
|
163
|
+
: node.value
|
|
164
|
+
? escape(node.label)
|
|
165
|
+
: has
|
|
166
|
+
? `<${escape(node.label)}>`
|
|
167
|
+
: `<${escape(node.label)} />`;
|
|
168
|
+
const count = has && !open && !node.props ? `<span class="fx-tree-count">${node.children.length}</span>` : '';
|
|
169
|
+
// Says the chunk it came from was fetched separately, which is why it appeared late.
|
|
170
|
+
const chunk = node.lazy ? '<span class="fx-tree-lazy" title="loaded as its own chunk">lazy</span>' : '';
|
|
171
|
+
const mark = node.badge
|
|
172
|
+
? `<span class="fx-tree-plugin" title="${escape(node.badge)}">${escape(node.badge)}</span>`
|
|
173
|
+
: '';
|
|
174
|
+
return (`<div class="fx-tree-row n-${node.kind}${node.value ? ' is-value' : ''}${node.prop ? ' is-prop' : ''}${node.props ? ' is-props' : ''}"` +
|
|
175
|
+
` data-key="${escape(node.key)}"${node.id !== undefined ? ` data-id="${node.id}"` : ''}` +
|
|
176
|
+
` style="--depth:${depth}">` +
|
|
177
|
+
`<button type="button" class="fx-tree-twist" data-twist="${escape(node.key)}"${has ? '' : ' tabindex="-1"'}>${twist}</button>` +
|
|
178
|
+
`<span class="fx-tree-tag"${node.prop?.value ? ` title="${escape(node.prop.name)}=${escape(node.prop.value)}"` : ''}>${tag}</span>${mark}${chunk}${count}` +
|
|
179
|
+
`</div>` +
|
|
180
|
+
(open ? node.children.map((child) => row(child, depth + 1)).join('') : ''));
|
|
181
|
+
};
|
|
182
|
+
/** The element a row stands for, out of what was drawn for it. */
|
|
183
|
+
const pickOf = (rowEl) => {
|
|
184
|
+
const found = shown.get(rowEl.dataset.key ?? '');
|
|
185
|
+
if (!found || found.kind !== 'element')
|
|
186
|
+
return undefined;
|
|
187
|
+
return {
|
|
188
|
+
key: found.key,
|
|
189
|
+
label: found.label,
|
|
190
|
+
...(found.owner !== undefined ? { owner: found.owner } : {}),
|
|
191
|
+
...(found.writes?.length ? { writes: found.writes } : {}),
|
|
192
|
+
...(found.at ? { at: found.at } : {}),
|
|
193
|
+
...(found.extra ? { extra: found.extra } : {}),
|
|
194
|
+
...(found.element ? { element: found.element } : {}),
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
const onClick = (event) => {
|
|
198
|
+
const target = event.target;
|
|
199
|
+
const twist = target?.closest?.('[data-twist]');
|
|
200
|
+
if (twist) {
|
|
201
|
+
const key = twist.dataset.twist ?? '';
|
|
202
|
+
if (shut.has(key))
|
|
203
|
+
shut.delete(key);
|
|
204
|
+
else
|
|
205
|
+
shut.add(key);
|
|
206
|
+
signature = '';
|
|
207
|
+
event.stopPropagation();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const rowEl = target?.closest?.('.fx-tree-row');
|
|
211
|
+
if (!rowEl)
|
|
212
|
+
return;
|
|
213
|
+
if (rowEl.dataset.id) {
|
|
214
|
+
markedKey = null;
|
|
215
|
+
options.onSelect?.(Number(rowEl.dataset.id));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
// A prop is not a node of its own, so selecting it selects the tag it was written on,
|
|
219
|
+
// which is where its value came from and what the inspector has something to say about.
|
|
220
|
+
const held = shown.get(rowEl.dataset.key ?? '');
|
|
221
|
+
if (held?.props) {
|
|
222
|
+
if (shut.has(held.key))
|
|
223
|
+
shut.delete(held.key);
|
|
224
|
+
else
|
|
225
|
+
shut.add(held.key);
|
|
226
|
+
signature = '';
|
|
227
|
+
if (last)
|
|
228
|
+
api.render(last.graph, last.selected);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (held?.prop) {
|
|
232
|
+
if (held.owner === undefined)
|
|
233
|
+
return;
|
|
234
|
+
markedKey = held.key;
|
|
235
|
+
paint();
|
|
236
|
+
options.onSelect?.(held.owner);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const pick = pickOf(rowEl);
|
|
240
|
+
if (!pick)
|
|
241
|
+
return;
|
|
242
|
+
markedKey = pick.key;
|
|
243
|
+
paint();
|
|
244
|
+
options.onPick?.(pick);
|
|
245
|
+
};
|
|
246
|
+
const onDouble = (event) => {
|
|
247
|
+
const rowEl = event.target?.closest?.('.fx-tree-row');
|
|
248
|
+
if (!rowEl)
|
|
249
|
+
return;
|
|
250
|
+
if (rowEl.dataset.id) {
|
|
251
|
+
options.onReveal?.(Number(rowEl.dataset.id));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const pick = pickOf(rowEl);
|
|
255
|
+
if (pick)
|
|
256
|
+
options.onRevealPick?.(pick);
|
|
257
|
+
};
|
|
258
|
+
/** Rows by key, so a pointer over one can be answered without searching the tree again. */
|
|
259
|
+
let shown = new Map();
|
|
260
|
+
const onOver = (event) => {
|
|
261
|
+
const rowEl = event.target?.closest?.('.fx-tree-row');
|
|
262
|
+
const found = rowEl ? shown.get(rowEl.dataset.key ?? '') : undefined;
|
|
263
|
+
if (!found) {
|
|
264
|
+
options.onHover?.(null);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
// The key doubles as a path to the element, for a host that has to ask the page to
|
|
268
|
+
// highlight rather than doing it itself.
|
|
269
|
+
options.onHover?.(found.id ?? null, found.element, found.id === undefined ? found.key : undefined);
|
|
270
|
+
};
|
|
271
|
+
const onOut = () => options.onHover?.(null);
|
|
272
|
+
host.addEventListener('click', onClick);
|
|
273
|
+
host.addEventListener('dblclick', onDouble);
|
|
274
|
+
if (options.onHover) {
|
|
275
|
+
host.addEventListener('pointermove', onOver);
|
|
276
|
+
host.addEventListener('pointerleave', onOut);
|
|
277
|
+
}
|
|
278
|
+
const paint = () => {
|
|
279
|
+
for (const el of list.querySelectorAll('.fx-tree-row')) {
|
|
280
|
+
const row = el;
|
|
281
|
+
const id = row.dataset.id;
|
|
282
|
+
const on = id !== undefined ? Number(id) === marked : row.dataset.key === markedKey;
|
|
283
|
+
el.classList.toggle('on', on);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
/**
|
|
287
|
+
* Build the rows, weaving each tag's DOM into the graph tree.
|
|
288
|
+
*
|
|
289
|
+
* A row goes where its element is rather than where the graph made it. A child component
|
|
290
|
+
* sits inside its parent's DOM, so its row goes there; a value goes inside the element it
|
|
291
|
+
* writes into. The second matters for a store declared at module scope, which belongs to
|
|
292
|
+
* no component but still renders somewhere.
|
|
293
|
+
*/
|
|
294
|
+
function rowsFor(nodes, graph) {
|
|
295
|
+
const byId = new Map();
|
|
296
|
+
const build = (node) => {
|
|
297
|
+
// What the tag was handed, ahead of what it produced. A prop is the other half of the
|
|
298
|
+
// story a tag tells: `<For each={todos()}>` says as much through `each` as through the
|
|
299
|
+
// rows it went on to render, and every component takes them.
|
|
300
|
+
const written = values ? (graph.get(node.id)?.props ?? []) : [];
|
|
301
|
+
const listed = written.map((p, i) => ({
|
|
302
|
+
key: `${node.id}=${i}`,
|
|
303
|
+
label: p.name,
|
|
304
|
+
kind: node.kind,
|
|
305
|
+
children: [],
|
|
306
|
+
prop: { name: p.name, ...(p.value === undefined ? {} : { value: p.value }) },
|
|
307
|
+
owner: node.id,
|
|
308
|
+
...(p.at?.line === undefined
|
|
309
|
+
? {}
|
|
310
|
+
: { at: { file: p.at.file ?? '', line: p.at.line, column: p.at.column ?? 0 } }),
|
|
311
|
+
}));
|
|
312
|
+
// One folded row rather than a prop per line. A tag with six of them otherwise buries
|
|
313
|
+
// what it rendered, which is the thing the tree is for.
|
|
314
|
+
const group = `${node.id}=props`;
|
|
315
|
+
// Shut the first time it is met, and whatever the reader does with it after that sticks.
|
|
316
|
+
if (listed.length && !defaulted.has(group)) {
|
|
317
|
+
defaulted.add(group);
|
|
318
|
+
shut.add(group);
|
|
319
|
+
}
|
|
320
|
+
const props = listed.length
|
|
321
|
+
? [{ key: group, label: 'props', kind: node.kind, children: listed, props: true, owner: node.id }]
|
|
322
|
+
: [];
|
|
323
|
+
const row = {
|
|
324
|
+
key: String(node.id),
|
|
325
|
+
label: node.label,
|
|
326
|
+
kind: node.kind,
|
|
327
|
+
id: node.id,
|
|
328
|
+
children: [...props, ...node.children.map(build)],
|
|
329
|
+
...(node.value ? { value: true } : {}),
|
|
330
|
+
...(node.lazy ? { lazy: true } : {}),
|
|
331
|
+
...(node.badge ? { badge: node.badge } : {}),
|
|
332
|
+
};
|
|
333
|
+
byId.set(node.id, row);
|
|
334
|
+
return row;
|
|
335
|
+
};
|
|
336
|
+
const roots = nodes.map(build);
|
|
337
|
+
const domFor = options.domFor;
|
|
338
|
+
if (!domFor)
|
|
339
|
+
return roots;
|
|
340
|
+
const placed = new Set();
|
|
341
|
+
/**
|
|
342
|
+
* Rows already written into the tree. A tag is reachable two ways, down the component
|
|
343
|
+
* nesting and through the element it rendered, and `expand` rewrites the row it is
|
|
344
|
+
* given, so without this the second arrival appends its dom all over again.
|
|
345
|
+
*/
|
|
346
|
+
const done = new Set();
|
|
347
|
+
/**
|
|
348
|
+
* How many places in the dom name each tag.
|
|
349
|
+
*
|
|
350
|
+
* A graph read from source has one node per component *declaration*, so two `<Badge/>`
|
|
351
|
+
* both point at it. Drawing only the first would take the second out of the tree, but
|
|
352
|
+
* a running graph gives each use its own node, where a tag named twice really is one
|
|
353
|
+
* subtree reached two ways and the second must go. Counting tells them apart.
|
|
354
|
+
*/
|
|
355
|
+
const uses = new Map();
|
|
356
|
+
const tally = (nodes) => {
|
|
357
|
+
for (const node of nodes) {
|
|
358
|
+
if (node.owner !== undefined)
|
|
359
|
+
uses.set(node.owner, (uses.get(node.owner) ?? 0) + 1);
|
|
360
|
+
tally(node.children);
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
for (const row of byId.values())
|
|
364
|
+
if (row.id !== undefined)
|
|
365
|
+
tally(domFor(row.id));
|
|
366
|
+
/**
|
|
367
|
+
* The innermost tag row that reads `id`, if one is in this tree.
|
|
368
|
+
*
|
|
369
|
+
* The node doing the reading is often machinery the tree never draws: `For` does not read
|
|
370
|
+
* its list, `mapArray` does, and that is hidden. So climb from whatever reads it to the
|
|
371
|
+
* nearest tag that is drawn, which is the `For`.
|
|
372
|
+
*
|
|
373
|
+
* Deepest wins, so a value read by a component inside a `For` belongs to the component.
|
|
374
|
+
* `skip` keeps the tag that declared it from adopting it back.
|
|
375
|
+
*/
|
|
376
|
+
const readerOf = (id, skip) => {
|
|
377
|
+
const drawn = (from) => {
|
|
378
|
+
const seen = new Set();
|
|
379
|
+
let at = from;
|
|
380
|
+
while (at !== undefined && !seen.has(at)) {
|
|
381
|
+
seen.add(at);
|
|
382
|
+
const row = byId.get(at);
|
|
383
|
+
if (row && !row.value && row !== skip)
|
|
384
|
+
return row;
|
|
385
|
+
at = graph.get(at)?.createdIn;
|
|
386
|
+
}
|
|
387
|
+
return undefined;
|
|
388
|
+
};
|
|
389
|
+
let best;
|
|
390
|
+
let deepest = -1;
|
|
391
|
+
for (const [nodeId, node] of graph) {
|
|
392
|
+
if (nodeId === id || !node.deps.has(id))
|
|
393
|
+
continue;
|
|
394
|
+
const row = drawn(nodeId);
|
|
395
|
+
if (!row || row.id === undefined)
|
|
396
|
+
continue;
|
|
397
|
+
const depth = graph.get(row.id)?.depth ?? 0;
|
|
398
|
+
if (depth >= deepest) {
|
|
399
|
+
deepest = depth;
|
|
400
|
+
best = row;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return best;
|
|
404
|
+
};
|
|
405
|
+
/** The same row again at another place, re-keyed so the two fold independently. */
|
|
406
|
+
const again = (row, key) => ({
|
|
407
|
+
...row,
|
|
408
|
+
key,
|
|
409
|
+
children: row.children.map((child, i) => again(child, `${key}/${i}`)),
|
|
410
|
+
});
|
|
411
|
+
const expand = (row) => {
|
|
412
|
+
if (row.value || row.id === undefined)
|
|
413
|
+
return row;
|
|
414
|
+
if (done.has(row))
|
|
415
|
+
return row;
|
|
416
|
+
done.add(row);
|
|
417
|
+
const dom = domFor(row.id);
|
|
418
|
+
if (!dom.length) {
|
|
419
|
+
row.children = row.children.map(expand);
|
|
420
|
+
return row;
|
|
421
|
+
}
|
|
422
|
+
const walk = (node, key) => {
|
|
423
|
+
const owner = node.owner === undefined ? undefined : byId.get(node.owner);
|
|
424
|
+
if (owner && owner !== row) {
|
|
425
|
+
if (done.has(owner)) {
|
|
426
|
+
// Named from one place only, so it is already showing where it belongs and
|
|
427
|
+
// drawing it here as well would be the same subtree twice.
|
|
428
|
+
const count = uses.get(node.owner) ?? 0;
|
|
429
|
+
return count > 1 ? again(owner, key) : undefined;
|
|
430
|
+
}
|
|
431
|
+
placed.add(owner);
|
|
432
|
+
return expand(owner);
|
|
433
|
+
}
|
|
434
|
+
const kids = [];
|
|
435
|
+
// Values first. They are the reason this element changes.
|
|
436
|
+
for (const id of node.writes ?? []) {
|
|
437
|
+
const value = byId.get(id);
|
|
438
|
+
if (!value?.value || placed.has(value))
|
|
439
|
+
continue;
|
|
440
|
+
placed.add(value);
|
|
441
|
+
kids.push(value);
|
|
442
|
+
}
|
|
443
|
+
// Then tags that render in here without an element of their own, so they read as
|
|
444
|
+
// being inside it rather than next to it.
|
|
445
|
+
for (const id of node.inside ?? []) {
|
|
446
|
+
const tag = byId.get(id);
|
|
447
|
+
if (!tag || tag === row || placed.has(tag) || done.has(tag))
|
|
448
|
+
continue;
|
|
449
|
+
placed.add(tag);
|
|
450
|
+
kids.push(expand(tag));
|
|
451
|
+
}
|
|
452
|
+
node.children.forEach((child, i) => {
|
|
453
|
+
const kid = walk(child, `${key}/${i}`);
|
|
454
|
+
if (kid)
|
|
455
|
+
kids.push(kid);
|
|
456
|
+
});
|
|
457
|
+
return {
|
|
458
|
+
key,
|
|
459
|
+
label: node.label,
|
|
460
|
+
kind: 'element',
|
|
461
|
+
children: kids,
|
|
462
|
+
owner: row.id,
|
|
463
|
+
...(node.writes?.length ? { writes: node.writes } : {}),
|
|
464
|
+
...(node.at ? { at: node.at } : {}),
|
|
465
|
+
...(node.extra ? { extra: node.extra } : {}),
|
|
466
|
+
...(pluginElementBadge(node.extra) ? { badge: pluginElementBadge(node.extra) } : {}),
|
|
467
|
+
...(node.element ? { element: node.element } : {}),
|
|
468
|
+
};
|
|
469
|
+
};
|
|
470
|
+
// The mount container was on the page before anything rendered, so the tag goes inside
|
|
471
|
+
// it rather than the other way round.
|
|
472
|
+
const mount = dom.findIndex((node) => node.container);
|
|
473
|
+
if (mount !== -1) {
|
|
474
|
+
const node = dom[mount];
|
|
475
|
+
row.children = row.children.map(expand);
|
|
476
|
+
return {
|
|
477
|
+
key: `${row.id}:${mount}`,
|
|
478
|
+
label: node.label,
|
|
479
|
+
kind: 'element',
|
|
480
|
+
children: [row],
|
|
481
|
+
owner: row.id,
|
|
482
|
+
...(node.at ? { at: node.at } : {}),
|
|
483
|
+
...(node.element ? { element: node.element } : {}),
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
const rendered = dom
|
|
487
|
+
.map((node, i) => walk(node, `${row.id}:${i}`))
|
|
488
|
+
.filter((node) => node !== undefined);
|
|
489
|
+
// Anything the dom did not account for: a portal, or a value read across a tag
|
|
490
|
+
// boundary rather than written into an element here.
|
|
491
|
+
//
|
|
492
|
+
// Above the markup, not below it. A signal a nested tag consumes writes nowhere this
|
|
493
|
+
// description reaches, so it never nests beside an element, and appending it left
|
|
494
|
+
// `todos` alone at the bottom of `App` well away from the `For` that reads it.
|
|
495
|
+
const rest = row.children.filter((child) => !placed.has(child)).map(expand);
|
|
496
|
+
// A value the dom cannot place goes under the tag that reads it.
|
|
497
|
+
//
|
|
498
|
+
// `draft` nests under the `<input>` it writes into, because the description names the
|
|
499
|
+
// values writing to each element. `todos` writes into no element here: `For` reads it
|
|
500
|
+
// and renders the rows itself, so it had nowhere to go and sat loose under `App`. The
|
|
501
|
+
// tag that depends on it is the honest place, and it reads the same way as `draft`.
|
|
502
|
+
const lifted = [];
|
|
503
|
+
const spare = [];
|
|
504
|
+
/** Collected per reader so several values keep their order when they go in. */
|
|
505
|
+
const adopt = new Map();
|
|
506
|
+
for (const child of rest) {
|
|
507
|
+
const reader = child.value && child.id !== undefined ? readerOf(child.id, row) : undefined;
|
|
508
|
+
if (reader) {
|
|
509
|
+
const held = adopt.get(reader);
|
|
510
|
+
if (held)
|
|
511
|
+
held.push(child);
|
|
512
|
+
else
|
|
513
|
+
adopt.set(reader, [child]);
|
|
514
|
+
placed.add(child);
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
(child.value || child.prop || child.props ? lifted : spare).push(child);
|
|
518
|
+
}
|
|
519
|
+
// Ahead of what the tag rendered. A value reads as what the tag was given, so it
|
|
520
|
+
// belongs above the rows it produced rather than after them.
|
|
521
|
+
for (const [reader, values] of adopt)
|
|
522
|
+
reader.children.unshift(...values);
|
|
523
|
+
// Whatever is left: what the tag was given first, then a portal, which renders
|
|
524
|
+
// elsewhere and still reads as part of the markup.
|
|
525
|
+
row.children = [...lifted, ...rendered, ...spare];
|
|
526
|
+
return row;
|
|
527
|
+
};
|
|
528
|
+
// Mapped, not just visited: expanding a root can return a different row, because the
|
|
529
|
+
// container it was mounted into ends up above it.
|
|
530
|
+
const top = roots.map((root) => (placed.has(root) ? root : expand(root)));
|
|
531
|
+
return top.filter((_, i) => !placed.has(roots[i]));
|
|
532
|
+
}
|
|
533
|
+
const api = {
|
|
534
|
+
render(graph, selected = null) {
|
|
535
|
+
last = { graph, selected };
|
|
536
|
+
const roots = rowsFor(componentTree(graph, { values, filter }), graph);
|
|
537
|
+
// Rebuilt only when the shape changes: this runs every frame, and replacing the markup
|
|
538
|
+
// each time would drop whatever the reader was about to click.
|
|
539
|
+
const next = `${values}|${filter}|${[...shut].sort().join(',')}|${roots.map(function label(n) {
|
|
540
|
+
return `${n.key}:${n.label}:${n.children.map(label).join('+')}`;
|
|
541
|
+
}).join(',')}`;
|
|
542
|
+
if (next !== signature) {
|
|
543
|
+
signature = next;
|
|
544
|
+
shown = new Map();
|
|
545
|
+
const index = (list) => {
|
|
546
|
+
for (const node of list) {
|
|
547
|
+
shown.set(node.key, node);
|
|
548
|
+
index(node.children);
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
index(roots);
|
|
552
|
+
list.innerHTML = roots.length
|
|
553
|
+
? roots.map((node) => row(node, 0)).join('')
|
|
554
|
+
: `<span class="fx-tree-empty">${escape(filter ? `nothing named ${filter}` : empty)}</span>`;
|
|
555
|
+
}
|
|
556
|
+
if (selected !== marked) {
|
|
557
|
+
marked = selected;
|
|
558
|
+
paint();
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
clear() {
|
|
562
|
+
signature = '';
|
|
563
|
+
marked = null;
|
|
564
|
+
shut.clear();
|
|
565
|
+
list.innerHTML = `<span class="fx-tree-empty">${escape(empty)}</span>`;
|
|
566
|
+
},
|
|
567
|
+
destroy() {
|
|
568
|
+
host.removeEventListener('click', onClick);
|
|
569
|
+
host.removeEventListener('dblclick', onDouble);
|
|
570
|
+
host.removeEventListener('pointermove', onOver);
|
|
571
|
+
host.removeEventListener('pointerleave', onOut);
|
|
572
|
+
host.innerHTML = '';
|
|
573
|
+
},
|
|
574
|
+
};
|
|
575
|
+
return api;
|
|
576
|
+
}
|
|
577
|
+
export const TREE_CSS = `
|
|
578
|
+
.fx-tree-row {
|
|
579
|
+
display: flex; align-items: center; gap: 4px;
|
|
580
|
+
padding: 1px 6px 1px calc(6px + var(--depth, 0) * 12px);
|
|
581
|
+
font-family: ui-monospace, monospace; font-size: 11.5px;
|
|
582
|
+
color: var(--fx-label, #e7e9ee); cursor: pointer; white-space: nowrap;
|
|
583
|
+
}
|
|
584
|
+
.fx-tree-row:hover { background: var(--fx-hover, #1b1e26); }
|
|
585
|
+
.fx-tree-row.on { background: var(--fx-sel, #232838); }
|
|
586
|
+
.fx-tree-twist {
|
|
587
|
+
width: 12px; padding: 0; border: 0; background: none; cursor: pointer;
|
|
588
|
+
color: var(--fx-muted, #8a90a2); font-size: 9px; line-height: 1;
|
|
589
|
+
}
|
|
590
|
+
.fx-tree-plugin {
|
|
591
|
+
font-size: 9px; color: var(--fx-accent, #5eead4);
|
|
592
|
+
border: 1px solid var(--fx-line, #262a35); border-radius: 6px; padding: 0 4px;
|
|
593
|
+
max-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
594
|
+
}
|
|
595
|
+
.fx-tree-lazy {
|
|
596
|
+
font-size: 9px; letter-spacing: .04em; color: var(--fx-router, #7dd3fc);
|
|
597
|
+
border: 1px solid var(--fx-line, #262a35); border-radius: 6px; padding: 0 4px;
|
|
598
|
+
}
|
|
599
|
+
.fx-tree-count {
|
|
600
|
+
font-size: 9.5px; color: var(--fx-muted, #8a90a2);
|
|
601
|
+
border: 1px solid var(--fx-line, #262a35); border-radius: 6px; padding: 0 4px;
|
|
602
|
+
}
|
|
603
|
+
${kindRules((k) => `.fx-tree-row.n-${k} .fx-tree-tag`, 'color')}
|
|
604
|
+
.fx-tree-bar {
|
|
605
|
+
display: flex; align-items: center; gap: 5px; padding: 2px 8px 5px;
|
|
606
|
+
font-size: 10.5px; color: var(--fx-muted, #8a90a2); cursor: pointer; user-select: none;
|
|
607
|
+
}
|
|
608
|
+
.fx-tree-bar:hover { color: var(--fx-label, #e7e9ee); }
|
|
609
|
+
.fx-tree-bar input { width: 11px; height: 11px; margin: 0; accent-color: var(--fx-accent, #5eead4); }
|
|
610
|
+
.fx-tree-row.n-element:hover .fx-tree-tag { color: var(--fx-label, #e7e9ee); }
|
|
611
|
+
/* A declared value is listed inside the tag that made it, so it reads quieter than a tag. */
|
|
612
|
+
.fx-tree-row.is-value .fx-tree-tag { font-style: italic; opacity: .85; }
|
|
613
|
+
.fx-tree-search {
|
|
614
|
+
width: calc(100% - 16px); margin: 0 8px 4px; padding: 2px 6px;
|
|
615
|
+
font: inherit; font-size: 10.5px;
|
|
616
|
+
color: var(--fx-label, #e7e9ee); background: var(--fx-bg, #0f1115);
|
|
617
|
+
border: 1px solid var(--fx-line, #262a35); border-radius: 4px;
|
|
618
|
+
}
|
|
619
|
+
.fx-tree-search:focus { outline: none; border-color: var(--fx-accent, #5eead4); }
|
|
620
|
+
.fx-tree-empty { display: block; padding: 8px; font-size: 11px; color: var(--fx-muted, #8a90a2); }
|
|
621
|
+
/* A prop reads as what was written on the tag, so it is quieter than the tag itself. */
|
|
622
|
+
/* The row that folds a tag's props away. Dashed, because it stands for something written
|
|
623
|
+
rather than something rendered. */
|
|
624
|
+
.fx-tree-row.is-props .fx-tree-tag {
|
|
625
|
+
display: inline-flex; align-items: center; gap: 5px;
|
|
626
|
+
padding: 0 5px; border: 1px dashed var(--fx-line, #262a35); border-radius: 3px;
|
|
627
|
+
color: var(--fx-muted, #8a90a2); font-size: 10px; letter-spacing: .04em; text-transform: uppercase;
|
|
628
|
+
}
|
|
629
|
+
.fx-tree-row.is-props:hover .fx-tree-tag { border-color: var(--fx-accent, #5eead4); color: var(--fx-label, #e7e9ee); }
|
|
630
|
+
.fx-tree-props-count { opacity: .7; }
|
|
631
|
+
|
|
632
|
+
/* A prop is what was written on the tag above it, so it reads as an aside: the name in a
|
|
633
|
+
quiet chip, the value beside it, and no marker pretending it opens. */
|
|
634
|
+
.fx-tree-row.is-prop .fx-tree-tag { display: inline-flex; align-items: baseline; gap: 5px; min-width: 0; }
|
|
635
|
+
.fx-tree-prop-name {
|
|
636
|
+
flex: none; padding: 0 4px; border-radius: 3px;
|
|
637
|
+
background: var(--fx-chip, rgba(138, 144, 162, .14));
|
|
638
|
+
color: var(--fx-muted, #8a90a2); font-size: 10px; letter-spacing: .02em;
|
|
639
|
+
}
|
|
640
|
+
.fx-tree-row.is-prop:hover .fx-tree-prop-name { color: var(--fx-label, #e7e9ee); }
|
|
641
|
+
.fx-tree-prop-value {
|
|
642
|
+
min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
643
|
+
color: var(--fx-label, #e7e9ee); opacity: .75;
|
|
644
|
+
}
|
|
645
|
+
`;
|