From 63f6a497cba61299a1c24aa96795a55479740ac6 Mon Sep 17 00:00:00 2001 From: Jacob Richman Date: Sun, 22 Jun 2025 00:54:10 +0000 Subject: Jacob314/overflow notification and one MaxSizedBox bug fix (#1288) --- .../cli/src/ui/components/shared/MaxSizedBox.tsx | 101 +++++++++++++-------- 1 file changed, 65 insertions(+), 36 deletions(-) (limited to 'packages/cli/src/ui/components/shared/MaxSizedBox.tsx') diff --git a/packages/cli/src/ui/components/shared/MaxSizedBox.tsx b/packages/cli/src/ui/components/shared/MaxSizedBox.tsx index 1b5b90aa..faa1052a 100644 --- a/packages/cli/src/ui/components/shared/MaxSizedBox.tsx +++ b/packages/cli/src/ui/components/shared/MaxSizedBox.tsx @@ -4,14 +4,22 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { Fragment } from 'react'; +import React, { Fragment, useEffect, useId } from 'react'; import { Box, Text } from 'ink'; import stringWidth from 'string-width'; import { Colors } from '../../colors.js'; import { toCodePoints } from '../../utils/textUtils.js'; +import { useOverflowActions } from '../../contexts/OverflowContext.js'; let enableDebugLog = false; +/** + * Minimum height for the MaxSizedBox component. + * This ensures there is room for at least one line of content as well as the + * message that content was truncated. + */ +export const MINIMUM_MAX_HEIGHT = 2; + export function setMaxSizedBoxDebugging(value: boolean) { enableDebugLog = value; } @@ -95,6 +103,10 @@ export const MaxSizedBox: React.FC = ({ overflowDirection = 'top', additionalHiddenLinesCount = 0, }) => { + const id = useId(); + const { addOverflowingId, removeOverflowingId } = useOverflowActions() || {}; + + const laidOutStyledText: StyledText[][] = []; // When maxHeight is not set, we render the content normally rather // than using our custom layout logic. This should slightly improve // performance for the case where there is no height limit and is @@ -103,54 +115,71 @@ export const MaxSizedBox: React.FC = ({ // In the future we might choose to still apply our layout logic // even in this case particularlly if there are cases where we // intentionally diverse how certain layouts are rendered. - if (maxHeight === undefined) { - return ( - - {children} - - ); - } - - if (maxWidth === undefined) { - throw new Error('maxWidth must be defined when maxHeight is set.'); - } + let targetMaxHeight; + if (maxHeight !== undefined) { + targetMaxHeight = Math.max(Math.round(maxHeight), MINIMUM_MAX_HEIGHT); - const laidOutStyledText: StyledText[][] = []; - function visitRows(element: React.ReactNode) { - if (!React.isValidElement(element)) { - return; + if (maxWidth === undefined) { + throw new Error('maxWidth must be defined when maxHeight is set.'); } - if (element.type === Fragment) { - React.Children.forEach(element.props.children, visitRows); - return; - } - if (element.type === Box) { - layoutInkElementAsStyledText(element, maxWidth!, laidOutStyledText); - return; + function visitRows(element: React.ReactNode) { + if (!React.isValidElement(element)) { + return; + } + if (element.type === Fragment) { + React.Children.forEach(element.props.children, visitRows); + return; + } + if (element.type === Box) { + layoutInkElementAsStyledText(element, maxWidth!, laidOutStyledText); + return; + } + + debugReportError('MaxSizedBox children must be elements', element); } - debugReportError('MaxSizedBox children must be elements', element); + React.Children.forEach(children, visitRows); } - React.Children.forEach(children, visitRows); - const contentWillOverflow = - (laidOutStyledText.length > maxHeight && maxHeight > 0) || + (targetMaxHeight !== undefined && + laidOutStyledText.length > targetMaxHeight) || additionalHiddenLinesCount > 0; - const visibleContentHeight = contentWillOverflow ? maxHeight - 1 : maxHeight; - - const hiddenLinesCount = Math.max( - 0, - laidOutStyledText.length - visibleContentHeight, - ); + const visibleContentHeight = + contentWillOverflow && targetMaxHeight !== undefined + ? targetMaxHeight - 1 + : targetMaxHeight; + + const hiddenLinesCount = + visibleContentHeight !== undefined + ? Math.max(0, laidOutStyledText.length - visibleContentHeight) + : 0; const totalHiddenLines = hiddenLinesCount + additionalHiddenLinesCount; + useEffect(() => { + if (totalHiddenLines > 0) { + addOverflowingId?.(id); + } else { + removeOverflowingId?.(id); + } + + return () => { + removeOverflowingId?.(id); + }; + }, [id, totalHiddenLines, addOverflowingId, removeOverflowingId]); + + if (maxHeight === undefined) { + return ( + + {children} + + ); + } + const visibleStyledText = hiddenLinesCount > 0 ? overflowDirection === 'top' - ? laidOutStyledText.slice( - laidOutStyledText.length - visibleContentHeight, - ) + ? laidOutStyledText.slice(hiddenLinesCount, laidOutStyledText.length) : laidOutStyledText.slice(0, visibleContentHeight) : laidOutStyledText; -- cgit v1.2.3