Files
oh-my-pi/packages/tui/src/components/loader.ts
T
can1357 0a49a4a9a7 fix(tui): corrected loader text overflow and terminal clear behavior
- Fixed loader component text overflow by truncating lines exceeding terminal width using column-aware slicing.
- Improved terminal clear logic on resize to use hard clear (\x1b[2J) preventing stale wrapped cells.
- Added test coverage for loader component line clamping to verify rendered lines fit within terminal width.
- Refactored terminal clear conditional logic from ternary to multi-line if-else for improved maintainability.
2026-03-01 02:35:43 +01:00

68 lines
1.6 KiB
TypeScript

import type { TUI } from "../tui";
import { sliceByColumn, visibleWidth } from "../utils";
import { Text } from "./text";
/**
* Loader component that updates every 80ms with spinning animation
*/
export class Loader extends Text {
#frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
#currentFrame = 0;
#intervalId?: NodeJS.Timeout;
#ui: TUI | null = null;
constructor(
ui: TUI,
private spinnerColorFn: (str: string) => string,
private messageColorFn: (str: string) => string,
private message: string = "Loading...",
spinnerFrames?: string[],
) {
super("", 1, 0);
this.#ui = ui;
if (spinnerFrames && spinnerFrames.length > 0) {
this.#frames = spinnerFrames;
}
this.start();
}
render(width: number): string[] {
const lines = ["", ...super.render(width)];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (visibleWidth(line) > width) {
lines[i] = sliceByColumn(line, 0, width, true);
}
}
return lines;
}
start() {
this.#updateDisplay();
this.#intervalId = setInterval(() => {
this.#currentFrame = (this.#currentFrame + 1) % this.#frames.length;
this.#updateDisplay();
}, 80);
}
stop() {
if (this.#intervalId) {
clearInterval(this.#intervalId);
this.#intervalId = undefined;
}
}
setMessage(message: string) {
this.message = message;
this.#updateDisplay();
}
#updateDisplay() {
const frame = this.#frames[this.#currentFrame];
this.setText(`${this.spinnerColorFn(frame)} ${this.messageColorFn(this.message)}`);
if (this.#ui) {
this.#ui.requestRender();
}
}
}