From 7ededf9bf5bab0ce29e128f1dbbf655f29080968 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Sun, 29 Jun 2025 17:40:54 -0700 Subject: [PATCH] Prevent default on scroll events Fixes #1301 --- packages/webamp/CHANGELOG.md | 1 + packages/webamp/js/actionCreators/media.ts | 6 ++-- packages/webamp/js/components/DropTarget.tsx | 31 ++++++++++++++++--- .../webamp/js/components/MainWindow/index.tsx | 2 +- .../js/components/PlaylistWindow/index.tsx | 2 +- 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/packages/webamp/CHANGELOG.md b/packages/webamp/CHANGELOG.md index f5b607ff..a1fc9a84 100644 --- a/packages/webamp/CHANGELOG.md +++ b/packages/webamp/CHANGELOG.md @@ -14,6 +14,7 @@ ### Bug Fixes +- Fix bug where scrolling the main window or playlist window would change the volume but also (incorrectly) scroll the page. - Fix bug where resizing the window such that the current layout cannot fit on the page, while also scrolled down the page, would cause the layout to be recentered out of view. - Avoid a console log from Redux Dev Tools. diff --git a/packages/webamp/js/actionCreators/media.ts b/packages/webamp/js/actionCreators/media.ts index b5296ef4..561b9d46 100644 --- a/packages/webamp/js/actionCreators/media.ts +++ b/packages/webamp/js/actionCreators/media.ts @@ -122,10 +122,8 @@ export function adjustVolume(volumeDiff: number): Thunk { }; } -export function scrollVolume(e: React.WheelEvent): Thunk { - // https://github.com/facebook/react/issues/22794 - // e.preventDefault(); - // TODO: https://github.com/captbaritone/webamp/issues/1301 +export function scrollVolume(e: WheelEvent): Thunk { + e.preventDefault(); return (dispatch, getState) => { const currentVolume = getState().media.volume; // Using pixels as percentage difference here is a bit arbitrary, but... oh well. diff --git a/packages/webamp/js/components/DropTarget.tsx b/packages/webamp/js/components/DropTarget.tsx index 7295ed94..65418bd9 100644 --- a/packages/webamp/js/components/DropTarget.tsx +++ b/packages/webamp/js/components/DropTarget.tsx @@ -1,4 +1,4 @@ -import { useCallback } from "react"; +import { useCallback, useEffect, useRef } from "react"; import { useActionCreator } from "../hooks"; import * as Actions from "../actionCreators"; import { WindowId } from "../types"; @@ -11,6 +11,7 @@ interface Coord { interface Props extends React.HTMLAttributes { handleDrop(e: React.DragEvent, coord: Coord): void; windowId: WindowId; + onWheelActive?: (e: WheelEvent) => void; } function suppress(e: React.DragEvent) { @@ -20,16 +21,37 @@ function suppress(e: React.DragEvent) { e.dataTransfer.effectAllowed = "link"; } -const DropTarget = (props: Props) => { +export default function DropTarget(props: Props) { const { // eslint-disable-next-line no-shadow, no-unused-vars handleDrop, windowId, + onWheelActive, ...passThroughProps } = props; + const divRef = useRef(null); const droppedFiles = useActionCreator(Actions.droppedFiles); + // Register onWheelActive as a non-passive event handler + useEffect(() => { + const element = divRef.current; + if (!element || !onWheelActive) { + return; + } + + const handleWheel = (e: WheelEvent) => { + // Convert native WheelEvent to React.WheelEvent + onWheelActive(e); + }; + + element.addEventListener("wheel", handleWheel, { passive: false }); + + return () => { + element.removeEventListener("wheel", handleWheel); + }; + }, [onWheelActive]); + const onDrop = useCallback( (e: React.DragEvent) => { suppress(e); @@ -48,6 +70,7 @@ const DropTarget = (props: Props) => { ); return (
{ onDrop={onDrop} /> ); -}; - -export default DropTarget; +} diff --git a/packages/webamp/js/components/MainWindow/index.tsx b/packages/webamp/js/components/MainWindow/index.tsx index 0a401725..b0ad7fe5 100644 --- a/packages/webamp/js/components/MainWindow/index.tsx +++ b/packages/webamp/js/components/MainWindow/index.tsx @@ -77,7 +77,7 @@ const MainWindow = React.memo(({ analyser, filePickers }: Props) => { windowId={WINDOWS.MAIN} className={className} handleDrop={loadMedia} - onWheel={scrollVolume} + onWheelActive={scrollVolume} >