fix(player): correct click-to-seek by accounting for pane offset

MouseEvent coordinates are terminal-absolute, so the bar's seek handler
must subtract its own absolute left edge (renderable ref) instead of
treating the mouse x as bar-local. Clicking was off by the width of the
parent/Up pane (~20%) plus chrome. Also drop the empty played-text node
at position 0, which rendered a phantom space column and shifted the
drawn bar one char when playback started.
This commit is contained in:
2026-08-10 17:53:46 -04:00
parent 116d095ad5
commit c52fa14e42
2 changed files with 197 additions and 4 deletions

View File

@@ -7,6 +7,7 @@
*/
import { useTerminalDimensions } from "@opentui/solid";
import type { Renderable } from "@opentui/core";
import { useAudio } from "@/hooks/useAudio";
import { useTheme } from "@/context/ThemeContext";
@@ -17,6 +18,11 @@ export function ProgressBar() {
const { theme } = useTheme();
const dimensions = useTerminalDimensions();
// The bar's renderable, captured for its absolute left edge: MouseEvent.x
// is terminal-absolute (not bar-relative), so local x needs the offset
// of the bar inside the 2-pane row (parent pane ≈ 20% of the width).
let bar: Renderable | undefined;
// Full content width of the player pane: the player is a 2-pane row
// (parent 1/5 + current 4/5 of the terminal width). Subtract ~8 chars
// of border/padding chrome (same math as RealtimeWaveform's numBars).
@@ -39,15 +45,21 @@ export function ProgressBar() {
padding={0}
flexDirection="row"
gap={0}
ref={(el) => {
bar = el;
}}
onMouseDown={(e: { x: number }) => {
const duration = audio.duration();
if (duration <= 0) return;
// e.x = 0 is the box border; content starts at x = 1.
const ratio = Math.max(0, Math.min(1, (e.x - 1) / width()));
if (duration <= 0 || !bar) return;
// localX = 0 is the box border; content starts at localX = 1.
const localX = e.x - bar.x;
const ratio = Math.max(0, Math.min(1, (localX - 1) / width()));
void audio.seek(ratio * duration);
}}
>
<text fg={theme.primary}>{"\u2588".repeat(playedChars())}</text>
{playedChars() > 0 && (
<text fg={theme.primary}>{"\u2588".repeat(playedChars())}</text>
)}
<text fg={remainingColor}>
{"\u2591".repeat(width() - playedChars())}
</text>