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
@@ -12,6 +12,7 @@ import {
useEffect, useEffect,
useReducer, useReducer,
useContext, useContext,
useState,
} from 'react'; } from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { theme } from '../semantic-colors.js'; import { theme } from '../semantic-colors.js';
@@ -782,28 +783,28 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
} }
}, [customOptionText, isCustomOptionSelected, question.multiSelect]); }, [customOptionText, isCustomOptionSelected, question.multiSelect]);
const [actualQuestionHeight, setActualQuestionHeight] = useState(0);
const HEADER_HEIGHT = progressHeader ? 2 : 0; const HEADER_HEIGHT = progressHeader ? 2 : 0;
const TITLE_MARGIN = 1; const TITLE_MARGIN = 1;
const FOOTER_HEIGHT = 2; // DialogFooter + margin const FOOTER_HEIGHT = 2; // DialogFooter + margin
const overhead = HEADER_HEIGHT + TITLE_MARGIN + FOOTER_HEIGHT; const overhead = HEADER_HEIGHT + TITLE_MARGIN + FOOTER_HEIGHT;
const listHeight = availableHeight const listHeight = availableHeight
? Math.max(1, availableHeight - overhead) ? Math.max(1, availableHeight - overhead)
: undefined; : undefined;
const maxQuestionHeight =
question.unconstrainedHeight && listHeight // Modulo the fixed overflow (overhead + 4 lines reserved for list), use all remaining height.
? // When unconstrained, give the question a majority of the vertical space (e.g., 70%). const maxQuestionHeight = listHeight ? Math.max(5, listHeight - 4) : 15;
// The options list will take the remaining space and scroll if necessary.
// This is more robust than calculating based on `selectionItems.length`, const questionHeightLimit =
// which can incorrectly shrink the question if there are many options.
Math.max(5, Math.floor(listHeight * 0.7))
: 15;
const questionHeight =
listHeight && !isAlternateBuffer listHeight && !isAlternateBuffer
? Math.min(maxQuestionHeight, Math.max(1, listHeight - DIALOG_PADDING)) ? Math.min(maxQuestionHeight, listHeight)
: undefined; : undefined;
const maxItemsToShow = const maxItemsToShow =
listHeight && questionHeight listHeight && actualQuestionHeight
? Math.max(1, Math.floor((listHeight - questionHeight) / 2)) ? Math.max(1, Math.floor((listHeight - actualQuestionHeight) / 2))
: selectionItems.length; : selectionItems.length;
return ( return (
@@ -811,9 +812,10 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
{progressHeader} {progressHeader}
<Box marginBottom={TITLE_MARGIN}> <Box marginBottom={TITLE_MARGIN}>
<MaxSizedBox <MaxSizedBox
maxHeight={questionHeight} maxHeight={questionHeightLimit}
maxWidth={availableWidth} maxWidth={availableWidth}
overflowDirection="bottom" overflowDirection="bottom"
onHeightChange={setActualQuestionHeight}
> >
<Box flexDirection="column"> <Box flexDirection="column">
<MarkdownDisplay <MarkdownDisplay
@@ -282,9 +282,10 @@ describe('ToolConfirmationQueue', () => {
// hideToolIdentity is true for ask_user -> subtracts 4 instead of 6 // hideToolIdentity is true for ask_user -> subtracts 4 instead of 6
// availableContentHeight = 19 - 4 = 15 // availableContentHeight = 19 - 4 = 15
// ToolConfirmationMessage handlesOwnUI=true -> returns full 15 // ToolConfirmationMessage handlesOwnUI=true -> returns full 15
// AskUserDialog allocates questionHeight = Math.min(maxQuestionHeight, Math.max(5, listHeight - DIALOG_PADDING)). // AskUserDialog allocates questionHeight = availableHeight - overhead - minListHeight.
// maxQuestionHeight = floor(15 * 0.7) = 10. // listHeight = 15 - overhead (Header:0, Margin:1, Footer:2) = 12.
// 10 lines is enough for the 6-line question + padding. // maxQuestionHeight = listHeight - 4 = 8.
// 8 lines is enough for the 6-line question.
await waitFor(() => { await waitFor(() => {
expect(lastFrame()).toContain('Line 6'); expect(lastFrame()).toContain('Line 6');
expect(lastFrame()).not.toContain('lines hidden'); expect(lastFrame()).not.toContain('lines hidden');
@@ -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`] = ` exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: true) > shows scroll arrows correctly when useAlternateBuffer is true 1`] = `
"Choose an option "Choose an option
@@ -26,6 +26,7 @@ interface MaxSizedBoxProps {
maxHeight?: number; maxHeight?: number;
overflowDirection?: 'top' | 'bottom'; overflowDirection?: 'top' | 'bottom';
additionalHiddenLinesCount?: number; additionalHiddenLinesCount?: number;
onHeightChange?: (height: number) => void;
} }
/** /**
@@ -38,6 +39,7 @@ export const MaxSizedBox: React.FC<MaxSizedBoxProps> = ({
maxHeight, maxHeight,
overflowDirection = 'top', overflowDirection = 'top',
additionalHiddenLinesCount = 0, additionalHiddenLinesCount = 0,
onHeightChange,
}) => { }) => {
const id = useId(); const id = useId();
const { addOverflowingId, removeOverflowingId } = useOverflowActions() || {}; const { addOverflowingId, removeOverflowingId } = useOverflowActions() || {};
@@ -80,6 +82,18 @@ export const MaxSizedBox: React.FC<MaxSizedBoxProps> = ({
? effectiveMaxHeight - 1 ? effectiveMaxHeight - 1
: effectiveMaxHeight; : 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 = const hiddenLinesCount =
visibleContentHeight !== undefined visibleContentHeight !== undefined
? Math.max(0, contentHeight - visibleContentHeight) ? Math.max(0, contentHeight - visibleContentHeight)