import type { Component } from "@earendil-works/pi-tui"; import { visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui"; import { renderInlineInputRow } from "./inline-input.js"; /** * Row-intent discriminated union. `kind` is the single discriminator — * pre-1.0.3 boolean flags have been removed (see `banned-flags.test.ts`). * Modeled after `QuestionnaireAction` (`key-router.ts:13-32`) and `Effect` * (`state-reducer.ts:26-32`) — pure literal-tagged variants, no shared base, * exhaustive-`switch` enforcement via non-`void` returns. * * Variant semantics: * - `option`: a regular author-defined option row. * - `other`: the inline free-text input row appended to every question * (label is "Type something."). Renders the headless multiline editor when active. * - `next`: the explicit commit-and-advance row appended to multi-select questions * (label is "Next"). Renders without a number / checkbox. */ export type WrappingSelectItem = | { kind: "option"; label: string; description?: string } | { kind: "other"; label: string; description?: string } | { kind: "next"; label: string; description?: string }; export interface WrappingSelectTheme { selectedText: (text: string) => string; description: (text: string) => string; scrollInfo: (text: string) => string; } /** * Numbering controls. * * Use `numberStartOffset` + `totalItemsForNumbering` when a list is logically a slice of a * larger numbered sequence — e.g. to start numbering at an offset and pad the column as * if the list were part of a longer continuous numbered sequence. */ export interface WrappingSelectOptions { /** Start numbering at this offset + 1 (default 0 → rows labeled 1, 2, 3 …). */ numberStartOffset?: number; /** Override the total used to pad the number column (useful when items span multiple lists). */ totalItemsForNumbering?: number; } export class WrappingSelect implements Component { private static readonly ACTIVE_POINTER = "❯ "; private static readonly INACTIVE_POINTER = " "; private static readonly NUMBER_SEPARATOR = ". "; private static readonly CONFIRMED_MARK = " ✔"; private static readonly MIN_CONTENT_WIDTH = 1; private readonly items: readonly WrappingSelectItem[]; private readonly maxVisible: number; private readonly theme: WrappingSelectTheme; private numberStartOffset: number; private totalItemsForNumbering: number; private selectedIndex = 0; private focused = true; private inputBuffer = ""; private inputCursorOffset: number | undefined = undefined; /** * Index of the row that was previously confirmed for this list (e.g. the user's prior * answer when re-entering a multi-question tab). Renders `