UNPKG

@cometchat/chat-uikit-react-native

Version:

Ready-to-use Chat UI Components for React Native

205 lines (191 loc) 6.77 kB
/** * Height Calculation Utility Functions for Auto-Expanding TextInput * * These pure functions handle height calculations for the auto-expanding * TextInput feature in CometChatSingleLineMessageComposer. * * @module heightUtils */ /** * Default constants for height calculations */ export const DEFAULT_MIN_HEIGHT = 24; export const DEFAULT_MAX_HEIGHT = 200; export const DEFAULT_LINE_HEIGHT = 24; export const DEFAULT_MAX_LINES = 5; export const DEFAULT_PADDING_VERTICAL = 12; /** * Pure function to calculate the target input height. * Clamps the content height between minHeight and maxHeight. * * @param contentHeight - The actual content height from onContentSizeChange * @param minHeight - Minimum allowed height (must be positive) * @param maxHeight - Maximum allowed height (must be >= minHeight) * @returns The clamped height value * * @example * // Content fits within bounds * calculateInputHeight(100, 40, 200) // returns 100 * * // Content exceeds max * calculateInputHeight(250, 40, 200) // returns 200 * * // Content below min * calculateInputHeight(20, 40, 200) // returns 40 * * **Validates: Requirements 1.1, 1.2, 1.3** */ export function calculateInputHeight( contentHeight: number, minHeight: number, maxHeight: number ): number { // Input validation: handle negative or zero values const validContentHeight = Math.max(0, contentHeight); const validMinHeight = Math.max(1, minHeight); // Ensure maxHeight is at least minHeight const validMaxHeight = Math.max(validMinHeight, maxHeight); // Clamp content height between min and max return Math.min(Math.max(validContentHeight, validMinHeight), validMaxHeight); } /** * Pure function to calculate max height from maxLines. * Computes the maximum height based on number of lines, line height, and padding. * * @param maxLines - Number of lines (must be positive, defaults to DEFAULT_MAX_LINES) * @param lineHeight - Height per line in pixels (must be positive) * @param paddingVertical - Vertical padding in pixels (must be non-negative) * @returns Maximum height in pixels * * @example * // 5 lines with 24px line height and 12px padding * calculateMaxHeightFromLines(5, 24, 12) // returns 144 (5 * 24 + 12 * 2) * * **Validates: Requirements 2.3** */ export function calculateMaxHeightFromLines( maxLines: number, lineHeight: number, paddingVertical: number ): number { // Input validation: handle negative or zero values const validMaxLines = Math.max(1, maxLines); const validLineHeight = Math.max(1, lineHeight); const validPaddingVertical = Math.max(0, paddingVertical); return validMaxLines * validLineHeight + validPaddingVertical * 2; } /** * Pure function to determine icon alignment based on expansion state. * Returns 'flex-start' (top alignment) when expanded, 'center' when collapsed. * * @param currentHeight - Current input height in pixels * @param minHeight - Minimum (single-line) height in pixels * @returns 'flex-start' when expanded (height > minHeight), 'center' when collapsed * * @example * // Expanded state (height > minHeight) * getIconAlignment(100, 40) // returns 'flex-start' * * // Collapsed state (height === minHeight) * getIconAlignment(40, 40) // returns 'center' * * **Validates: Requirements 3.1, 3.2, 3.3** */ export function getIconAlignment( currentHeight: number, minHeight: number ): 'flex-start' | 'center' { // Input validation: handle negative or zero values const validCurrentHeight = Math.max(0, currentHeight); const validMinHeight = Math.max(1, minHeight); return validCurrentHeight > validMinHeight ? 'flex-start' : 'center'; } /** * Configuration options for resolving effective max height. */ export interface MaxHeightConfig { /** * Maximum number of lines before scrolling is enabled. * Used to calculate maxHeight if maxHeight is not explicitly provided. */ maxLines?: number; /** * Maximum height in pixels. Takes precedence over maxLines if provided. */ maxHeight?: number; /** * Line height for text (used in maxLines calculation). * @default DEFAULT_LINE_HEIGHT */ lineHeight?: number; /** * Vertical padding (used in maxLines calculation). * @default DEFAULT_PADDING_VERTICAL */ paddingVertical?: number; } /** * Pure function to resolve the effective maximum height based on configuration. * When both maxLines and maxHeight are provided, maxHeight takes precedence. * * @param config - Configuration object with maxLines and/or maxHeight * @returns The effective maximum height in pixels * * @example * // maxHeight takes precedence over maxLines * resolveEffectiveMaxHeight({ maxLines: 5, maxHeight: 100 }) // returns 100 * * // maxLines is used when maxHeight is not provided * resolveEffectiveMaxHeight({ maxLines: 5 }) // returns 144 (5 * 24 + 12 * 2) * * // Default values when neither is provided * resolveEffectiveMaxHeight({}) // returns DEFAULT_MAX_HEIGHT * * **Validates: Requirements 2.2, 2.3** */ export function resolveEffectiveMaxHeight(config: MaxHeightConfig): number { const { maxLines, maxHeight, lineHeight = DEFAULT_LINE_HEIGHT, paddingVertical = DEFAULT_PADDING_VERTICAL, } = config; // If maxHeight is explicitly provided (not undefined), it takes precedence if (maxHeight !== undefined) { // Validate maxHeight: must be positive return Math.max(1, maxHeight); } // If maxLines is provided, calculate maxHeight from it if (maxLines !== undefined) { return calculateMaxHeightFromLines(maxLines, lineHeight, paddingVertical); } // Default: use DEFAULT_MAX_HEIGHT return DEFAULT_MAX_HEIGHT; } /** * Pure function to determine if height state should be updated. * Returns true only when the new calculated height differs from the current height. * This optimization prevents unnecessary re-renders when height doesn't change. * * @param newHeight - The newly calculated height from content size change * @param currentHeight - The current height state value * @returns true if height should be updated, false otherwise * * @example * // Height changed - should update * shouldUpdateHeight(100, 40) // returns true * * // Height unchanged - should not update * shouldUpdateHeight(40, 40) // returns false * * // Floating point comparison with tolerance * shouldUpdateHeight(40.0001, 40) // returns false (within tolerance) * * **Validates: Requirements 7.2** */ export function shouldUpdateHeight(newHeight: number, currentHeight: number): boolean { // Use a small tolerance for floating point comparison to avoid // unnecessary updates due to floating point precision issues const TOLERANCE = 0.001; return Math.abs(newHeight - currentHeight) > TOLERANCE; }