UNPKG

tuix

Version:

A performant TUI framework for Bun with JSX and reactive state management

103 lines (86 loc) 2.66 kB
/** * String width calculation utility using Bun's native stringWidth * * This wrapper provides compatibility with the string-width package * while leveraging Bun's native performance (26x faster). * * Handles edge cases where Bun.stringWidth differs from string-width package. */ // Known emoji sequences where Bun.stringWidth returns different values const EMOJI_OVERRIDES: Record<string, number> = { "🏳️‍🌈": 2, // Rainbow flag - Bun returns 1, should be 2 } /** * Calculate the visual width of a string in terminal columns * * @param str - The string to measure * @returns The width in terminal columns */ export const stringWidth = (str: string): number => { // Check for exact matches in overrides if (str in EMOJI_OVERRIDES) { return EMOJI_OVERRIDES[str] } // Use Bun's native implementation return Bun.stringWidth(str) } /** * Truncate a string to fit within a given width * * @param str - The string to truncate * @param maxWidth - Maximum width in columns * @param suffix - Suffix to append when truncated (default: "…") * @returns Truncated string */ export const truncateString = (str: string, maxWidth: number, suffix = "…"): string => { const width = stringWidth(str) if (width <= maxWidth) { return str } const suffixWidth = stringWidth(suffix) const targetWidth = maxWidth - suffixWidth if (targetWidth <= 0) { return suffix.slice(0, maxWidth) } let result = "" let currentWidth = 0 // Use grapheme segmenter to avoid splitting characters const segmenter = new Intl.Segmenter() const segments = [...segmenter.segment(str)] for (const { segment } of segments) { const segmentWidth = stringWidth(segment) if (currentWidth + segmentWidth > targetWidth) { break } result += segment currentWidth += segmentWidth } return result + suffix } /** * Pad a string to a specific width * * @param str - The string to pad * @param width - Target width * @param align - Alignment (left, center, right) * @returns Padded string */ export const padString = (str: string, width: number, align: 'left' | 'center' | 'right' = 'left'): string => { const strWidth = stringWidth(str) if (strWidth >= width) { return str } const padding = width - strWidth switch (align) { case 'center': { const leftPad = Math.floor(padding / 2) const rightPad = padding - leftPad return ' '.repeat(leftPad) + str + ' '.repeat(rightPad) } case 'right': return ' '.repeat(padding) + str case 'left': default: return str + ' '.repeat(padding) } }