[GH-ISSUE #467] Scrollbox receives clicks outside its visible bounds (hit-testing ignores clipping) #122

Closed
opened 2026-03-02 23:44:42 +03:00 by kerem · 2 comments
Owner

Originally created by @nyxkrage on GitHub (Jan 3, 2026).
Original GitHub issue: https://github.com/anomalyco/opentui/issues/467

When a scrollbox contains content taller than its visible area, clicks outside the scrollbox's visible bounds are incorrectly captured by the scrollbox. The content is visually clipped correctly, but hit-testing uses the full unclipped content height.

  • When scrolled to top: clicking below the dialog hits the scrollbox
  • When scrolled to bottom: clicking above the dialog hits the scrollbox

Steps to Reproduce

  1. Create a dialog with an overlay (for click-to-close)
  2. Add a scrollbox inside the dialog with content taller than the visible area (e.g., 50 lines)
  3. Open the dialog
  4. Click directly below the scrollable content (in the overlay area)

Expected: The overlay receives the click and the dialog closes
Actual: The scrollbox receives the click (even though it's visually contained within the dialog)

Minimal Example

import { render, useKeyboard, useTerminalDimensions, useRenderer } from "@opentui/solid"
import { createSignal, Show, For } from "solid-js"

function App() {
  const [showDialog, setShowDialog] = createSignal(false)
  const [lastClick, setLastClick] = createSignal("none")
  const dimensions = useTerminalDimensions()
  const renderer = useRenderer()

  useKeyboard((key) => {
    if (key.name === "q") {
      renderer.destroy()
      process.exit(0)
    }
    if (key.name === "d") setShowDialog(!showDialog())
    if (key.name === "escape") setShowDialog(false)
  })

  const lines = Array.from({ length: 50 }, (_, i) => `Line ${i + 1}: This is some content`)

  return (
    <box flexDirection="column" height={dimensions().height} width={dimensions().width} backgroundColor="#1a1a2e">
      <text>Press 'd' to toggle dialog, 'q' to quit, 'esc' to close</text>
      <text>Last click: {lastClick()}</text>

      <Show when={showDialog()}>
        {/* Overlay - clicking here should close the dialog */}
        <box
          position="absolute"
          top={0}
          left={0}
          width={dimensions().width}
          height={dimensions().height}
          backgroundColor="#ff000033"
          onMouseDown={() => {
            setLastClick("OVERLAY (red)")
            setShowDialog(false)
          }}
        >
          <box
            position="absolute"
            top={Math.floor(dimensions().height / 4)}
            left={Math.floor(dimensions().width / 4)}
            width={Math.floor(dimensions().width / 2)}
            height={Math.floor(dimensions().height / 2)}
            backgroundColor="#0000ff"
            borderStyle="rounded"
            flexDirection="column"
            onMouseDown={(e: { stopPropagation: () => void }) => {
              console.log(">>> DIALOG BOX clicked")
              setLastClick("DIALOG BOX (blue)")
              e.stopPropagation()
            }}
          >
            <scrollbox
              flexGrow={1}
              backgroundColor="#ffff00"
              onMouseDown={(e: { stopPropagation: () => void }) => {
                setLastClick("SCROLLBOX (yellow)")
                e.stopPropagation()
              }}
            >
              <For each={lines}>{(line) => <text>{line}</text>}</For>
            </scrollbox>
          </box>
        </box>
      </Show>
    </box>
  )
}

render(() => <App />)
  • Press d to open the dialog
  • Click in the red area directly below the scrollable content in the dialog box
  • "Last click" shows "SCROLLBOX (yellow)" instead of "OVERLAY (red)" and closing the dialog

Environment

  • @opentui/core: 0.1.68
  • @opentui/solid: 0.1.68
  • bun: 1.3.5
  • OS: Linux x64
  • Terminal: Tested in Ghostty and the Zen integrated terminal
Originally created by @nyxkrage on GitHub (Jan 3, 2026). Original GitHub issue: https://github.com/anomalyco/opentui/issues/467 When a `scrollbox` contains content taller than its visible area, clicks outside the scrollbox's visible bounds are incorrectly captured by the scrollbox. The content is visually clipped correctly, but hit-testing uses the full unclipped content height. - When scrolled to top: clicking below the dialog hits the scrollbox - When scrolled to bottom: clicking above the dialog hits the scrollbox ## Steps to Reproduce 1. Create a dialog with an overlay (for click-to-close) 2. Add a scrollbox inside the dialog with content taller than the visible area (e.g., 50 lines) 3. Open the dialog 4. Click directly below the scrollable content (in the overlay area) Expected: The overlay receives the click and the dialog closes Actual: The scrollbox receives the click (even though it's visually contained within the dialog) ## Minimal Example ```tsx import { render, useKeyboard, useTerminalDimensions, useRenderer } from "@opentui/solid" import { createSignal, Show, For } from "solid-js" function App() { const [showDialog, setShowDialog] = createSignal(false) const [lastClick, setLastClick] = createSignal("none") const dimensions = useTerminalDimensions() const renderer = useRenderer() useKeyboard((key) => { if (key.name === "q") { renderer.destroy() process.exit(0) } if (key.name === "d") setShowDialog(!showDialog()) if (key.name === "escape") setShowDialog(false) }) const lines = Array.from({ length: 50 }, (_, i) => `Line ${i + 1}: This is some content`) return ( <box flexDirection="column" height={dimensions().height} width={dimensions().width} backgroundColor="#1a1a2e"> <text>Press 'd' to toggle dialog, 'q' to quit, 'esc' to close</text> <text>Last click: {lastClick()}</text> <Show when={showDialog()}> {/* Overlay - clicking here should close the dialog */} <box position="absolute" top={0} left={0} width={dimensions().width} height={dimensions().height} backgroundColor="#ff000033" onMouseDown={() => { setLastClick("OVERLAY (red)") setShowDialog(false) }} > <box position="absolute" top={Math.floor(dimensions().height / 4)} left={Math.floor(dimensions().width / 4)} width={Math.floor(dimensions().width / 2)} height={Math.floor(dimensions().height / 2)} backgroundColor="#0000ff" borderStyle="rounded" flexDirection="column" onMouseDown={(e: { stopPropagation: () => void }) => { console.log(">>> DIALOG BOX clicked") setLastClick("DIALOG BOX (blue)") e.stopPropagation() }} > <scrollbox flexGrow={1} backgroundColor="#ffff00" onMouseDown={(e: { stopPropagation: () => void }) => { setLastClick("SCROLLBOX (yellow)") e.stopPropagation() }} > <For each={lines}>{(line) => <text>{line}</text>}</For> </scrollbox> </box> </box> </Show> </box> ) } render(() => <App />) ``` - Press `d` to open the dialog - Click in the red area directly below the scrollable content in the dialog box - "Last click" shows "SCROLLBOX (yellow)" instead of "OVERLAY (red)" and closing the dialog ## Environment - @opentui/core: 0.1.68 - @opentui/solid: 0.1.68 - bun: 1.3.5 - OS: Linux x64 - Terminal: Tested in Ghostty and the Zen integrated terminal
kerem 2026-03-02 23:44:42 +03:00
  • closed this issue
  • added the
    core
    bug
    labels
Author
Owner

@simonklee commented on GitHub (Jan 8, 2026):

4b241ad280 should fix the issue. It was one of my test cases.

<!-- gh-comment-id:3725499302 --> @simonklee commented on GitHub (Jan 8, 2026): 4b241ad280dccb120c78efdcc421bde7d0550e1a should fix the issue. It was one of my test cases.
Author
Owner

@nyxkrage commented on GitHub (Jan 8, 2026):

Yep! Thanks a lot!

<!-- gh-comment-id:3726315198 --> @nyxkrage commented on GitHub (Jan 8, 2026): Yep! Thanks a lot!
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/opentui#122
No description provided.