feat: add zoom slider and trackpad panning#985
Conversation
✅ Deploy Preview for circuitverse ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughNavbar zoom controls were replaced with discrete increment/decrement buttons in desktop and mobile QuickButton components instead of a range slider. Wheel/trackpad events now perform canvas panning using cross‑browser delta extraction ( 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This pull request adds trackpad panning support to the circuit simulator canvas and refactors the zoom controls from a jQuery-based implementation to a Vue-based implementation. The changes modify how users navigate the canvas by replacing scroll-to-zoom with scroll-to-pan behavior, while keeping zoom controls accessible via keyboard shortcuts and UI slider buttons.
Changes:
- Changed mouse wheel/trackpad scroll events from zooming to panning the canvas
- Replaced jQuery-based zoom slider implementation with Vue-based reactive zoom controls in QuickButton and QuickButtonMobile components
- Added keyboard shortcut enhancements for zoom (supporting both keyCode and e.key checks)
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/simulator/src/listeners.js | Replaced MouseScroll (zoom) with handleCanvasPan (pan), removed jQuery zoom slider listeners, added setZoomFromSlider/getZoomSliderValue exports, removed desktop app Tauri event listeners |
| src/simulator/src/engine.js | Updated comment to clarify existing drag-to-pan behavior |
| src/simulator/src/embedListeners.js | Enhanced keyboard zoom shortcuts to support e.key in addition to keyCode |
| src/components/Navbar/QuickButton/QuickButton.vue | Replaced jQuery zoom slider with Vue reactive implementation, added zoom conversion logic and state management |
| src/components/Navbar/QuickButton/QuickButtonMobile.vue | Replaced jQuery zoom slider with Vue reactive implementation (similar to desktop but with mobile-specific styling) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
src/simulator/src/listeners.js (2)
781-783: Refresh this comment to match current behavior.The note says slider-based zoom was removed, but this PR adds slider helpers and slider-driven zoom usage. Keeping this stale will mislead future changes.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/simulator/src/listeners.js` around lines 781 - 783, Update the stale comment near the embed check in listeners.js: replace the note that says the slider was removed and zoom is controlled only by ZoomIn/ZoomOut with a concise description that the code now includes slider helpers and supports slider-driven zoom (mentioning the zoomSliderListeners helper and the ZoomIn/ZoomOut controls), and clarify when the slider logic is enabled/disabled relative to the embed flag so future readers won't be misled.
600-620: AdddeltaModenormalization to ensure consistent pan speeds across browsers and devices.The
extractScrollDelta()function currently extractsdeltaXanddeltaYwithout checking thedeltaModeproperty. WhendeltaModeis 1 (lines) or 2 (pages), these values need pixel normalization. Without this, pan speed will be inconsistent across different browsers and input devices.Proposed fix
function extractScrollDelta(event) { let deltaX = 0 let deltaY = 0 @@ if (event.deltaX !== undefined && event.deltaY !== undefined) { // Modern browsers: event.deltaX, event.deltaY deltaX = event.deltaX deltaY = event.deltaY + // Normalize to pixel units when needed + if (event.deltaMode === 1) { + // lines -> pixels (approx) + deltaX *= 16 + deltaY *= 16 + } else if (event.deltaMode === 2) { + // pages -> pixels (approx viewport) + deltaX *= window.innerWidth + deltaY *= window.innerHeight + } } else if (event.wheelDeltaX !== undefined && event.wheelDeltaY !== undefined) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/simulator/src/listeners.js` around lines 600 - 620, The extractScrollDelta function must normalize for event.deltaMode so pan speeds are consistent: after computing deltaX/deltaY from deltaX/deltaY, wheelDelta*, wheelDelta or detail, check event.deltaMode and, when ===1 (lines) multiply deltas by a reasonable line height constant (e.g., ~16) and when ===2 (pages) multiply by viewport height (e.g., window.innerHeight) to convert to pixels; ensure this normalization uses the already-computed deltaX/deltaY values and is applied before returning from extractScrollDelta.src/components/Navbar/QuickButton/QuickButton.vue (1)
99-153: Extract shared zoom mapping logic into a composable.This conversion/clamp/init/update block is duplicated with
src/components/Navbar/QuickButton/QuickButtonMobile.vue, which increases drift risk.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/Navbar/QuickButton/QuickButton.vue` around lines 99 - 153, The zoom conversion/clamping/initialization/update logic is duplicated and should be extracted into a reusable composable; create a composable (e.g., useZoomComposable) that exports the constants DISPLAY_ZOOM_MIN/MAX/DEFAULT/STEP and INTERNAL_ZOOM_MIN/MAX, the reactive displayZoomPercent ref, and the functions clampZoomPercent, convertInternalToDisplayZoom, convertDisplayToInternalZoom, initializeZoomSlider and updateSimulatorZoom (which internally call getZoomSliderValue and setZoomFromSlider as before), then replace the duplicated implementations in QuickButton.vue and QuickButtonMobile.vue by importing and using the composable so both components share the same logic and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/Navbar/QuickButton/QuickButton.vue`:
- Around line 121-156: The zoom slider is only synced once in onMounted via
initializeZoomSlider, so displayZoomPercent can drift when zoom changes
elsewhere; fix by adding a reactive sync that updates displayZoomPercent
whenever the authoritative zoom changes (e.g., add a watcher or event listener
that calls initializeZoomSlider or updateSimulatorZoom when the simulator's
zoom/state changes), reference initializeZoomSlider, updateSimulatorZoom and
onMounted to locate the logic, and ensure you register the listener/watcher
during mount and remove it onBeforeUnmount to avoid leaks.
In `@src/simulator/src/embedListeners.js`:
- Around line 182-187: The keyboard shortcut checks for zoom use incorrect
`KeyboardEvent` property casing—replace the undefined `e.KeyCode` and
`e.Keycode` with the correct `e.keyCode` in the zoom-in and zoom-out
conditionals so the control+plus and control+minus shortcuts properly detect key
codes; update the conditions around the ZoomIn() and ZoomOut() calls in
embedListeners (the lines checking `simulationArea.controlDown`) to use
`e.keyCode` consistently.
In `@src/simulator/src/listeners.js`:
- Around line 659-665: The legacy-only listeners for canvas panning miss modern
browsers; add a standard "wheel" event listener on the element with id
"simulationArea" so handleCanvasPan is invoked for modern wheel events (it
already supports deltaX/deltaY via extractScrollDelta); update the registration
near the existing calls that addEventListener('mousewheel', handleCanvasPan) and
addEventListener('DOMMouseScroll', handleCanvasPan) to also call
addEventListener('wheel', handleCanvasPan) so panning works reliably across
browsers.
---
Nitpick comments:
In `@src/components/Navbar/QuickButton/QuickButton.vue`:
- Around line 99-153: The zoom conversion/clamping/initialization/update logic
is duplicated and should be extracted into a reusable composable; create a
composable (e.g., useZoomComposable) that exports the constants
DISPLAY_ZOOM_MIN/MAX/DEFAULT/STEP and INTERNAL_ZOOM_MIN/MAX, the reactive
displayZoomPercent ref, and the functions clampZoomPercent,
convertInternalToDisplayZoom, convertDisplayToInternalZoom, initializeZoomSlider
and updateSimulatorZoom (which internally call getZoomSliderValue and
setZoomFromSlider as before), then replace the duplicated implementations in
QuickButton.vue and QuickButtonMobile.vue by importing and using the composable
so both components share the same logic and behavior.
In `@src/simulator/src/listeners.js`:
- Around line 781-783: Update the stale comment near the embed check in
listeners.js: replace the note that says the slider was removed and zoom is
controlled only by ZoomIn/ZoomOut with a concise description that the code now
includes slider helpers and supports slider-driven zoom (mentioning the
zoomSliderListeners helper and the ZoomIn/ZoomOut controls), and clarify when
the slider logic is enabled/disabled relative to the embed flag so future
readers won't be misled.
- Around line 600-620: The extractScrollDelta function must normalize for
event.deltaMode so pan speeds are consistent: after computing deltaX/deltaY from
deltaX/deltaY, wheelDelta*, wheelDelta or detail, check event.deltaMode and,
when ===1 (lines) multiply deltas by a reasonable line height constant (e.g.,
~16) and when ===2 (pages) multiply by viewport height (e.g.,
window.innerHeight) to convert to pixels; ensure this normalization uses the
already-computed deltaX/deltaY values and is applied before returning from
extractScrollDelta.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/components/Navbar/QuickButton/QuickButton.vuesrc/components/Navbar/QuickButton/QuickButtonMobile.vuesrc/simulator/src/embedListeners.jssrc/simulator/src/engine.jssrc/simulator/src/listeners.js
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/simulator/src/listeners.js (1)
663-669:⚠️ Potential issue | 🟠 MajorRegister the standard
wheellistener for reliable panning.Line 665-Line 669 only bind legacy events. On browsers that emit only
wheel, canvas pan won’t trigger.Proposed fix
- document - .getElementById('simulationArea') - .addEventListener('mousewheel', handleCanvasPan) - document - .getElementById('simulationArea') - .addEventListener('DOMMouseScroll', handleCanvasPan) + const simulationAreaElement = document.getElementById('simulationArea') + simulationAreaElement.addEventListener('wheel', handleCanvasPan, { passive: false }) + simulationAreaElement.addEventListener('mousewheel', handleCanvasPan, { passive: false }) + simulationAreaElement.addEventListener('DOMMouseScroll', handleCanvasPan, { passive: false })#!/bin/bash # Verify which wheel-related listeners are currently registered in main simulator listeners. rg -n "addEventListener\\(['\"](wheel|mousewheel|DOMMouseScroll)['\"]" src/simulator/src/listeners.js -C2
🧹 Nitpick comments (4)
src/components/Navbar/QuickButton/QuickButton.vue (2)
75-76: Set explicittype="button"on the new zoom controls.Without explicit type, these default to
submitin form contexts.Proposed fix
- <button class="zoom-button-decrement" `@click`="decrement" title="Zoom Out">−</button> - <button class="zoom-button-increment" `@click`="increment" title="Zoom In">+</button> + <button type="button" class="zoom-button-decrement" `@click`="decrement" title="Zoom Out">−</button> + <button type="button" class="zoom-button-increment" `@click`="increment" title="Zoom In">+</button>
223-269: Remove stale slider-specific styles to keep CSS aligned with the template.The slider UI is no longer present, but
.zoom-slider/.zoom-labelrules remain.src/components/Navbar/QuickButton/QuickButtonMobile.vue (2)
86-87: Set explicittype="button"for mobile zoom buttons.This avoids accidental form submission behavior in embedded/form contexts.
Proposed fix
- <button class="zoom-button-decrement" `@click`="decrement" title="Zoom Out">−</button> - <button class="zoom-button-increment" `@click`="increment" title="Zoom In">+</button> + <button type="button" class="zoom-button-decrement" `@click`="decrement" title="Zoom Out">−</button> + <button type="button" class="zoom-button-increment" `@click`="increment" title="Zoom In">+</button>
176-222: Clean up leftover slider CSS in mobile quick controls.
.zoom-sliderand.zoom-labelstyles are no longer used after the button-only zoom UI switch.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/components/Navbar/QuickButton/QuickButton.vuesrc/components/Navbar/QuickButton/QuickButtonMobile.vuesrc/simulator/src/embedListeners.jssrc/simulator/src/listeners.js
c13a478 to
4ef87f0
Compare
…mprove pan/zoom robustness
…ection and add inline documentation for wheel/trackpad handling
…e constants in applyZoomChange function
|
Please follow pr template :)) @AkshitBitsian356u |
There was a problem hiding this comment.
Hi @AkshitBitsian356u !!! Good direction on trackpad-to-pan! A few blockers:
- All Tauri listen() desktop handlers are accidentally deleted in listeners.js
- const DPR must move after imports.
- MIN_ZOOM_SCALE should be 0.5 * DPR.
Fix the listen() deletion first, rest are minor!

Screenshots/Video of the UI changes -
2026-03-01.23-56-41.mp4
Fix #987
Changes :
intuitive canvas navigation.
components for cleaner zoom UX.
to prevent conflicting interactions.
inputs for consistent zoom behavior.
applyCanvasPanand added inline documentation forwheel/trackpad event handling.
findDimensionscall for minimap setup andrefactored zoom scale constants in
applyZoomChange.devicePixelRatioconstant (DPR) for consistentscaling across devices, fixing blank simulator screen.
Code Understanding and AI Usage
Did you use AI assistance (ChatGPT, Claude, Copilot, etc.) to write any part of this code?
If you used AI assistance:
Explain your implementation approach:
Checklist before requesting a review
Note: Please check Allow edits from maintainers if you would like us to assist in the PR.
Summary by CodeRabbit
New Features
Improvements