From d128b0ad4f224b12f7549ff55aab5749309ad299 Mon Sep 17 00:00:00 2001 From: Weongyu Jeon Date: Thu, 3 Sep 2026 19:45:31 +0000 Subject: [PATCH] Refactor scanner state handling --- packages/tui/src/ui/spinner.ts | 134 +++++++++++++++------------ packages/tui/test/ui/spinner.test.ts | 20 ++++ 2 files changed, 94 insertions(+), 60 deletions(-) create mode 100644 packages/tui/test/ui/spinner.test.ts diff --git a/packages/tui/src/ui/spinner.ts b/packages/tui/src/ui/spinner.ts index c185ea7b..16c91bbb 100644 --- a/packages/tui/src/ui/spinner.ts +++ b/packages/tui/src/ui/spinner.ts @@ -22,85 +22,99 @@ interface ScannerState { isMovingForward: boolean } -function getScannerState( +function getBidirectionalScannerState( frameIndex: number, totalChars: number, - options: Pick, + holdFrames: NonNullable, ): ScannerState { - const { direction = "forward", holdFrames = {} } = options + const forwardFrames = totalChars + const holdEndFrames = holdFrames.end ?? 0 + const backwardFrames = totalChars - 1 - if (direction === "bidirectional") { - const forwardFrames = totalChars - const holdEndFrames = holdFrames.end ?? 0 - const backwardFrames = totalChars - 1 - - if (frameIndex < forwardFrames) { - // Moving forward - return { - activePosition: frameIndex, - isHolding: false, - holdProgress: 0, - holdTotal: 0, - movementProgress: frameIndex, - movementTotal: forwardFrames, - isMovingForward: true, - } - } else if (frameIndex < forwardFrames + holdEndFrames) { - // Holding at end - return { - activePosition: totalChars - 1, - isHolding: true, - holdProgress: frameIndex - forwardFrames, - holdTotal: holdEndFrames, - movementProgress: 0, - movementTotal: 0, - isMovingForward: true, - } - } else if (frameIndex < forwardFrames + holdEndFrames + backwardFrames) { - // Moving backward - const backwardIndex = frameIndex - forwardFrames - holdEndFrames - return { - activePosition: totalChars - 2 - backwardIndex, - isHolding: false, - holdProgress: 0, - holdTotal: 0, - movementProgress: backwardIndex, - movementTotal: backwardFrames, - isMovingForward: false, - } - } else { - // Holding at start - return { - activePosition: 0, - isHolding: true, - holdProgress: frameIndex - forwardFrames - holdEndFrames - backwardFrames, - holdTotal: holdFrames.start ?? 0, - movementProgress: 0, - movementTotal: 0, - isMovingForward: false, - } + if (frameIndex < forwardFrames) { + // Moving forward + return { + activePosition: frameIndex, + isHolding: false, + holdProgress: 0, + holdTotal: 0, + movementProgress: frameIndex, + movementTotal: forwardFrames, + isMovingForward: true, } - } else if (direction === "backward") { + } + + if (frameIndex < forwardFrames + holdEndFrames) { + // Holding at end return { - activePosition: totalChars - 1 - (frameIndex % totalChars), + activePosition: totalChars - 1, + isHolding: true, + holdProgress: frameIndex - forwardFrames, + holdTotal: holdEndFrames, + movementProgress: 0, + movementTotal: 0, + isMovingForward: true, + } + } + + if (frameIndex < forwardFrames + holdEndFrames + backwardFrames) { + // Moving backward + const backwardIndex = frameIndex - forwardFrames - holdEndFrames + return { + activePosition: totalChars - 2 - backwardIndex, isHolding: false, holdProgress: 0, holdTotal: 0, - movementProgress: frameIndex % totalChars, - movementTotal: totalChars, + movementProgress: backwardIndex, + movementTotal: backwardFrames, isMovingForward: false, } - } else { + } + + // Holding at start + return { + activePosition: 0, + isHolding: true, + holdProgress: frameIndex - forwardFrames - holdEndFrames - backwardFrames, + holdTotal: holdFrames.start ?? 0, + movementProgress: 0, + movementTotal: 0, + isMovingForward: false, + } +} + +function getScannerState( + frameIndex: number, + totalChars: number, + options: Pick, +): ScannerState { + const { direction = "forward", holdFrames = {} } = options + + if (direction === "bidirectional") { + return getBidirectionalScannerState(frameIndex, totalChars, holdFrames) + } + + if (direction === "backward") { return { - activePosition: frameIndex % totalChars, + activePosition: totalChars - 1 - (frameIndex % totalChars), isHolding: false, holdProgress: 0, holdTotal: 0, movementProgress: frameIndex % totalChars, movementTotal: totalChars, - isMovingForward: true, + isMovingForward: false, } } + + return { + activePosition: frameIndex % totalChars, + isHolding: false, + holdProgress: 0, + holdTotal: 0, + movementProgress: frameIndex % totalChars, + movementTotal: totalChars, + isMovingForward: true, + } } function calculateColorIndex( diff --git a/packages/tui/test/ui/spinner.test.ts b/packages/tui/test/ui/spinner.test.ts new file mode 100644 index 00000000..c5dca52d --- /dev/null +++ b/packages/tui/test/ui/spinner.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, test } from "bun:test" +import { createFrames } from "../../src/ui/spinner" + +describe("ui.spinner", () => { + test("creates the expected bidirectional scanner frames", () => { + expect(createFrames({ width: 4, holdStart: 2, holdEnd: 2 })).toEqual([ + "⬥···", + "◆⬥··", + "⬩◆⬥·", + "⬪⬩◆⬥", + "⬪⬩◆⬥", + "⬪⬪⬩◆", + "··⬥◆", + "·⬥◆⬩", + "⬥◆⬩⬪", + "⬥◆⬩⬪", + "◆⬩⬪⬪", + ]) + }) +})