From 04f82da13c635b50e9f62dabb2d685f6e259738d Mon Sep 17 00:00:00 2001 From: Connor Berghoffer Date: Wed, 19 Nov 2025 16:24:19 +1300 Subject: [PATCH] fix: emoji breaking box alignment Emojis take up 2 visual columns but were counted as 1 character, causing the box right edge to misalign. Now using stringWidth instead of .length which properly calculates visual width for emojis and other wide characters. Fixes #402 --- src/utils/box.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/utils/box.ts b/src/utils/box.ts index e400a15a..b72d1da1 100644 --- a/src/utils/box.ts +++ b/src/utils/box.ts @@ -1,3 +1,4 @@ +import stringWidth from "string-width"; import { getColor } from "./color"; import { stripAnsi } from "./string"; @@ -257,8 +258,8 @@ export function box(text: string, _opts: BoxOpts = {}) { const height = textLines.length + paddingOffset; const width = Math.max( - ...textLines.map((line) => stripAnsi(line).length), - opts.title ? stripAnsi(opts.title).length : 0, + ...textLines.map((line) => stringWidth(line)), + opts.title ? stringWidth(opts.title) : 0, ) + paddingOffset; const widthOffset = width + paddingOffset; @@ -273,12 +274,12 @@ export function box(text: string, _opts: BoxOpts = {}) { if (opts.title) { const title = _color ? _color(opts.title) : opts.title; const left = borderStyle.h.repeat( - Math.floor((width - stripAnsi(opts.title).length) / 2), + Math.floor((width - stringWidth(opts.title)) / 2), ); const right = borderStyle.h.repeat( width - - stripAnsi(opts.title).length - - stripAnsi(left).length + + stringWidth(opts.title) - + stringWidth(left) + paddingOffset, ); boxLines.push( @@ -312,7 +313,7 @@ export function box(text: string, _opts: BoxOpts = {}) { // Text line const line = textLines[i - valignOffset]; const left = " ".repeat(paddingOffset); - const right = " ".repeat(width - stripAnsi(line).length); + const right = " ".repeat(width - stringWidth(line)); boxLines.push( `${leftSpace}${borderStyle.v}${left}${line}${right}${borderStyle.v}`, );