| 1 | import { throttle } from './utils.js'; |
| 2 | |
| 3 | export 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 | */ |
| 12 | function 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 | } |