import type { QuestionData } from "../../../tool/types.js"; import type { WrappingSelectItem } from "../wrapping-select.ts"; /** Min terminal/pane width for the side-by-side layout to engage. */ export declare const PREVIEW_MIN_WIDTH = 100; /** Visual gap between options column and preview column in side-by-side. */ export declare const PREVIEW_COLUMN_GAP = 2; /** 1 col padding inside the preview column (between gap and `│`). */ export declare const PREVIEW_PADDING_LEFT = 1; /** Empty rows between options and preview blocks in stacked (narrow) layout. */ export declare const STACKED_GAP_ROWS = 1; /** Floor for the adaptive left column width — prevents collapse on short labels. */ export declare const MIN_LEFT = 30; /** Ceiling ratio: left column never exceeds this fraction of pane width. */ export declare const MAX_LEFT_RATIO = 0.5; /** Floor for the preview column width — prevents right-side collapse on narrow terminals. */ export declare const MIN_PREVIEW_WIDTH = 45; /** visibleWidth(" ✔") = 2 (space + ✔ codepoint). Reserved on the longest-label measurement * so a confirmed row never gets truncated when MIN_LEFT clamps the column. */ export declare const CONFIRMED_OVERHEAD = 2; export type PreviewLayoutMode = "side-by-side" | "stacked"; /** * Decide layout mode from terminal + pane widths. Pure of inputs. * * The terminal-width gate is the AND check from the previous `preview-pane.ts` — * lifted here so the decision is computed ONCE per render and threaded explicitly * through `previewBlockHeight`. Removes the bug class where `previewBlockHeight` * re-derived `sideBySide` from a column width (already < pane width post-split), * capping height too short. */ export declare function decideLayout(terminalWidth: number, paneWidth: number): PreviewLayoutMode; /** * Compute the adaptive left column width from option labels. * Pure function — deterministic for a given (items, totalForNumbering, paneWidth). * * Pipeline: * 1. Measure: longest visible label width + prefix overhead + confirmed-mark overhead * 2. Clamp: floor MIN_LEFT, ceiling paneWidth * MAX_LEFT_RATIO * 3. Safety net: never exceed available = paneWidth - GAP - MIN_PREVIEW_WIDTH */ export declare function adaptiveLeftWidth(items: readonly WrappingSelectItem[], totalForNumbering: number, paneWidth: number): number; /** * Cross-tab maximum left-column width. Aggregates `adaptiveLeftWidth` over every tab * and returns the widest result, so the options column stays stable on tab switch. * * Pure function — `tabs.length` MUST equal `itemsByTab.length`. Multi-select tabs * use `items.length` for numbering; single-select tabs add 1 for the chat row slot. * Floor is `MIN_LEFT` so an all-empty input still produces a usable column. */ export declare function crossTabMaxLeftWidth(tabs: ReadonlyArray<{ multiSelect?: boolean; }>, itemsByTab: ReadonlyArray, paneWidth: number): number; /** * Source-line probe: measures the widest source line across all options' previews. * Returns 0 when no option carries a preview. * * V1 heuristic — works well for tree/table/heading/list content where source-line * width ≈ rendered width. Paragraphs overstate (source width "wants" the full pane); * the worst case is "donation does nothing useful" — fallback to the label-driven path. * Upgrade path: replace with a render-width probe that invokes Markdown at a probe width. * * Pure function — O(total chars) per question; no Markdown invocation, no cache interaction. */ export declare function previewSourceWidth(question: QuestionData): number; /** * Cross-tab/cross-option preview budget. Iterates all questions, computes each question's * preview appetite via `previewSourceWidth`, adds box + padding overhead (5 cols), and * returns the widest result. Floor is `MIN_PREVIEW_WIDTH` so a previewless question still * reserves a usable column. Ceiling ensures the left column retains its `MIN_LEFT` floor. * * Pure function — same cross-tab max pattern as `crossTabMaxLeftWidth`. */ export declare function crossTabPreviewBudget(questions: readonly QuestionData[], paneWidth: number): number; /** * Cross-tab left-column width with slack donation. Combines the label-driven width * (from `crossTabMaxLeftWidth`) with the slack donated by narrow previews. * * Pipeline: * 1. `labelDriven` = `crossTabMaxLeftWidth(tabs, itemsByTab, paneWidth)` * 2. `previewBudget` = `crossTabPreviewBudget(questions, paneWidth)` * 3. `slackDonation` = `paneWidth − GAP − previewBudget` * 4. Return `min(max(labelDriven, slackDonation), ceiling)` where `ceiling = paneWidth − GAP − MIN_PREVIEW_WIDTH` * * Invariants: * - Floor: result ≥ MIN_LEFT (labelDriven ≥ MIN_LEFT) * - Preview floor: right column ≥ MIN_PREVIEW_WIDTH (ceiling enforces this) * - Cross-tab stability: both reductions are tab-independent * - Determinism: pure of (questions, itemsByTab, paneWidth) * * `crossTabMaxLeftWidth` is NOT replaced — it continues to exist as the primitive. * `MAX_LEFT_RATIO` caps the label-driven path inside `adaptiveLeftWidth`; donation * operates above the cap when preview slack is available. */ export declare function crossTabLeftWidthWithDonation(tabs: ReadonlyArray<{ multiSelect?: boolean; }>, itemsByTab: ReadonlyArray, questions: readonly QuestionData[], paneWidth: number): number; /** * Width allocation for side-by-side mode. * `adaptiveLeft` is the pre-computed left column width (from `adaptiveLeftWidth`, * cross-tab aggregated). The Math.max(1, ...) calls keep both columns >= 1 col on * extreme inputs. */ export declare function columnWidths(paneWidth: number, adaptiveLeft: number): { leftWidth: number; rightWidth: number; gap: number; }; /** * Returns the widths actually passed to `options.render` and `previewLines` inside * `render()`. Stacked uses the full pane width for both; side-by-side splits via * `columnWidths`, with the preview column offset by `PREVIEW_PADDING_LEFT`. */ export declare function bodyWidths(paneWidth: number, mode: PreviewLayoutMode, adaptiveLeft: number): { optionsWidth: number; previewWidth: number; }; //# sourceMappingURL=preview-layout-decider.d.ts.map