From 6e4db8aad3ea366fc7c86f7f75df5ec599ec734e Mon Sep 17 00:00:00 2001 From: Aran-Fey Date: Tue, 14 Oct 2025 07:38:34 +0200 Subject: [PATCH] fix InputBox eating all pointer events (not just left button) --- frontend/code/eventHandling.ts | 12 +++++ frontend/code/inputBox.ts | 26 ++++++--- .../test_pointer_event_listener.py | 54 +++++++++++++++++++ 3 files changed, 86 insertions(+), 6 deletions(-) diff --git a/frontend/code/eventHandling.ts b/frontend/code/eventHandling.ts index 98b3e006..3e2e75a0 100644 --- a/frontend/code/eventHandling.ts +++ b/frontend/code/eventHandling.ts @@ -10,6 +10,18 @@ export function stopPropagation(event: Event): void { event.stopPropagation(); } +export function markLeftButtonAsHandled(event: PointerEvent): void { + if (event.button === 0) { + markEventAsHandled(event); + } +} + +export function stopLeftButtonPropagation(event: PointerEvent): void { + if (event.button === 0) { + event.stopPropagation(); + } +} + export abstract class EventHandler { component: ComponentBase; diff --git a/frontend/code/inputBox.ts b/frontend/code/inputBox.ts index 1d74aeb6..c840fe2c 100644 --- a/frontend/code/inputBox.ts +++ b/frontend/code/inputBox.ts @@ -1,6 +1,11 @@ import { TextStyle } from "./dataModels"; import { applyTextStyleCss, textStyleToCss } from "./cssUtils"; -import { markEventAsHandled, stopPropagation } from "./eventHandling"; +import { + markEventAsHandled, + stopPropagation, + stopLeftButtonPropagation, + markLeftButtonAsHandled, +} from "./eventHandling"; export type InputBoxStyle = "underlined" | "rounded" | "pill"; @@ -139,16 +144,22 @@ export class InputBox { // Consider any clicks on the input box as handled. This prevents e.g. // drag events when trying to select something. - this.outerElement.addEventListener("pointerdown", stopPropagation); - this.outerElement.addEventListener("pointerup", stopPropagation); + this.outerElement.addEventListener( + "pointerdown", + stopLeftButtonPropagation + ); + this.outerElement.addEventListener( + "pointerup", + stopLeftButtonPropagation + ); this.prefixTextElement.addEventListener( "pointerdown", - markEventAsHandled + markLeftButtonAsHandled ); this.suffixElementContainer.addEventListener( "pointerdown", - markEventAsHandled + markLeftButtonAsHandled ); // When clicked, focus the text element and move the cursor accordingly. @@ -180,7 +191,10 @@ export class InputBox { // Pointer down events select the input element and/or text in it (via // dragging), so let them do their default behavior but then stop them // from propagating to other elements - this._inputElement.addEventListener("pointerdown", stopPropagation); + this._inputElement.addEventListener( + "pointerdown", + stopLeftButtonPropagation + ); } private _hasDefaultHandler(event: KeyboardEvent): boolean { diff --git a/tests/test_frontend/test_pointer_event_listener.py b/tests/test_frontend/test_pointer_event_listener.py index 8d777453..fa83a549 100644 --- a/tests/test_frontend/test_pointer_event_listener.py +++ b/tests/test_frontend/test_pointer_event_listener.py @@ -103,3 +103,57 @@ async def test_specific_button_events( else: assert down_event is None assert up_event is None + + +@pytest.mark.parametrize("pressed_button", ["left", "middle", "right"]) +@pytest.mark.parametrize( + "build_child, child_eats_press_event, child_eats_pointer_events", + [ + (rio.Spacer, False, []), + (rio.TextInput, True, ["left"]), + ], +) +async def test_event_propagation( + pressed_button: t.Literal["left", "middle", "right"], + build_child: t.Callable[[], rio.Component], + child_eats_press_event: bool, + child_eats_pointer_events: t.Sequence[str], +) -> None: + press_event: rio.PointerEvent | None = None + down_event: rio.PointerEvent | None = None + up_event: rio.PointerEvent | None = None + + def on_press(e: rio.PointerEvent): + nonlocal press_event + press_event = e + + def on_pointer_down(e: rio.PointerEvent): + nonlocal down_event + down_event = e + + def on_pointer_up(e: rio.PointerEvent): + nonlocal up_event + up_event = e + + def build(): + return rio.PointerEventListener( + build_child(), + on_press=on_press, + on_pointer_down=on_pointer_down, + on_pointer_up=on_pointer_up, + ) + + async with BrowserClient(build) as client: + await client.click(0.5, 0.5, button=pressed_button, sleep=0.5) + + if pressed_button == "left" and not child_eats_press_event: + assert press_event is not None + else: + assert press_event is None + + if pressed_button not in child_eats_pointer_events: + assert down_event is not None + assert up_event is not None + else: + assert down_event is None + assert up_event is None