@mauroandre/velojs 0.0.40 → 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.
@@ -48,7 +48,7 @@ const s = `<!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 highlight · / = search</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
 
@@ -90,16 +90,16 @@ function buildRouteLayout() {
90
90
  var leafCount = 0;
91
91
  // tidy-ish: leaves get sequential x; parents sit at the midpoint of children
92
92
  function layout(node, depth, parent) {
93
- var rn = { data: node, depth: depth, x: 0, y: depth, parent: parent };
93
+ var rn = { data: node, depth: depth, x: 0, y: depth, parent: parent, kids: [] };
94
94
  routeNodes.push(rn);
95
- if (parent) routeEdges.push({ from: parent, to: rn });
95
+ if (parent) { parent.kids.push(rn); routeEdges.push({ from: parent, to: rn }); }
96
96
  if (!node.children.length) { rn.x = leafCount++; return rn; }
97
97
  var xs = node.children.map(function (c) { return layout(c, depth + 1, rn).x; });
98
98
  rn.x = (xs[0] + xs[xs.length - 1]) / 2;
99
99
  return rn;
100
100
  }
101
101
  graph.routes.forEach(function (r) { layout(r, 0, null); });
102
- var gapX = 210, gapY = 110;
102
+ var gapX = 250, gapY = 130;
103
103
  var minX = Math.min.apply(null, routeNodes.map(function (n) { return n.x; }));
104
104
  routeNodes.forEach(function (n) { n.px = (n.x - minX) * gapX + 80; n.py = n.y * gapY + 80; });
105
105
  }
@@ -118,8 +118,9 @@ function toWorld(sx, sy) { return [(sx - cam.x) / cam.k, (sy - cam.y) / cam.k];
118
118
 
119
119
  // ---------- force simulation (modules) ----------
120
120
  var alpha = 1;
121
+ var MAXV = 25;
121
122
  function tick() {
122
- if (view !== "modules" || alpha < 0.005) return;
123
+ if (alpha < 0.005) return;
123
124
  alpha *= 0.995;
124
125
  var i, j, n, m, dx, dy, d2, f;
125
126
  for (i = 0; i < modNodes.length; i++) {
@@ -145,26 +146,40 @@ function tick() {
145
146
  if (n === dragNode) return;
146
147
  n.vx += -n.x * 0.002 * alpha; n.vy += -n.y * 0.002 * alpha;
147
148
  n.vx *= 0.85; n.vy *= 0.85;
149
+ if (n.vx > MAXV) n.vx = MAXV; else if (n.vx < -MAXV) n.vx = -MAXV;
150
+ if (n.vy > MAXV) n.vy = MAXV; else if (n.vy < -MAXV) n.vy = -MAXV;
148
151
  n.x += n.vx; n.y += n.vy;
149
152
  });
150
153
  }
151
154
 
152
- // ---------- impact highlight ----------
153
- var hoverNode = null;
154
- var impactSet = null;
155
- function computeImpact(n) {
156
- var set = {}, queue = [n.id];
157
- set[n.id] = 2;
158
- while (queue.length) {
159
- var id = queue.shift();
160
- var mod = graph.modules[id];
161
- if (!mod) continue;
162
- mod.importedBy.forEach(function (up) {
163
- if (!(up in set)) { set[up] = 1; queue.push(up); }
164
- });
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);
165
178
  }
166
- return set;
179
+ return list;
167
180
  }
181
+ function modVisible(n) { return matches(n.id); }
182
+ function routeVisible(n) { return matches(n.data.moduleId || "(wrapper)"); }
168
183
 
169
184
  // ---------- selection / panel ----------
170
185
  var selected = null;
@@ -205,19 +220,22 @@ var query = "";
205
220
  document.getElementById("search").addEventListener("input", function (e) { query = e.target.value.toLowerCase(); });
206
221
  document.addEventListener("keydown", function (e) {
207
222
  if (e.key === "/" && document.activeElement !== document.getElementById("search")) { e.preventDefault(); document.getElementById("search").focus(); }
208
- if (e.key === "Escape") { panel.className = ""; selected = null; query = ""; document.getElementById("search").value = ""; }
223
+ if (e.key === "Escape") { panel.className = ""; selected = null; query = ""; document.getElementById("search").value = ""; hoverHL = null; pinnedHL = null; }
209
224
  });
210
225
 
211
226
  // ---------- interaction ----------
212
227
  var dragNode = null, panning = false, lastX = 0, lastY = 0, moved = false;
213
228
  function nodeAt(sx, sy) {
214
- var w = toWorld(sx, sy);
229
+ // Hit test in SCREEN space, tight to the dot (dot radius + 6px slack)
215
230
  var list = view === "modules" ? modNodes : routeNodes;
216
231
  for (var i = list.length - 1; i >= 0; i--) {
217
232
  var n = list[i];
218
- var nx = view === "modules" ? n.x : n.px, ny = view === "modules" ? n.y : n.py;
219
- var dx = w[0] - nx, dy = w[1] - ny;
220
- if (dx * dx + dy * dy < 14 * 14 / (cam.k * cam.k) + 36) return n;
233
+ if (view === "modules" ? !modVisible(n) : !routeVisible(n)) continue;
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;
221
239
  }
222
240
  return null;
223
241
  }
@@ -234,8 +252,7 @@ canvas.addEventListener("mousemove", function (e) {
234
252
  else if (panning) { cam.x += dx; cam.y += dy; }
235
253
  else {
236
254
  var n = nodeAt(e.clientX, e.clientY);
237
- hoverNode = view === "modules" ? n : null;
238
- impactSet = hoverNode ? computeImpact(hoverNode) : null;
255
+ hoverHL = n ? { list: relatedNodes(n), origin: n } : null;
239
256
  canvas.style.cursor = n ? "pointer" : "grab";
240
257
  }
241
258
  lastX = e.clientX; lastY = e.clientY;
@@ -243,9 +260,14 @@ canvas.addEventListener("mousemove", function (e) {
243
260
  canvas.addEventListener("mouseup", function (e) {
244
261
  if (!moved) {
245
262
  var n = nodeAt(e.clientX, e.clientY);
246
- if (view === "modules") showPanel(n ? n.id : null);
247
- else if (n && n.data.moduleId) showPanel(n.data.moduleId);
248
- else showPanel(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);
267
+ } else {
268
+ pinnedHL = null;
269
+ showPanel(null);
270
+ }
249
271
  }
250
272
  dragNode = null; panning = false;
251
273
  });
@@ -262,57 +284,73 @@ function matches(id) { return !query || id.toLowerCase().indexOf(query) >= 0; }
262
284
 
263
285
  function drawRoutes() {
264
286
  ctx.clearRect(0, 0, W, H);
265
- ctx.strokeStyle = "#2a3247"; ctx.lineWidth = 1.2;
287
+ var hl = activeHL();
266
288
  routeEdges.forEach(function (e) {
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;
267
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;
268
295
  ctx.beginPath();
269
296
  ctx.moveTo(a[0], a[1]);
270
297
  ctx.bezierCurveTo(a[0], (a[1] + b[1]) / 2, b[0], (a[1] + b[1]) / 2, b[0], b[1]);
271
298
  ctx.stroke();
272
299
  });
300
+ ctx.globalAlpha = 1;
273
301
  routeNodes.forEach(function (n) {
274
302
  var d = n.data;
275
303
  var id = d.moduleId || "(wrapper)";
276
- if (!matches(id)) return;
304
+ if (!routeVisible(n)) return;
277
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;
278
309
  var color = d.isRoot ? KIND_COLORS.root : (d.children.length ? KIND_COLORS.layout : KIND_COLORS.page);
279
310
  var sel = selected === d.moduleId;
280
- ctx.beginPath(); ctx.arc(p[0], p[1], sel ? 9 : 6, 0, 7);
281
- 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();
282
314
  if (sel) { ctx.strokeStyle = "#e8edf5"; ctx.lineWidth = 2; ctx.stroke(); }
283
- ctx.fillStyle = "#d7dde8"; ctx.font = "11px ui-monospace, monospace"; ctx.textAlign = "center";
284
- ctx.fillText(id.split("/").pop(), p[0], p[1] - 13);
285
- ctx.fillStyle = "#55607a"; ctx.font = "10px ui-monospace, monospace";
286
- var sub = d.fullPath || "";
287
- if (d.middlewares.length) sub += " · " + d.middlewares.join(", ");
288
- ctx.fillText(sub, p[0], p[1] + 20);
315
+ if (!dim) {
316
+ ctx.fillStyle = member ? "#e8edf5" : "#d7dde8"; ctx.font = "11px ui-monospace, monospace"; ctx.textAlign = "center";
317
+ ctx.fillText(id.split("/").pop(), p[0], p[1] - 13);
318
+ ctx.fillStyle = "#55607a"; ctx.font = "10px ui-monospace, monospace";
319
+ var sub = d.fullPath || "";
320
+ if (d.middlewares.length) sub += " · " + d.middlewares.join(", ");
321
+ ctx.fillText(sub, p[0], p[1] + 20);
322
+ }
323
+ ctx.globalAlpha = 1;
289
324
  });
290
325
  }
291
326
 
292
327
  function drawModules() {
293
328
  ctx.clearRect(0, 0, W, H);
294
- ctx.strokeStyle = "#232a3a"; ctx.lineWidth = 1;
329
+ var hl = activeHL();
295
330
  modEdges.forEach(function (e) {
296
- if (!matches(e.from.id) && !matches(e.to.id)) return;
297
- var hot = impactSet && impactSet[e.from.id] && impactSet[e.to.id];
331
+ if (!modVisible(e.from) || !modVisible(e.to)) return;
332
+ var hot = hl && hl.list.indexOf(e.from) >= 0 && hl.list.indexOf(e.to) >= 0;
298
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;
299
335
  ctx.strokeStyle = hot ? "#f59e0b" : "#232a3a";
300
336
  ctx.lineWidth = hot ? 1.8 : 1;
301
337
  ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke();
302
338
  });
339
+ ctx.globalAlpha = 1;
303
340
  modNodes.forEach(function (n) {
304
- if (!matches(n.id)) return;
341
+ if (!modVisible(n)) return;
305
342
  var p = toScreen(n.x, n.y);
306
- var dim = impactSet && !impactSet[n.id];
307
- var hot = impactSet && impactSet[n.id];
308
- 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;
309
347
  ctx.globalAlpha = dim ? 0.15 : 1;
310
- ctx.beginPath(); ctx.arc(p[0], p[1], selected === n.id ? r + 3 : r, 0, 7);
311
- 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;
312
350
  ctx.fill();
313
351
  if (selected === n.id) { ctx.strokeStyle = "#e8edf5"; ctx.lineWidth = 2; ctx.stroke(); }
314
352
  if (!dim) {
315
- ctx.fillStyle = hot ? "#e8edf5" : "#9aa5b8";
353
+ ctx.fillStyle = member ? "#e8edf5" : "#9aa5b8";
316
354
  ctx.font = "10px ui-monospace, monospace"; ctx.textAlign = "center";
317
355
  ctx.fillText(n.id.split("/").pop(), p[0], p[1] - r - 5);
318
356
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mauroandre/velojs",
3
- "version": "0.0.40",
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": [