import React, { ReactNode } from 'react'; import { BoxProps } from 'ink'; /** * Props for the ScrollView component. * * @remarks * Extends standard BoxProps from Ink. */ interface ScrollViewProps extends BoxProps { /** * Callback fired when the scroll position changes. * * @param scrollOffset - The new scroll offset (distance from top). * * @remarks * Use this to sync external state or UI (e.g., scrollbars) with the current scroll position. */ onScroll?: (scrollOffset: number) => void; /** * Callback fired when the ScrollView viewport (visible area) dimensions change. * * @param size - The new dimensions of the viewport (width, height). * @param previousSize - The previous dimensions of the viewport (width, height). * * @remarks * Fired whenever the outer container size changes (e.g., terminal resize or layout update). */ onViewportSizeChange?: (size: { width: number; height: number; }, previousSize: { width: number; height: number; }) => void; /** * Callback fired when the total height of the content changes. * * @param height - The new total content height. * @param previousHeight - The previous total content height. * * @remarks * Useful for debug logging or adjusting external layouts based on content size. */ onContentHeightChange?: (height: number, previousHeight: number) => void; /** * Callback fired when an individual child item's height changes. * * @param index - The index of the item. * @param height - The new height of the item. * @param previousHeight - The previous height of the item. * * @remarks * This is triggered whenever an item is re-measured and its height differs from the previous value. */ onItemHeightChange?: (index: number, height: number, previousHeight: number) => void; /** * Enable debug mode to visualize the ScrollView internals. * * @remarks * When enabled, the viewport overflow is not hidden, allowing the full content * to be visible. This is useful for inspecting the layout and verifying * that content is being rendered correctly off-screen. */ debug?: boolean; /** * The content to be scrolled. * * @remarks * Accepts an array of React elements. Each element should have a unique `key` * prop, which will be preserved during rendering for proper reconciliation. */ children?: ReactNode; } /** * Ref interface for controlling the ScrollView programmatically. */ interface ScrollViewRef { /** * Scrolls to a specific vertical position. * * @param offset - The target Y offset (distance from top). * * @example * ```tsx * // Scroll to the very top * ref.current?.scrollTo(0); * ``` */ scrollTo: (offset: number) => void; /** * Scrolls by a relative amount. * * @param delta - Positive for down, negative for up. * * @example * ```tsx * useInput((input, key) => { * if (key.downArrow) ref.current?.scrollBy(1); * if (key.upArrow) ref.current?.scrollBy(-1); * }); * ``` */ scrollBy: (delta: number) => void; /** * Scrolls to the very top (position 0). */ scrollToTop: () => void; /** * Scrolls to the very bottom. * * @remarks * This calculates the target offset as `contentHeight - viewportHeight`. */ scrollToBottom: () => void; /** * Gets the current scroll offset (distance scrolled from the top). * * @remarks * The scroll offset represents how many terminal rows the content has been * scrolled up from its initial position. A value of 0 means the content is * at the very top (no scrolling has occurred). * * @returns The current scroll offset in terminal rows. */ getScrollOffset: () => number; /** * Gets the total height of the content. * * @remarks * This is the sum of the heights of all child items. * * @returns The total content height in terminal rows. */ getContentHeight: () => number; /** * Gets the current height of the visible viewport. * * @returns The viewport height in terminal rows. */ getViewportHeight: () => number; /** * Gets the scroll offset when the content is scrolled to the very bottom. * * @remarks * This is calculated as `contentHeight - viewportHeight`. When the scroll * offset equals this value, the last item of the content is visible at the * bottom of the viewport. * * @returns The bottom scroll offset in terminal rows. */ getBottomOffset: () => number; /** * Gets the height of a specific item by its index. * * @param index - The index of the item. * @returns The height of the item in terminal rows, or 0 if not found. */ getItemHeight: (index: number) => number; /** * Gets the position of a specific item. * * @param index - The index of the item. * @returns The position (top offset) and height of the item, or null if not found. */ getItemPosition: (index: number) => { top: number; height: number; } | null; /** * Re-measures the ScrollView viewport dimensions. * * @remarks * Checks the current dimensions of the viewport and updates state if they have changed. * This is crucial for handling terminal resizes, as Ink does not automatically propagate resize events to components. * * @example * ```tsx * // Handle terminal resize manually * useEffect(() => { * const onResize = () => ref.current?.remeasure(); * process.stdout.on('resize', onResize); * return () => process.stdout.off('resize', onResize); * }, []); * ``` */ remeasure: () => void; /** * Triggers re-measurement of a specific child item. * * @param index - The index of the child to re-measure. * * @remarks * Use this if a child's internal content changes size in a way that doesn't trigger a standard React render cycle update * (e.g., internal state change within the child that affects its height). */ remeasureItem: (index: number) => void; } /** * A ScrollView component for Ink applications. * * @remarks * Allows scrolling through content that exceeds the visible area of the terminal. * It manages a virtual viewport and renders all children, but strictly controls * their visibility using `overflow="hidden"` and `marginTop` offsets. * * **Features:** * - ↕️ Vertical scrolling * - 📏 Auto-measurement of child heights * - 🎯 Imperative scrolling methods via ref * - 🔁 Dynamic content support (adding/removing children) * - 🖥️ Viewport resize handling (via manual `remeasure`) * * **Important Notes:** * - This component does NOT automatically capture keyboard input. You must use `useInput` in a parent component and control the scroll via the `onInput` hook or similar. * - Children MUST generally have specific keys if you plan to dynamically update them, to ensure correct height tracking across renders. * * * @example * ```tsx * import React, { useRef } from 'react'; * import { Box, Text, useInput } from 'ink'; * import { ScrollView, ScrollViewRef } from 'ink-scroll-view'; * * const Demo = () => { * const scrollRef = useRef(null); * * useInput((input, key) => { * if (key.downArrow) { * scrollRef.current?.scrollBy(1); * } * if (key.upArrow) { * scrollRef.current?.scrollBy(-1); * } * }); * * return ( * * * {items.map(item => ( * {item.label} * ))} * * * ); * }; * ``` */ declare const ScrollView: React.ForwardRefExoticComponent>; interface ControlledScrollViewProps extends BoxProps { /** * The current scroll offset (distance from top). * Controlled by the parent. */ scrollOffset: number; /** * Callback fired when the ScrollView viewport (visible area) dimensions change. */ onViewportSizeChange?: (size: { width: number; height: number; }, previousSize: { width: number; height: number; }) => void; /** * Callback fired when the total height of the content changes. */ onContentHeightChange?: (height: number, previousHeight: number) => void; /** * Callback fired when an individual child item's height changes. */ onItemHeightChange?: (index: number, height: number, previousHeight: number) => void; /** * Enable debug mode to visualize the ScrollView internals. */ debug?: boolean; children?: ReactNode; } interface ControlledScrollViewRef { /** * Gets the total height of the content. * * @returns The sum of heights of all child items. */ getContentHeight: () => number; /** * Gets the current height of the visible viewport. * * @returns The height of the viewport container. */ getViewportHeight: () => number; /** * Gets the maximum scroll offset (content height - viewport height). * * @remarks * This represents the scroll offset required to view the very bottom of the content. * It is clamped to 0 if the content fits entirely within the viewport. */ getBottomOffset: () => number; /** * Gets the height of a specific item by its index. * * @param index - The index of the child item. * @returns The measured height of the item. */ getItemHeight: (index: number) => number; /** * Gets the absolute position and dimensions of a specific item. * * @param index - The index of the child item. * @returns Object containing `top` (offset from content start) and `height`, or `null` if the index is invalid. * * @remarks * This method uses a cached offset calculation system (`itemOffsetsRef`) for performance. * It calculates offsets lazily and caches them until the underlying measurements change. */ getItemPosition: (index: number) => { top: number; height: number; } | null; /** * Re-measures the ScrollView viewport dimensions. * * @remarks * Explicitly triggers a measurement of the viewport Box. This is necessary because * Ink does not automatically detect terminal window resizes or parent layout changes * that might affect the viewport size. */ remeasure: () => void; /** * Triggers re-measurement of a specific child item. * * @param index - The index of the child to re-measure. * * @remarks * Forces the `MeasurableItem` wrapper for the specified index to re-run `measureElement`. * Use this when a child's content changes internally (e.g., expanding text) without changing props. */ remeasureItem: (index: number) => void; } /** * A ControlledScrollView component for Ink applications. * * @remarks * This is a lower-level component that handles the complex logic of: * 1. Rendering children within a virtual viewport. * 2. Continuously measuring child heights. * 3. Calculating total content height. * 4. Managing viewport wrapping adjustments (`marginTop`). * * It is "controlled" because it does not maintain its own scroll state; it purely renders * based on the provided `scrollOffset` prop. This allows for flexible parent-controlled behavior. */ declare const ControlledScrollView: React.ForwardRefExoticComponent>; export { ControlledScrollView, type ControlledScrollViewProps, type ControlledScrollViewRef, ScrollView, type ScrollViewProps, type ScrollViewRef };