@mauroandre/velojs 0.0.41 → 0.0.42

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.
@@ -1,7 +1,7 @@
1
1
  import r from "node:http";
2
2
  import d from "node:fs";
3
- import s from "node:path";
4
- const l = `<!DOCTYPE html>
3
+ import l from "node:path";
4
+ const s = `<!DOCTYPE html>
5
5
  <html lang="en">
6
6
  <head>
7
7
  <meta charset="utf-8">
@@ -48,7 +48,7 @@ const l = `<!DOCTYPE html>
48
48
  <div><i style="background:#5b8def"></i>page</div>
49
49
  <div><i style="background:#4b5565"></i>module</div>
50
50
  </div>
51
- <div id="hint">drag to pan · scroll to zoom · drag nodes · hover = impact · click = focus neighborhood · / = search · esc = reset</div>
51
+ <div id="hint">drag to pan · scroll to zoom · drag nodes · hover = highlight · click = pin highlight + details · / = search · esc = reset</div>
52
52
  <script>
53
53
  var KIND_COLORS = { root: "#f59e0b", layout: "#a78bfa", page: "#5b8def", unknown: "#4b5565" };
54
54
 
@@ -152,47 +152,34 @@ function tick() {
152
152
  });
153
153
  }
154
154
 
155
- // ---------- focus mode ----------
156
- // Click isolates a node's neighborhood: transitive imports + importedBy
157
- // (modules), or ancestors + subtree (routes). Click empty / ESC exits.
158
- var focusModSet = null, focusRouteSet = null;
159
- function computeFocusModules(id) {
160
- var set = {}, q = [id];
161
- set[id] = true;
162
- while (q.length) {
163
- var m = graph.modules[q.shift()];
164
- if (!m) continue;
165
- m.imports.forEach(function (i) { if (modById[i] && !set[i]) { set[i] = true; q.push(i); } });
166
- m.importedBy.forEach(function (i) { if (modById[i] && !set[i]) { set[i] = true; q.push(i); } });
167
- }
168
- return set;
169
- }
170
- function computeFocusRoutes(rn) {
171
- var set = [];
172
- var p = rn.parent;
173
- while (p) { set.push(p); p = p.parent; }
174
- (function down(n) { set.push(n); n.kids.forEach(down); })(rn);
175
- return set;
176
- }
177
- function modVisible(n) { return matches(n.id) && (!focusModSet || focusModSet[n.id]); }
178
- function routeVisible(n) { return matches(n.data.moduleId || "(wrapper)") && (!focusRouteSet || focusRouteSet.indexOf(n) >= 0); }
179
-
180
- // ---------- impact highlight ----------
181
- var hoverNode = null;
182
- var impactSet = null;
183
- function computeImpact(n) {
184
- var set = {}, queue = [n.id];
185
- set[n.id] = 2;
186
- while (queue.length) {
187
- var id = queue.shift();
188
- var mod = graph.modules[id];
189
- if (!mod) continue;
190
- mod.importedBy.forEach(function (up) {
191
- if (!(up in set)) { set[up] = 1; queue.push(up); }
192
- });
155
+ // ---------- highlight — hover = transient, click = pinned ----------
156
+ // Same pattern in both views. Related set: modules → transitive
157
+ // importedBy (impact); routes → ancestors + subtree (context).
158
+ var hoverHL = null, pinnedHL = null;
159
+ function activeHL() { return pinnedHL || hoverHL; }
160
+ function relatedNodes(node) {
161
+ var list = [];
162
+ if (view === "modules") {
163
+ var seen = {}, q = [node];
164
+ seen[node.id] = true;
165
+ while (q.length) {
166
+ var cur = q.shift();
167
+ list.push(cur);
168
+ var m = graph.modules[cur.id];
169
+ if (!m) continue;
170
+ m.importedBy.forEach(function (i) {
171
+ if (modById[i] && !seen[i]) { seen[i] = true; q.push(modById[i]); }
172
+ });
173
+ }
174
+ } else {
175
+ var p = node.parent;
176
+ while (p) { list.push(p); p = p.parent; }
177
+ (function down(n) { list.push(n); n.kids.forEach(down); })(node);
193
178
  }
194
- return set;
179
+ return list;
195
180
  }
181
+ function modVisible(n) { return matches(n.id); }
182
+ function routeVisible(n) { return matches(n.data.moduleId || "(wrapper)"); }
196
183
 
197
184
  // ---------- selection / panel ----------
198
185
  var selected = null;
@@ -233,20 +220,22 @@ var query = "";
233
220
  document.getElementById("search").addEventListener("input", function (e) { query = e.target.value.toLowerCase(); });
234
221
  document.addEventListener("keydown", function (e) {
235
222
  if (e.key === "/" && document.activeElement !== document.getElementById("search")) { e.preventDefault(); document.getElementById("search").focus(); }
236
- if (e.key === "Escape") { panel.className = ""; selected = null; query = ""; document.getElementById("search").value = ""; focusModSet = null; focusRouteSet = null; }
223
+ if (e.key === "Escape") { panel.className = ""; selected = null; query = ""; document.getElementById("search").value = ""; hoverHL = null; pinnedHL = null; }
237
224
  });
238
225
 
239
226
  // ---------- interaction ----------
240
227
  var dragNode = null, panning = false, lastX = 0, lastY = 0, moved = false;
241
228
  function nodeAt(sx, sy) {
242
- var w = toWorld(sx, sy);
229
+ // Hit test in SCREEN space, tight to the dot (dot radius + 6px slack)
243
230
  var list = view === "modules" ? modNodes : routeNodes;
244
231
  for (var i = list.length - 1; i >= 0; i--) {
245
232
  var n = list[i];
246
233
  if (view === "modules" ? !modVisible(n) : !routeVisible(n)) continue;
247
- var nx = view === "modules" ? n.x : n.px, ny = view === "modules" ? n.y : n.py;
248
- var dx = w[0] - nx, dy = w[1] - ny;
249
- if (dx * dx + dy * dy < 14 * 14 / (cam.k * cam.k) + 36) return n;
234
+ var wx = view === "modules" ? n.x : n.px, wy = view === "modules" ? n.y : n.py;
235
+ var p = toScreen(wx, wy);
236
+ var rad = (view === "modules" ? (n.kind === "unknown" ? 5 : 8) : 6) + 6;
237
+ var dx = sx - p[0], dy = sy - p[1];
238
+ if (dx * dx + dy * dy < rad * rad) return n;
250
239
  }
251
240
  return null;
252
241
  }
@@ -263,8 +252,7 @@ canvas.addEventListener("mousemove", function (e) {
263
252
  else if (panning) { cam.x += dx; cam.y += dy; }
264
253
  else {
265
254
  var n = nodeAt(e.clientX, e.clientY);
266
- hoverNode = view === "modules" ? n : null;
267
- impactSet = hoverNode ? computeImpact(hoverNode) : null;
255
+ hoverHL = n ? { list: relatedNodes(n), origin: n } : null;
268
256
  canvas.style.cursor = n ? "pointer" : "grab";
269
257
  }
270
258
  lastX = e.clientX; lastY = e.clientY;
@@ -272,12 +260,13 @@ canvas.addEventListener("mousemove", function (e) {
272
260
  canvas.addEventListener("mouseup", function (e) {
273
261
  if (!moved) {
274
262
  var n = nodeAt(e.clientX, e.clientY);
275
- if (view === "modules") {
276
- showPanel(n ? n.id : null);
277
- focusModSet = n ? computeFocusModules(n.id) : null;
263
+ if (n) {
264
+ pinnedHL = { list: relatedNodes(n), origin: n };
265
+ var id = view === "modules" ? n.id : n.data.moduleId;
266
+ showPanel(id || null);
278
267
  } else {
279
- if (n && n.data.moduleId) showPanel(n.data.moduleId);
280
- focusRouteSet = n ? computeFocusRoutes(n) : null;
268
+ pinnedHL = null;
269
+ showPanel(null);
281
270
  }
282
271
  }
283
272
  dragNode = null; panning = false;
@@ -295,64 +284,73 @@ function matches(id) { return !query || id.toLowerCase().indexOf(query) >= 0; }
295
284
 
296
285
  function drawRoutes() {
297
286
  ctx.clearRect(0, 0, W, H);
298
- ctx.strokeStyle = "#2a3247"; ctx.lineWidth = 1.2;
287
+ var hl = activeHL();
299
288
  routeEdges.forEach(function (e) {
300
289
  if (!routeVisible(e.from) || !routeVisible(e.to)) return;
290
+ var hot = hl && hl.list.indexOf(e.from) >= 0 && hl.list.indexOf(e.to) >= 0;
301
291
  var a = toScreen(e.from.px, e.from.py), b = toScreen(e.to.px, e.to.py);
292
+ ctx.globalAlpha = hl && !hot ? 0.08 : 1;
293
+ ctx.strokeStyle = hot ? "#f59e0b" : "#2a3247";
294
+ ctx.lineWidth = hot ? 2 : 1.2;
302
295
  ctx.beginPath();
303
296
  ctx.moveTo(a[0], a[1]);
304
297
  ctx.bezierCurveTo(a[0], (a[1] + b[1]) / 2, b[0], (a[1] + b[1]) / 2, b[0], b[1]);
305
298
  ctx.stroke();
306
299
  });
300
+ ctx.globalAlpha = 1;
307
301
  routeNodes.forEach(function (n) {
308
302
  var d = n.data;
309
303
  var id = d.moduleId || "(wrapper)";
310
304
  if (!routeVisible(n)) return;
311
305
  var p = toScreen(n.px, n.py);
306
+ var member = hl && hl.list.indexOf(n) >= 0;
307
+ var dim = hl && !member;
308
+ var isOrigin = hl && hl.origin === n;
312
309
  var color = d.isRoot ? KIND_COLORS.root : (d.children.length ? KIND_COLORS.layout : KIND_COLORS.page);
313
310
  var sel = selected === d.moduleId;
314
- ctx.beginPath(); ctx.arc(p[0], p[1], sel ? 9 : 6, 0, 7);
315
- ctx.fillStyle = color; ctx.fill();
311
+ ctx.globalAlpha = dim ? 0.15 : 1;
312
+ ctx.beginPath(); ctx.arc(p[0], p[1], (sel || isOrigin) ? 9 : 6, 0, 7);
313
+ ctx.fillStyle = isOrigin ? "#f59e0b" : color; ctx.fill();
316
314
  if (sel) { ctx.strokeStyle = "#e8edf5"; ctx.lineWidth = 2; ctx.stroke(); }
317
- // level-of-detail: labels only when zoomed in enough, otherwise the
318
- // overview stays clean (dots only). fullPath needs even more zoom.
319
- if (cam.k >= 0.55 || sel) {
320
- ctx.fillStyle = "#d7dde8"; ctx.font = "11px ui-monospace, monospace"; ctx.textAlign = "center";
315
+ if (!dim) {
316
+ ctx.fillStyle = member ? "#e8edf5" : "#d7dde8"; ctx.font = "11px ui-monospace, monospace"; ctx.textAlign = "center";
321
317
  ctx.fillText(id.split("/").pop(), p[0], p[1] - 13);
322
- }
323
- if (cam.k >= 0.85 || sel) {
324
318
  ctx.fillStyle = "#55607a"; ctx.font = "10px ui-monospace, monospace";
325
319
  var sub = d.fullPath || "";
326
320
  if (d.middlewares.length) sub += " · " + d.middlewares.join(", ");
327
321
  ctx.fillText(sub, p[0], p[1] + 20);
328
322
  }
323
+ ctx.globalAlpha = 1;
329
324
  });
330
325
  }
331
326
 
332
327
  function drawModules() {
333
328
  ctx.clearRect(0, 0, W, H);
334
- ctx.strokeStyle = "#232a3a"; ctx.lineWidth = 1;
329
+ var hl = activeHL();
335
330
  modEdges.forEach(function (e) {
336
331
  if (!modVisible(e.from) || !modVisible(e.to)) return;
337
- var hot = impactSet && impactSet[e.from.id] && impactSet[e.to.id];
332
+ var hot = hl && hl.list.indexOf(e.from) >= 0 && hl.list.indexOf(e.to) >= 0;
338
333
  var a = toScreen(e.from.x, e.from.y), b = toScreen(e.to.x, e.to.y);
334
+ ctx.globalAlpha = hl && !hot ? 0.08 : 1;
339
335
  ctx.strokeStyle = hot ? "#f59e0b" : "#232a3a";
340
336
  ctx.lineWidth = hot ? 1.8 : 1;
341
337
  ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke();
342
338
  });
339
+ ctx.globalAlpha = 1;
343
340
  modNodes.forEach(function (n) {
344
341
  if (!modVisible(n)) return;
345
342
  var p = toScreen(n.x, n.y);
346
- var dim = impactSet && !impactSet[n.id];
347
- var hot = impactSet && impactSet[n.id];
348
- var r = n.kind === "unknown" ? 4 : 7;
343
+ var member = hl && hl.list.indexOf(n) >= 0;
344
+ var dim = hl && !member;
345
+ var isOrigin = hl && hl.origin === n;
346
+ var r = n.kind === "unknown" ? 5 : 8;
349
347
  ctx.globalAlpha = dim ? 0.15 : 1;
350
- ctx.beginPath(); ctx.arc(p[0], p[1], selected === n.id ? r + 3 : r, 0, 7);
351
- ctx.fillStyle = hot === 2 ? "#f59e0b" : KIND_COLORS[n.kind] || KIND_COLORS.unknown;
348
+ ctx.beginPath(); ctx.arc(p[0], p[1], (selected === n.id || isOrigin) ? r + 3 : r, 0, 7);
349
+ ctx.fillStyle = isOrigin ? "#f59e0b" : KIND_COLORS[n.kind] || KIND_COLORS.unknown;
352
350
  ctx.fill();
353
351
  if (selected === n.id) { ctx.strokeStyle = "#e8edf5"; ctx.lineWidth = 2; ctx.stroke(); }
354
352
  if (!dim) {
355
- ctx.fillStyle = hot ? "#e8edf5" : "#9aa5b8";
353
+ ctx.fillStyle = member ? "#e8edf5" : "#9aa5b8";
356
354
  ctx.font = "10px ui-monospace, monospace"; ctx.textAlign = "center";
357
355
  ctx.fillText(n.id.split("/").pop(), p[0], p[1] - r - 5);
358
356
  }
@@ -391,8 +389,8 @@ fetch("/graph.json").then(function (r) { return r.json(); }).then(function (g) {
391
389
  <\/script>
392
390
  </body>
393
391
  </html>`;
394
- async function f(a) {
395
- const i = s.join(a, ".velojs", "graph.json"), o = r.createServer((n, e) => {
392
+ async function f(o) {
393
+ const i = l.join(o, ".velojs", "graph.json"), a = r.createServer((n, e) => {
396
394
  if (n.url === "/graph.json") {
397
395
  let t;
398
396
  try {
@@ -404,11 +402,11 @@ async function f(a) {
404
402
  e.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" }), e.end(t);
405
403
  return;
406
404
  }
407
- e.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), e.end(l);
405
+ e.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }), e.end(s);
408
406
  });
409
407
  return await new Promise((n) => {
410
- o.listen(0, "127.0.0.1", () => {
411
- const e = o.address(), t = typeof e == "object" && e ? e.port : 0;
408
+ a.listen(0, "127.0.0.1", () => {
409
+ const e = a.address(), t = typeof e == "object" && e ? e.port : 0;
412
410
  n(`http://127.0.0.1:${t}`);
413
411
  });
414
412
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mauroandre/velojs",
3
- "version": "0.0.41",
3
+ "version": "0.0.42",
4
4
  "type": "module",
5
5
  "description": "Fullstack web framework built on Hono + Preact with Vite-powered AST transforms",
6
6
  "keywords": [