Blame Raw
· · · 66 lines (2.8 KB)
0 contributors
1import { throttle } from './utils.js';
2
3export function initDomHandlers() {
4 handleInputWheel();
5}
6
7/**
8 * Trap mouse wheel inside of focused number inputs to prevent scrolling their containers.
9 * Instead of firing wheel events, manually update both slider and input values.
10 * This also makes wheel work inside Firefox.
11 */
12function handleInputWheel() {
13 const minInterval = 25; // ms
14
15 /**
16 * Update input and slider values based on wheel delta
17 * @param {HTMLInputElement} input The number input element
18 * @param {HTMLInputElement|null} slider The associated range input element, if any
19 * @param {number} deltaY The wheel deltaY value
20 */
21 function updateValue(input, slider, deltaY) {
22 const currentValue = parseFloat(input.value);
23 const step = parseFloat(input.step);
24 const min = parseFloat(input.min);
25 const max = parseFloat(input.max);
26
27 // Sanity checks before trying to calculate new value
28 if (isNaN(currentValue) || isNaN(step) || step <= 0 || deltaY === 0) return;
29
30 // Calculate new value based on wheel movement delta (negative = up, positive = down)
31 let newValue = currentValue + (deltaY > 0 ? -step : step);
32 // Ensure it's a multiple of step
33 newValue = Math.round(newValue / step) * step;
34 // Ensure it's within the min and max range (NaN-aware)
35 newValue = !isNaN(min) ? Math.max(newValue, min) : newValue;
36 newValue = !isNaN(max) ? Math.min(newValue, max) : newValue;
37 // Simple fix for floating point precision issues
38 newValue = Math.round(newValue * 1e10) / 1e10;
39
40 // Update both input and slider values
41 input.value = newValue.toString();
42 if (slider) slider.value = newValue.toString();
43 // Trigger input event (just ONE) to update any listeners
44 const inputEvent = new Event('input', { bubbles: true });
45 input.dispatchEvent(inputEvent);
46 }
47
48 const updateValueThrottled = throttle(updateValue, minInterval);
49
50 document.addEventListener('wheel', (e) => {
51 // Try to carefully narrow down if we even need to fire this handler
52 const input = document.activeElement instanceof HTMLInputElement ? document.activeElement : null;
53 if (input && input.type === 'number' && input.hasAttribute('step')) {
54 const parent = input.closest('.range-block-range-and-counter') ?? input.closest('div') ?? input.parentElement;
55 const slider = /** @type {HTMLInputElement} */ (parent?.querySelector('input[type="range"]'));
56
57 // Stop propagation for either target
58 if (e.target === input || (slider && e.target === slider)) {
59 e.stopPropagation();
60 e.preventDefault();
61
62 updateValueThrottled(input, slider, e.deltaY);
63 }
64 }
65 }, { passive: false });
66}