waygraph 0.12.36 → 0.12.38
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/dist/cli.js +194 -40
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -711,6 +711,8 @@ 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 };
|
|
715
|
+
window.__wgCursorTravelUntil = Date.now() + (instant ? 0 : travel) + 40;
|
|
714
716
|
cursor.style.setProperty("--wg-cursor-ms", travel + "ms");
|
|
715
717
|
if (instant) {
|
|
716
718
|
const prev = cursor.style.transition;
|
|
@@ -811,6 +813,54 @@ async function installOverlay(page, title) {
|
|
|
811
813
|
let settled = zoom <= 1;
|
|
812
814
|
const settleAt = Date.now() + camMs;
|
|
813
815
|
const style = { size, weight, focus, color };
|
|
816
|
+
// Screen Studio / Screenify: dead zone follow (see followTick).
|
|
817
|
+
const deadFrac = 0.5;
|
|
818
|
+
let lastPanAt = 0;
|
|
819
|
+
const readCursorViewport = () => {
|
|
820
|
+
const cursor = document.getElementById("wg-cursor");
|
|
821
|
+
if (cursor && cursor.style.opacity !== "0") {
|
|
822
|
+
const r = cursor.getBoundingClientRect();
|
|
823
|
+
if (r.width || r.height || r.left || r.top) {
|
|
824
|
+
return { x: r.left, y: r.top };
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (window.__wgCursorPos) return window.__wgCursorPos;
|
|
828
|
+
return null;
|
|
829
|
+
};
|
|
830
|
+
const followTick = () => {
|
|
831
|
+
if (!followMouse || !settled || window.__wgCameraLock) return;
|
|
832
|
+
if (!window.__wgSetCamera || !window.__wgViewportToDoc) return;
|
|
833
|
+
const cam = window.__wgCamera;
|
|
834
|
+
if (!cam || !(cam.scale > 1.001)) return;
|
|
835
|
+
// Skip while the demo cursor is mid CSS travel - reading a
|
|
836
|
+
// mid-lerp rect makes the camera chase noise and teleport.
|
|
837
|
+
if (window.__wgCursorTravelUntil && Date.now() < window.__wgCursorTravelUntil) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
const pos = readCursorViewport();
|
|
841
|
+
if (!pos) return;
|
|
842
|
+
const vw = window.innerWidth;
|
|
843
|
+
const vh = window.innerHeight;
|
|
844
|
+
const dx = pos.x - vw / 2;
|
|
845
|
+
const dy = pos.y - vh / 2;
|
|
846
|
+
const deadX = (vw * deadFrac) / 2;
|
|
847
|
+
const deadY = (vh * deadFrac) / 2;
|
|
848
|
+
let ox = 0;
|
|
849
|
+
let oy = 0;
|
|
850
|
+
if (dx > deadX) ox = dx - deadX;
|
|
851
|
+
else if (dx < -deadX) ox = dx + deadX;
|
|
852
|
+
if (dy > deadY) oy = dy - deadY;
|
|
853
|
+
else if (dy < -deadY) oy = dy + deadY;
|
|
854
|
+
if (ox === 0 && oy === 0) return;
|
|
855
|
+
const now = Date.now();
|
|
856
|
+
if (now - lastPanAt < 120) return;
|
|
857
|
+
lastPanAt = now;
|
|
858
|
+
// Partial gain - never yank the full overflow in one pan.
|
|
859
|
+
const gain = 0.35;
|
|
860
|
+
const nx = cam.cx + (ox / cam.scale) * gain;
|
|
861
|
+
const ny = cam.cy + (oy / cam.scale) * gain;
|
|
862
|
+
window.__wgSetCamera(nx, ny, cam.scale, 280);
|
|
863
|
+
};
|
|
814
864
|
const tick = () => {
|
|
815
865
|
const el = document.querySelector(sel);
|
|
816
866
|
if (el && window.__wgPositionRing) {
|
|
@@ -823,32 +873,34 @@ async function installOverlay(page, title) {
|
|
|
823
873
|
);
|
|
824
874
|
}
|
|
825
875
|
if (!settled && Date.now() >= settleAt) settled = true;
|
|
876
|
+
followTick();
|
|
826
877
|
window.__wgRingRaf = requestAnimationFrame(tick);
|
|
827
878
|
};
|
|
828
879
|
if (zoom > 1 && window.__wgSetCamera) {
|
|
829
880
|
const el0 = document.querySelector(sel);
|
|
830
881
|
if (el0) {
|
|
831
|
-
const r0 = el0.getBoundingClientRect();
|
|
832
882
|
const already =
|
|
833
883
|
window.__wgCamera && window.__wgCamera.scale > 1.001;
|
|
834
|
-
// Already zoomed (zoomOut:false chain): short pan, not full zoom-in.
|
|
835
884
|
const dur = already ? Math.min(camMs, 420) : camMs;
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
885
|
+
// Focus must be document-space, not raw getBoundingClientRect
|
|
886
|
+
// (viewport) - otherwise already-zoomed pans fight the cursor.
|
|
887
|
+
const pt = window.__wgElDocPoint
|
|
888
|
+
? window.__wgElDocPoint(el0)
|
|
889
|
+
: {
|
|
890
|
+
x: el0.getBoundingClientRect().left + el0.getBoundingClientRect().width / 2,
|
|
891
|
+
y: el0.getBoundingClientRect().top + el0.getBoundingClientRect().height / 2,
|
|
892
|
+
};
|
|
893
|
+
window.__wgCameraLock = true;
|
|
894
|
+
window.__wgSetCamera(pt.x, pt.y, zoom, dur);
|
|
895
|
+
setTimeout(() => {
|
|
896
|
+
window.__wgCameraLock = false;
|
|
897
|
+
}, dur + 40);
|
|
842
898
|
}
|
|
843
899
|
}
|
|
844
900
|
window.__wgRingRaf = requestAnimationFrame(tick);
|
|
901
|
+
// Real mouse only seeds cursor pos; dead-zone follow runs in RAF.
|
|
845
902
|
const onMove = (ev) => {
|
|
846
|
-
|
|
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);
|
|
903
|
+
window.__wgCursorPos = { x: ev.clientX, y: ev.clientY };
|
|
852
904
|
};
|
|
853
905
|
window.addEventListener("mousemove", onMove, { passive: true });
|
|
854
906
|
window.__wgRingTrack = onMove;
|
|
@@ -893,7 +945,34 @@ async function installOverlay(page, title) {
|
|
|
893
945
|
// rings; only resetCameraZoom / teardown call __wgClearZoom.
|
|
894
946
|
};
|
|
895
947
|
// Screen Studio-style camera: scale <body>, keep overlays on <html> fixed.
|
|
896
|
-
|
|
948
|
+
// Focus (cx,cy) is DOCUMENT space (pre-transform). Viewport mouse/rects
|
|
949
|
+
// must go through __wgViewportToDoc before SetCamera.
|
|
950
|
+
window.__wgCamera = {
|
|
951
|
+
scale: 1,
|
|
952
|
+
cx: window.innerWidth / 2,
|
|
953
|
+
cy: window.innerHeight / 2,
|
|
954
|
+
};
|
|
955
|
+
window.__wgCameraLock = false;
|
|
956
|
+
window.__wgViewportToDoc = (vx, vy) => {
|
|
957
|
+
const cam = window.__wgCamera || {
|
|
958
|
+
scale: 1,
|
|
959
|
+
cx: window.innerWidth / 2,
|
|
960
|
+
cy: window.innerHeight / 2,
|
|
961
|
+
};
|
|
962
|
+
const s = cam.scale > 1.001 ? cam.scale : 1;
|
|
963
|
+
if (s <= 1.001) return { x: vx, y: vy };
|
|
964
|
+
return {
|
|
965
|
+
x: (vx - window.innerWidth / 2) / s + cam.cx,
|
|
966
|
+
y: (vy - window.innerHeight / 2) / s + cam.cy,
|
|
967
|
+
};
|
|
968
|
+
};
|
|
969
|
+
window.__wgElDocPoint = (el) => {
|
|
970
|
+
const r = el.getBoundingClientRect();
|
|
971
|
+
return window.__wgViewportToDoc(
|
|
972
|
+
r.left + r.width / 2,
|
|
973
|
+
r.top + r.height / 2,
|
|
974
|
+
);
|
|
975
|
+
};
|
|
897
976
|
window.__wgSetCamera = (fx, fy, scale, ms) => {
|
|
898
977
|
const body = document.body;
|
|
899
978
|
if (!body) return;
|
|
@@ -961,7 +1040,7 @@ async function installOverlay(page, title) {
|
|
|
961
1040
|
'<div class="wg-todo-head">Checklist</div><ul>' + rows + '</ul>';
|
|
962
1041
|
modal.classList.add("wg-todo-on");
|
|
963
1042
|
};
|
|
964
|
-
window.__wgResetCamera = () => {
|
|
1043
|
+
window.__wgResetCamera = () => {
|
|
965
1044
|
window.__wgSetCamera(window.innerWidth / 2, window.innerHeight / 2, 1);
|
|
966
1045
|
};
|
|
967
1046
|
window.__wgClearZoom = () => {
|
|
@@ -1413,13 +1492,17 @@ async function applyCameraZoom(page, box, zoom, ms) {
|
|
|
1413
1492
|
}).catch(() => {});
|
|
1414
1493
|
return;
|
|
1415
1494
|
}
|
|
1416
|
-
const
|
|
1417
|
-
const
|
|
1495
|
+
const vx = box.x + box.width / 2;
|
|
1496
|
+
const vy = box.y + box.height / 2;
|
|
1418
1497
|
const dur = ms != null ? ms : 650;
|
|
1419
1498
|
await page
|
|
1420
|
-
.evaluate(({
|
|
1421
|
-
if (window.__wgSetCamera)
|
|
1422
|
-
|
|
1499
|
+
.evaluate(({ vx, vy, scale, dur }) => {
|
|
1500
|
+
if (!window.__wgSetCamera) return;
|
|
1501
|
+
const pt = window.__wgViewportToDoc
|
|
1502
|
+
? window.__wgViewportToDoc(vx, vy)
|
|
1503
|
+
: { x: vx, y: vy };
|
|
1504
|
+
window.__wgSetCamera(pt.x, pt.y, scale, dur);
|
|
1505
|
+
}, { vx, vy, scale: z, dur })
|
|
1423
1506
|
.catch(() => {});
|
|
1424
1507
|
}
|
|
1425
1508
|
|
|
@@ -1553,16 +1636,9 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
|
1553
1636
|
: h.follow !== undefined
|
|
1554
1637
|
? !!h.follow
|
|
1555
1638
|
: z > 1;
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
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).
|
|
1639
|
+
// Track/zoom first, then park the demo cursor on the LIVE rect.
|
|
1640
|
+
// Placing the cursor on a pre-zoom boundingBox leaves it stranded after
|
|
1641
|
+
// body scale (html cursor is outside the transform).
|
|
1566
1642
|
await page
|
|
1567
1643
|
.evaluate(
|
|
1568
1644
|
({ sel, label, tone, size, weight, focus, color, zoom, followMouse, camMs }) => {
|
|
@@ -1600,7 +1676,21 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
|
1600
1676
|
.catch(() => false);
|
|
1601
1677
|
const waitMs = alreadyZoomed ? Math.min(camMs, 420) : camMs;
|
|
1602
1678
|
await new Promise((res) => setTimeout(res, waitMs));
|
|
1603
|
-
|
|
1679
|
+
if (h.cursor !== false) {
|
|
1680
|
+
await page
|
|
1681
|
+
.evaluate(({ sel, ms }) => {
|
|
1682
|
+
const el = document.querySelector(sel);
|
|
1683
|
+
if (!el || !window.__wgMoveCursorTo) return;
|
|
1684
|
+
window.__wgCameraLock = true;
|
|
1685
|
+
const r = el.getBoundingClientRect();
|
|
1686
|
+
window.__wgMoveCursorTo(r.left + r.width / 2, r.top + r.height / 2, ms, false);
|
|
1687
|
+
setTimeout(() => {
|
|
1688
|
+
window.__wgCameraLock = false;
|
|
1689
|
+
}, ms + 40);
|
|
1690
|
+
}, { sel: h.selector, ms: Math.min(280, camMs) })
|
|
1691
|
+
.catch(() => {});
|
|
1692
|
+
await new Promise((res) => setTimeout(res, Math.min(280, camMs)));
|
|
1693
|
+
}
|
|
1604
1694
|
// Appear settle
|
|
1605
1695
|
await new Promise((res) => setTimeout(res, fadeMs));
|
|
1606
1696
|
const authored = resolveFixtureDwellMs(h, { gatesFast, pace });
|
|
@@ -1623,6 +1713,14 @@ async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
|
1623
1713
|
const last = list.length > 0 ? list[list.length - 1] : null;
|
|
1624
1714
|
if (!last || last.zoomOut !== false) {
|
|
1625
1715
|
await resetCameraZoom(page);
|
|
1716
|
+
} else {
|
|
1717
|
+
// Hold lock into the upcoming act click so follow/scroll cannot yank
|
|
1718
|
+
// the camera off the already-centered target (cart teleport bug).
|
|
1719
|
+
await page
|
|
1720
|
+
.evaluate(() => {
|
|
1721
|
+
window.__wgCameraLock = true;
|
|
1722
|
+
})
|
|
1723
|
+
.catch(() => {});
|
|
1626
1724
|
}
|
|
1627
1725
|
if (list.length === 0) {
|
|
1628
1726
|
await hideRing(page);
|
|
@@ -2359,12 +2457,16 @@ async function teardownOverlay(page) {
|
|
|
2359
2457
|
.catch(() => {});
|
|
2360
2458
|
}
|
|
2361
2459
|
|
|
2362
|
-
async function moveCursorTo(page, box, ms) {
|
|
2460
|
+
async function moveCursorTo(page, box, ms, opts) {
|
|
2363
2461
|
const x = box.x + box.width / 2;
|
|
2364
2462
|
const y = box.y + box.height / 2;
|
|
2365
2463
|
const travel = Math.max(0, Number(ms) || 0);
|
|
2464
|
+
const keepLock = !!(opts && opts.keepLock);
|
|
2465
|
+
// Freeze camera follow while the demo cursor travels - otherwise dead-zone
|
|
2466
|
+
// pans fight the click target (esp. right-edge cart chrome).
|
|
2366
2467
|
await page
|
|
2367
2468
|
.evaluate(({ x, y, ms }) => {
|
|
2469
|
+
window.__wgCameraLock = true;
|
|
2368
2470
|
if (window.__wgMoveCursorTo) window.__wgMoveCursorTo(x, y, ms);
|
|
2369
2471
|
}, { x, y, ms: travel })
|
|
2370
2472
|
.catch(() => {});
|
|
@@ -2374,6 +2476,13 @@ async function moveCursorTo(page, box, ms) {
|
|
|
2374
2476
|
if (travel > 0) {
|
|
2375
2477
|
await new Promise((res) => setTimeout(res, travel));
|
|
2376
2478
|
}
|
|
2479
|
+
if (!keepLock) {
|
|
2480
|
+
await page
|
|
2481
|
+
.evaluate(() => {
|
|
2482
|
+
window.__wgCameraLock = false;
|
|
2483
|
+
})
|
|
2484
|
+
.catch(() => {});
|
|
2485
|
+
}
|
|
2377
2486
|
return { x, y };
|
|
2378
2487
|
}
|
|
2379
2488
|
|
|
@@ -2501,6 +2610,7 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2501
2610
|
proto.click = async function (options) {
|
|
2502
2611
|
let clickPoint = null;
|
|
2503
2612
|
let clickTone = "auto";
|
|
2613
|
+
let keptCameraLock = false;
|
|
2504
2614
|
try {
|
|
2505
2615
|
await installOverlay(page);
|
|
2506
2616
|
// Wait until the target is visible BEFORE measuring - otherwise
|
|
@@ -2511,6 +2621,20 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2511
2621
|
state: "visible",
|
|
2512
2622
|
timeout: (options && options.timeout) || 30000,
|
|
2513
2623
|
}).catch(() => {});
|
|
2624
|
+
// Body-scale camera + Playwright scrollIntoView = teleport storm.
|
|
2625
|
+
// Lock the camera for the whole click when zoomed; force-click so
|
|
2626
|
+
// Playwright does not try to scroll the transformed page.
|
|
2627
|
+
const zoomed = await page
|
|
2628
|
+
.evaluate(() => !!(window.__wgCamera && window.__wgCamera.scale > 1.001))
|
|
2629
|
+
.catch(() => false);
|
|
2630
|
+
if (zoomed) {
|
|
2631
|
+
await page
|
|
2632
|
+
.evaluate(() => {
|
|
2633
|
+
window.__wgCameraLock = true;
|
|
2634
|
+
})
|
|
2635
|
+
.catch(() => {});
|
|
2636
|
+
keptCameraLock = true;
|
|
2637
|
+
}
|
|
2514
2638
|
const box = await this.boundingBox();
|
|
2515
2639
|
const narrated = box ? await wasJustNarrated(page) : false;
|
|
2516
2640
|
if (box && !narrated) {
|
|
@@ -2532,8 +2656,18 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2532
2656
|
const stubs = (stubBeforeRef && stubBeforeRef.current) || [];
|
|
2533
2657
|
const cap = await captionForLocator(page, this, stubs, fallback);
|
|
2534
2658
|
clickTone = cap.tone;
|
|
2535
|
-
|
|
2536
|
-
|
|
2659
|
+
// Already centered from stubBefore zoom: short cursor, keep lock.
|
|
2660
|
+
const travel = zoomed ? cursorMs(160) : cursorMs(600);
|
|
2661
|
+
clickPoint = await moveCursorTo(page, box, travel, {
|
|
2662
|
+
keepLock: zoomed,
|
|
2663
|
+
});
|
|
2664
|
+
// Re-measure after cursor settle - stale pre-travel box teleports the ring.
|
|
2665
|
+
const liveBox = (await this.boundingBox()) || box;
|
|
2666
|
+
clickPoint = {
|
|
2667
|
+
x: liveBox.x + liveBox.width / 2,
|
|
2668
|
+
y: liveBox.y + liveBox.height / 2,
|
|
2669
|
+
};
|
|
2670
|
+
await showRing(page, liveBox, cap.label, cap.tone, {
|
|
2537
2671
|
size: cap.size,
|
|
2538
2672
|
weight: cap.weight,
|
|
2539
2673
|
});
|
|
@@ -2545,7 +2679,10 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2545
2679
|
} else if (box) {
|
|
2546
2680
|
// Ring/caption already handled by narrate() - still move the
|
|
2547
2681
|
// cursor + pulse the click point, just skip re-showing the ring.
|
|
2548
|
-
|
|
2682
|
+
const travel = zoomed ? cursorMs(120) : cursorMs(200);
|
|
2683
|
+
clickPoint = await moveCursorTo(page, box, travel, {
|
|
2684
|
+
keepLock: zoomed,
|
|
2685
|
+
});
|
|
2549
2686
|
clickTone = "planned";
|
|
2550
2687
|
}
|
|
2551
2688
|
} catch {
|
|
@@ -2561,10 +2698,27 @@ function instrumentInteractionHighlighting(page, mem, slowMo, pacing, stubBefore
|
|
|
2561
2698
|
// mock click visually completes before the real one fires.
|
|
2562
2699
|
await new Promise((res) => setTimeout(res, skipTheater() ? 0 : 500));
|
|
2563
2700
|
}
|
|
2564
|
-
const
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2701
|
+
const zoomedClick = await page
|
|
2702
|
+
.evaluate(() => !!(window.__wgCamera && window.__wgCamera.scale > 1.001))
|
|
2703
|
+
.catch(() => false);
|
|
2704
|
+
const clickOpts =
|
|
2705
|
+
zoomedClick || keptCameraLock
|
|
2706
|
+
? { ...(options || {}), force: true }
|
|
2707
|
+
: options;
|
|
2708
|
+
try {
|
|
2709
|
+
const result = await originalClick.call(this, clickOpts);
|
|
2710
|
+
await new Promise((res) => setTimeout(res, clickPostPop()));
|
|
2711
|
+
await hideRing(page);
|
|
2712
|
+
return result;
|
|
2713
|
+
} finally {
|
|
2714
|
+
if (keptCameraLock) {
|
|
2715
|
+
await page
|
|
2716
|
+
.evaluate(() => {
|
|
2717
|
+
window.__wgCameraLock = false;
|
|
2718
|
+
})
|
|
2719
|
+
.catch(() => {});
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2568
2722
|
};
|
|
2569
2723
|
}
|
|
2570
2724
|
|