co-mono/packages/coding-agent/src/modes/interactive/components/hook-selector.ts
2025-12-21 22:56:20 +01:00

91 lines
2.4 KiB
TypeScript

/**
* Generic selector component for hooks.
* Displays a list of string options with keyboard navigation.
*/
import { Container, isArrowDown, isArrowUp, isEnter, isEscape, Spacer, Text } from "@mariozechner/pi-tui";
import { theme } from "../theme/theme.js";
import { DynamicBorder } from "./dynamic-border.js";
export class HookSelectorComponent extends Container {
private options: string[];
private selectedIndex = 0;
private listContainer: Container;
private onSelectCallback: (option: string) => void;
private onCancelCallback: () => void;
constructor(title: string, options: string[], onSelect: (option: string) => void, onCancel: () => void) {
super();
this.options = options;
this.onSelectCallback = onSelect;
this.onCancelCallback = onCancel;
// Add top border
this.addChild(new DynamicBorder());
this.addChild(new Spacer(1));
// Add title
this.addChild(new Text(theme.fg("accent", title), 1, 0));
this.addChild(new Spacer(1));
// Create list container
this.listContainer = new Container();
this.addChild(this.listContainer);
this.addChild(new Spacer(1));
// Add hint
this.addChild(new Text(theme.fg("dim", "↑↓ navigate enter select esc cancel"), 1, 0));
this.addChild(new Spacer(1));
// Add bottom border
this.addChild(new DynamicBorder());
// Initial render
this.updateList();
}
private updateList(): void {
this.listContainer.clear();
for (let i = 0; i < this.options.length; i++) {
const option = this.options[i];
const isSelected = i === this.selectedIndex;
let text = "";
if (isSelected) {
text = theme.fg("accent", "→ ") + theme.fg("accent", option);
} else {
text = ` ${theme.fg("text", option)}`;
}
this.listContainer.addChild(new Text(text, 1, 0));
}
}
handleInput(keyData: string): void {
// Up arrow or k
if (isArrowUp(keyData) || keyData === "k") {
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
this.updateList();
}
// Down arrow or j
else if (isArrowDown(keyData) || keyData === "j") {
this.selectedIndex = Math.min(this.options.length - 1, this.selectedIndex + 1);
this.updateList();
}
// Enter
else if (isEnter(keyData) || keyData === "\n") {
const selected = this.options[this.selectedIndex];
if (selected) {
this.onSelectCallback(selected);
}
}
// Escape
else if (isEscape(keyData)) {
this.onCancelCallback();
}
}
}