From 57827157823219ffb71047fd65217ebbd2420a9d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 1 May 2025 15:32:25 -0400
Subject: from last

---
 src/client/views/ObservableReactComponent.tsx | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

(limited to 'src/client/views/ObservableReactComponent.tsx')

diff --git a/src/client/views/ObservableReactComponent.tsx b/src/client/views/ObservableReactComponent.tsx
index bb7a07f0e..2290516dc 100644
--- a/src/client/views/ObservableReactComponent.tsx
+++ b/src/client/views/ObservableReactComponent.tsx
@@ -15,6 +15,33 @@ export abstract class ObservableReactComponent<T> extends React.Component<T, obj
         this._props = props;
         makeObservable(this);
     }
+    __passiveWheel: HTMLElement | null = null;
+    _isContentActive: () => boolean | undefined = () => false;
+
+    /**
+     * default method to stop wheel events from bubbling up to parent components.
+     * @param e
+     */
+    onPassiveWheel = (e: WheelEvent) => {
+        if (this._isContentActive?.()) e.stopPropagation();
+    };
+
+    /**
+     * This fixes the problem where a component uses wheel events to scroll, but is nested inside another component that
+     * can also scroll. In that case, the wheel event will bubble up to the parent component and cause it to scroll in addition.
+     * This is based on the native HTML5 behavior where wheel events are passive by default, meaning that they do not prevent the default action of scrolling.
+     * This method should be called from a ref={} property on or above the component that uses wheel events to scroll.
+     * @param ele HTMLELement containing the component that will scroll
+     * @param isContentActive function determining if the component is active and should handle the wheel event.
+     * @param onPassiveWheel an optional function to call to handle the wheel event (and block its propagation. If omitted, the event won't propagate.
+     */
+    fixWheelEvents = (ele: HTMLElement | null, isContentActive: () => boolean | undefined, onPassiveWheel?: (e: WheelEvent) => void) => {
+        this._isContentActive = isContentActive;
+        this.__passiveWheel?.removeEventListener('wheel', onPassiveWheel ?? this.onPassiveWheel);
+        this.__passiveWheel = ele;
+        ele?.addEventListener('wheel', onPassiveWheel ?? this.onPassiveWheel, { passive: false });
+    };
+
     componentDidUpdate(prevProps: Readonly<T>): void {
         Object.keys(prevProps)
             .filter(pkey => (prevProps as {[key:string]: unknown})[pkey] !== (this.props as  {[key:string]: unknown})[pkey])
-- 
cgit v1.2.3-70-g09d2

