update to height based on all available height module fix overflow

This commit is contained in:
A.K.M. Adib
2026-03-05 15:40:12 -05:00
parent 0f88ddb73e
commit ec2d0a89d2
4 changed files with 49 additions and 16 deletions

View File

@@ -12,6 +12,7 @@ import {
useEffect,
useReducer,
useContext,
useState,
} from 'react';
import { Box, Text } from 'ink';
import { theme } from '../semantic-colors.js';
@@ -782,28 +783,28 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
}
}, [customOptionText, isCustomOptionSelected, question.multiSelect]);
const [actualQuestionHeight, setActualQuestionHeight] = useState(0);
const HEADER_HEIGHT = progressHeader ? 2 : 0;
const TITLE_MARGIN = 1;
const FOOTER_HEIGHT = 2; // DialogFooter + margin
const overhead = HEADER_HEIGHT + TITLE_MARGIN + FOOTER_HEIGHT;
const listHeight = availableHeight
? Math.max(1, availableHeight - overhead)
: undefined;
const maxQuestionHeight =
question.unconstrainedHeight && listHeight
? // When unconstrained, give the question a majority of the vertical space (e.g., 70%).
// The options list will take the remaining space and scroll if necessary.
// This is more robust than calculating based on `selectionItems.length`,
// which can incorrectly shrink the question if there are many options.
Math.max(5, Math.floor(listHeight * 0.7))
: 15;
const questionHeight =
// Modulo the fixed overflow (overhead + 4 lines reserved for list), use all remaining height.
const maxQuestionHeight = listHeight ? Math.max(5, listHeight - 4) : 15;
const questionHeightLimit =
listHeight && !isAlternateBuffer
? Math.min(maxQuestionHeight, Math.max(1, listHeight - DIALOG_PADDING))
? Math.min(maxQuestionHeight, listHeight)
: undefined;
const maxItemsToShow =
listHeight && questionHeight
? Math.max(1, Math.floor((listHeight - questionHeight) / 2))
listHeight && actualQuestionHeight
? Math.max(1, Math.floor((listHeight - actualQuestionHeight) / 2))
: selectionItems.length;
return (
@@ -811,9 +812,10 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
{progressHeader}
<Box marginBottom={TITLE_MARGIN}>
<MaxSizedBox
maxHeight={questionHeight}
maxHeight={questionHeightLimit}
maxWidth={availableWidth}
overflowDirection="bottom"
onHeightChange={setActualQuestionHeight}
>
<Box flexDirection="column">
<MarkdownDisplay

View File

@@ -282,9 +282,10 @@ describe('ToolConfirmationQueue', () => {
// hideToolIdentity is true for ask_user -> subtracts 4 instead of 6
// availableContentHeight = 19 - 4 = 15
// ToolConfirmationMessage handlesOwnUI=true -> returns full 15
// AskUserDialog allocates questionHeight = Math.min(maxQuestionHeight, Math.max(5, listHeight - DIALOG_PADDING)).
// maxQuestionHeight = floor(15 * 0.7) = 10.
// 10 lines is enough for the 6-line question + padding.
// AskUserDialog allocates questionHeight = availableHeight - overhead - minListHeight.
// listHeight = 15 - overhead (Header:0, Margin:1, Footer:2) = 12.
// maxQuestionHeight = listHeight - 4 = 8.
// 8 lines is enough for the 6-line question.
await waitFor(() => {
expect(lastFrame()).toContain('Line 6');
expect(lastFrame()).not.toContain('lines hidden');

View File

@@ -36,6 +36,22 @@ Enter to select · ↑/↓ to navigate · Esc to cancel
"
`;
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: false) > shows scroll arrows correctly when useAlternateBuffer is false 2`] = `
"Choose an option
● 1. Option 1
Description 1
2. Option 2
Description 2
3. Option 3
Description 3
Enter to select · ↑/↓ to navigate · Esc to cancel
"
`;
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: true) > shows scroll arrows correctly when useAlternateBuffer is true 1`] = `
"Choose an option

View File

@@ -26,6 +26,7 @@ interface MaxSizedBoxProps {
maxHeight?: number;
overflowDirection?: 'top' | 'bottom';
additionalHiddenLinesCount?: number;
onHeightChange?: (height: number) => void;
}
/**
@@ -38,6 +39,7 @@ export const MaxSizedBox: React.FC<MaxSizedBoxProps> = ({
maxHeight,
overflowDirection = 'top',
additionalHiddenLinesCount = 0,
onHeightChange,
}) => {
const id = useId();
const { addOverflowingId, removeOverflowingId } = useOverflowActions() || {};
@@ -80,6 +82,18 @@ export const MaxSizedBox: React.FC<MaxSizedBoxProps> = ({
? effectiveMaxHeight - 1
: effectiveMaxHeight;
const actualHeight =
visibleContentHeight !== undefined
? Math.min(contentHeight, visibleContentHeight)
: contentHeight;
const totalActualHeight =
actualHeight + (isOverflowing && effectiveMaxHeight !== undefined ? 1 : 0);
useEffect(() => {
onHeightChange?.(totalActualHeight);
}, [totalActualHeight, onHeightChange]);
const hiddenLinesCount =
visibleContentHeight !== undefined
? Math.max(0, contentHeight - visibleContentHeight)