fix(timeline): keep scroll zoom anchored to cursor#860
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #860 +/- ##
=======================================
Coverage 34.38% 34.38%
=======================================
Files 36 36
Lines 2114 2114
Branches 408 408
=======================================
Hits 727 727
Misses 1308 1308
Partials 79 79 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Greptile SummaryThis PR fixes a zoom-anchor drift bug in the vis-timeline component that occurred because
Confidence Score: 5/5The change is narrowly scoped to wheel-event routing in a single Vue component; the new capture-phase handler, deltaMode normalisation, and beforeDestroy cleanup are all correct, and preferZoom:true cleanly removes the double-processing that caused the reported drift. The fix correctly intercepts only horizontally-dominant wheel events in the capture phase, normalises delta values across all three deltaMode variants, and prevents vis-timeline from reprocessing the same event. The listener lifecycle (add in mounted, remove in beforeDestroy) is handled properly. No correctness defects were found in the changed code. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
W[WheelEvent fires on #visualization]
W --> CP[Capture phase: onHorizontalWheel]
CP --> COND{"|deltaX| > |deltaY|?"}
COND -- "No (vertical dominates)" --> PASSTHROUGH[Return — event propagates normally]
PASSTHROUGH --> VT_ZOOM[vis-timeline bubble-phase handler\npreferZoom:true → zoom around cursor]
COND -- "Yes (horizontal dominates)" --> NORM["Normalise deltaX\n• DOM_DELTA_LINE → x40\n• DOM_DELTA_PAGE → x800\n• DOM_DELTA_PIXEL → unchanged"]
NORM --> CALC["diff = (deltaX/120) x (windowSpan/20)"]
CALC --> SW["timeline.setWindow(start+diff, end+diff)"]
SW --> STOP["preventDefault + stopImmediatePropagation\n→ vis-timeline never sees event"]
Reviews (2): Last reviewed commit: "fix(timeline): normalize horizontal whee..." | Re-trigger Greptile |
| const currentWindow = this.timeline.getWindow(); | ||
| const start = currentWindow.start.valueOf(); | ||
| const end = currentWindow.end.valueOf(); | ||
| const diff = (event.deltaX / 120) * ((end - start) / 20); |
There was a problem hiding this comment.
The panning delta is computed as
event.deltaX / 120 without checking WheelEvent.deltaMode. When deltaMode === 1 (DOM_DELTA_LINE, which Firefox uses for tilt-wheel mice), deltaX is typically 1–3 lines per notch instead of 120 pixels, making deltaX / 120 roughly 0.008–0.025 — effectively no visible panning. A deltaMode normalisation step (pixel ≈ 1×, line ≈ 40×, page ≈ 800×) keeps the formula consistent across browsers.
| const diff = (event.deltaX / 120) * ((end - start) / 20); | |
| // Normalise deltaX to CSS-pixel equivalents across deltaMode values | |
| // (DOM_DELTA_PIXEL=0 ~1px, DOM_DELTA_LINE=1 ~40px, DOM_DELTA_PAGE=2 ~800px). | |
| const PIXELS_PER_LINE = 40; | |
| const PIXELS_PER_PAGE = 800; | |
| let deltaXpx = event.deltaX; | |
| if (event.deltaMode === 1) deltaXpx *= PIXELS_PER_LINE; | |
| else if (event.deltaMode === 2) deltaXpx *= PIXELS_PER_PAGE; | |
| const diff = (deltaXpx / 120) * ((end - start) / 20); |
There was a problem hiding this comment.
Fixed in fe3dec5: normalized deltaX for DOM_DELTA_LINE and DOM_DELTA_PAGE before applying the pan formula. Verified with npm run lint -- src/visualizations/VisTimeline.vue and npm run build.
|
@greptileai review |
|
Ready for maintainer merge. Rechecked head I attempted a guarded squash merge with |
Summary
Root cause
With
horizontalScroll: true, vis-timeline first emits the wheel event to the range zoom handler, then its horizontal-scroll path pans the same vertical wheel event. That makes zoom behave like a mix of cursor anchor plus center/range drift.Fixes #847.
Verification
npm run lint -- src/visualizations/VisTimeline.vuenpm run build(passes; existing bundle-size and Sass/Babel deprecation warnings only)