waygraph 0.12.36 → 0.12.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +125 -32
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -711,6 +711,7 @@ async function installOverlay(page, title) {
711
711
  const cursor = document.getElementById("wg-cursor");
712
712
  if (!cursor) return;
713
713
  const travel = ms || 600;
714
+ window.__wgCursorPos = { x: Number(x) || 0, y: Number(y) || 0 };
714
715
  cursor.style.setProperty("--wg-cursor-ms", travel + "ms");
715
716
  if (instant) {
716
717
  const prev = cursor.style.transition;
@@ -811,6 +812,50 @@ async function installOverlay(page, title) {
811
812
  let settled = zoom <= 1;
812
813
  const settleAt = Date.now() + camMs;
813
814
  const style = { size, weight, focus, color };
815
+ // Screen Studio / Screenify: dead zone = center ~40% of viewport.
816
+ // Pan only when the demo cursor leaves that zone - never hard-center
817
+ // on every mousemove (that fights like same-pole magnets).
818
+ const deadFrac = 0.4;
819
+ let lastPanAt = 0;
820
+ const readCursorViewport = () => {
821
+ const cursor = document.getElementById("wg-cursor");
822
+ if (cursor && cursor.style.opacity !== "0") {
823
+ const r = cursor.getBoundingClientRect();
824
+ if (r.width || r.height || r.left || r.top) {
825
+ return { x: r.left, y: r.top };
826
+ }
827
+ }
828
+ if (window.__wgCursorPos) return window.__wgCursorPos;
829
+ return null;
830
+ };
831
+ const followTick = () => {
832
+ if (!followMouse || !settled || window.__wgCameraLock) return;
833
+ if (!window.__wgSetCamera || !window.__wgViewportToDoc) return;
834
+ const cam = window.__wgCamera;
835
+ if (!cam || !(cam.scale > 1.001)) return;
836
+ const pos = readCursorViewport();
837
+ if (!pos) return;
838
+ const vw = window.innerWidth;
839
+ const vh = window.innerHeight;
840
+ const dx = pos.x - vw / 2;
841
+ const dy = pos.y - vh / 2;
842
+ const deadX = (vw * deadFrac) / 2;
843
+ const deadY = (vh * deadFrac) / 2;
844
+ let ox = 0;
845
+ let oy = 0;
846
+ if (dx > deadX) ox = dx - deadX;
847
+ else if (dx < -deadX) ox = dx + deadX;
848
+ if (dy > deadY) oy = dy - deadY;
849
+ else if (dy < -deadY) oy = dy + deadY;
850
+ if (ox === 0 && oy === 0) return;
851
+ const now = Date.now();
852
+ if (now - lastPanAt < 80) return;
853
+ lastPanAt = now;
854
+ // Viewport overflow -> document delta (divide by scale).
855
+ const nx = cam.cx + ox / cam.scale;
856
+ const ny = cam.cy + oy / cam.scale;
857
+ window.__wgSetCamera(nx, ny, cam.scale, 220);
858
+ };
814
859
  const tick = () => {
815
860
  const el = document.querySelector(sel);
816
861
  if (el && window.__wgPositionRing) {
@@ -823,32 +868,34 @@ async function installOverlay(page, title) {
823
868
  );
824
869
  }
825
870
  if (!settled && Date.now() >= settleAt) settled = true;
871
+ followTick();
826
872
  window.__wgRingRaf = requestAnimationFrame(tick);
827
873
  };
828
874
  if (zoom > 1 && window.__wgSetCamera) {
829
875
  const el0 = document.querySelector(sel);
830
876
  if (el0) {
831
- const r0 = el0.getBoundingClientRect();
832
877
  const already =
833
878
  window.__wgCamera && window.__wgCamera.scale > 1.001;
834
- // Already zoomed (zoomOut:false chain): short pan, not full zoom-in.
835
879
  const dur = already ? Math.min(camMs, 420) : camMs;
836
- window.__wgSetCamera(
837
- r0.left + r0.width / 2,
838
- r0.top + r0.height / 2,
839
- zoom,
840
- dur,
841
- );
880
+ // Focus must be document-space, not raw getBoundingClientRect
881
+ // (viewport) - otherwise already-zoomed pans fight the cursor.
882
+ const pt = window.__wgElDocPoint
883
+ ? window.__wgElDocPoint(el0)
884
+ : {
885
+ x: el0.getBoundingClientRect().left + el0.getBoundingClientRect().width / 2,
886
+ y: el0.getBoundingClientRect().top + el0.getBoundingClientRect().height / 2,
887
+ };
888
+ window.__wgCameraLock = true;
889
+ window.__wgSetCamera(pt.x, pt.y, zoom, dur);
890
+ setTimeout(() => {
891
+ window.__wgCameraLock = false;
892
+ }, dur + 40);
842
893
  }
843
894
  }
844
895
  window.__wgRingRaf = requestAnimationFrame(tick);
896
+ // Real mouse only seeds cursor pos; dead-zone follow runs in RAF.
845
897
  const onMove = (ev) => {
846
- if (!followMouse || !settled) return;
847
- if (!window.__wgSetCamera) return;
848
- const cam = window.__wgCamera;
849
- const scale = (cam && cam.scale > 1 ? cam.scale : zoom) || zoom;
850
- if (!(scale > 1)) return;
851
- window.__wgSetCamera(ev.clientX, ev.clientY, scale, 140);
898
+ window.__wgCursorPos = { x: ev.clientX, y: ev.clientY };
852
899
  };
853
900
  window.addEventListener("mousemove", onMove, { passive: true });
854
901
  window.__wgRingTrack = onMove;
@@ -893,7 +940,34 @@ async function installOverlay(page, title) {
893
940
  // rings; only resetCameraZoom / teardown call __wgClearZoom.
894
941
  };
895
942
  // Screen Studio-style camera: scale <body>, keep overlays on <html> fixed.
896
- window.__wgCamera = { scale: 1, cx: 0, cy: 0 };
943
+ // Focus (cx,cy) is DOCUMENT space (pre-transform). Viewport mouse/rects
944
+ // must go through __wgViewportToDoc before SetCamera.
945
+ window.__wgCamera = {
946
+ scale: 1,
947
+ cx: window.innerWidth / 2,
948
+ cy: window.innerHeight / 2,
949
+ };
950
+ window.__wgCameraLock = false;
951
+ window.__wgViewportToDoc = (vx, vy) => {
952
+ const cam = window.__wgCamera || {
953
+ scale: 1,
954
+ cx: window.innerWidth / 2,
955
+ cy: window.innerHeight / 2,
956
+ };
957
+ const s = cam.scale > 1.001 ? cam.scale : 1;
958
+ if (s <= 1.001) return { x: vx, y: vy };
959
+ return {
960
+ x: (vx - window.innerWidth / 2) / s + cam.cx,
961
+ y: (vy - window.innerHeight / 2) / s + cam.cy,
962
+ };
963
+ };
964
+ window.__wgElDocPoint = (el) => {
965
+ const r = el.getBoundingClientRect();
966
+ return window.__wgViewportToDoc(
967
+ r.left + r.width / 2,
968
+ r.top + r.height / 2,
969
+ );
970
+ };
897
971
  window.__wgSetCamera = (fx, fy, scale, ms) => {
898
972
  const body = document.body;
899
973
  if (!body) return;
@@ -961,7 +1035,7 @@ async function installOverlay(page, title) {
961
1035
  '<div class="wg-todo-head">Checklist</div><ul>' + rows + '</ul>';
962
1036
  modal.classList.add("wg-todo-on");
963
1037
  };
964
- window.__wgResetCamera = () => {
1038
+ window.__wgResetCamera = () => {
965
1039
  window.__wgSetCamera(window.innerWidth / 2, window.innerHeight / 2, 1);
966
1040
  };
967
1041
  window.__wgClearZoom = () => {
@@ -1413,13 +1487,17 @@ async function applyCameraZoom(page, box, zoom, ms) {
1413
1487
  }).catch(() => {});
1414
1488
  return;
1415
1489
  }
1416
- const cx = box.x + box.width / 2;
1417
- const cy = box.y + box.height / 2;
1490
+ const vx = box.x + box.width / 2;
1491
+ const vy = box.y + box.height / 2;
1418
1492
  const dur = ms != null ? ms : 650;
1419
1493
  await page
1420
- .evaluate(({ cx, cy, scale, dur }) => {
1421
- if (window.__wgSetCamera) window.__wgSetCamera(cx, cy, scale, dur);
1422
- }, { cx, cy, scale: z, dur })
1494
+ .evaluate(({ vx, vy, scale, dur }) => {
1495
+ if (!window.__wgSetCamera) return;
1496
+ const pt = window.__wgViewportToDoc
1497
+ ? window.__wgViewportToDoc(vx, vy)
1498
+ : { x: vx, y: vy };
1499
+ window.__wgSetCamera(pt.x, pt.y, scale, dur);
1500
+ }, { vx, vy, scale: z, dur })
1423
1501
  .catch(() => {});
1424
1502
  }
1425
1503
 
@@ -1553,16 +1631,9 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1553
1631
  : h.follow !== undefined
1554
1632
  ? !!h.follow
1555
1633
  : z > 1;
1556
- const cx = box.x + box.width / 2;
1557
- const cy = box.y + box.height / 2;
1558
- if (h.cursor !== false) {
1559
- await page
1560
- .evaluate(({ cx, cy, camMs }) => {
1561
- if (window.__wgMoveCursorTo) window.__wgMoveCursorTo(cx, cy, camMs, false);
1562
- }, { cx, cy, camMs })
1563
- .catch(() => {});
1564
- }
1565
- // Track first: ring rides visual rect through the zoom (no re-adjust).
1634
+ // Track/zoom first, then park the demo cursor on the LIVE rect.
1635
+ // Placing the cursor on a pre-zoom boundingBox leaves it stranded after
1636
+ // body scale (html cursor is outside the transform).
1566
1637
  await page
1567
1638
  .evaluate(
1568
1639
  ({ sel, label, tone, size, weight, focus, color, zoom, followMouse, camMs }) => {
@@ -1600,7 +1671,21 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
1600
1671
  .catch(() => false);
1601
1672
  const waitMs = alreadyZoomed ? Math.min(camMs, 420) : camMs;
1602
1673
  await new Promise((res) => setTimeout(res, waitMs));
1603
-
1674
+ if (h.cursor !== false) {
1675
+ await page
1676
+ .evaluate(({ sel, ms }) => {
1677
+ const el = document.querySelector(sel);
1678
+ if (!el || !window.__wgMoveCursorTo) return;
1679
+ window.__wgCameraLock = true;
1680
+ const r = el.getBoundingClientRect();
1681
+ window.__wgMoveCursorTo(r.left + r.width / 2, r.top + r.height / 2, ms, false);
1682
+ setTimeout(() => {
1683
+ window.__wgCameraLock = false;
1684
+ }, ms + 40);
1685
+ }, { sel: h.selector, ms: Math.min(280, camMs) })
1686
+ .catch(() => {});
1687
+ await new Promise((res) => setTimeout(res, Math.min(280, camMs)));
1688
+ }
1604
1689
  // Appear settle
1605
1690
  await new Promise((res) => setTimeout(res, fadeMs));
1606
1691
  const authored = resolveFixtureDwellMs(h, { gatesFast, pace });
@@ -2363,8 +2448,11 @@ async function moveCursorTo(page, box, ms) {
2363
2448
  const x = box.x + box.width / 2;
2364
2449
  const y = box.y + box.height / 2;
2365
2450
  const travel = Math.max(0, Number(ms) || 0);
2451
+ // Freeze camera follow while the demo cursor travels - otherwise dead-zone
2452
+ // pans fight the click target (esp. right-edge cart chrome).
2366
2453
  await page
2367
2454
  .evaluate(({ x, y, ms }) => {
2455
+ window.__wgCameraLock = true;
2368
2456
  if (window.__wgMoveCursorTo) window.__wgMoveCursorTo(x, y, ms);
2369
2457
  }, { x, y, ms: travel })
2370
2458
  .catch(() => {});
@@ -2374,6 +2462,11 @@ async function moveCursorTo(page, box, ms) {
2374
2462
  if (travel > 0) {
2375
2463
  await new Promise((res) => setTimeout(res, travel));
2376
2464
  }
2465
+ await page
2466
+ .evaluate(() => {
2467
+ window.__wgCameraLock = false;
2468
+ })
2469
+ .catch(() => {});
2377
2470
  return { x, y };
2378
2471
  }
2379
2472
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.12.36",
3
+ "version": "0.12.37",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "license": "MIT",