@@ -40,6 +40,7 @@ const {
4040 RegExpPrototypeExec,
4141 SafeMap,
4242 StringPrototypeSlice,
43+ StringPrototypeStartsWith,
4344 StringPrototypeToWellFormed,
4445} = primordials ;
4546
@@ -118,6 +119,8 @@ const kBoldCode = 1;
118119
119120// Close sequence for 24-bit foreground colors (reset to default foreground)
120121const kHexCloseSeq = kEscape + '39' + kEscapeEnd ;
122+ // Close sequence for 24-bit background colors (reset to default background)
123+ const kBgHexCloseSeq = kEscape + '49' + kEscapeEnd ;
121124
122125let styleCache ;
123126
@@ -155,20 +158,27 @@ function getStyleCache() {
155158}
156159
157160/**
158- * Returns the cached ANSI escape sequences for a hex color.
161+ * Returns the cached ANSI escape sequences for a hex color (foreground or background) .
159162 * Computes and caches on first use to avoid repeated Buffer allocations.
160- * @param {string } hex A valid hex color string (#RGB or #RRGGBB)
163+ * @param {string } hex A valid hex color string (#RGB or #RRGGBB) or background (bg#RGB or bg#RRGGBB)
161164 * @returns {{openSeq: string, closeSeq: string} }
162165 */
163166function getHexStyle ( hex ) {
164167 const cache = getHexStyleCache ( ) ;
165168 const cached = cache . get ( hex ) ;
166169 if ( cached !== undefined ) return cached ;
167- const { 0 : r , 1 : g , 2 : b } = hexToRgb ( hex ) ;
170+
171+ // Check if this is a background hex color prefixed with 'bg#'
172+ const isBg = StringPrototypeStartsWith ( hex , 'bg#' ) ;
173+ // Strip 'bg' prefix to extract the raw hex color string (#RGB or #RRGGBB)
174+ const cleanHex = isBg ? StringPrototypeSlice ( hex , 2 ) : hex ;
175+ const { 0 : r , 1 : g , 2 : b } = hexToRgb ( cleanHex ) ;
168176 const style = {
169177 __proto__ : null ,
170- openSeq : kEscape + rgbToAnsi24Bit ( r , g , b ) + kEscapeEnd ,
171- closeSeq : kHexCloseSeq ,
178+ // 38 represents foreground TrueColor SGR parameter, while 48 represents background
179+ openSeq : kEscape + ( isBg ? bgRgbToAnsi24Bit ( r , g , b ) : rgbToAnsi24Bit ( r , g , b ) ) + kEscapeEnd ,
180+ // 39 resets foreground to default, while 49 resets background to default
181+ closeSeq : isBg ? kBgHexCloseSeq : kHexCloseSeq ,
172182 } ;
173183 if ( cache . size >= kHexStyleCacheMax )
174184 cache . delete ( cache . keys ( ) . next ( ) . value ) ;
@@ -201,6 +211,8 @@ function replaceCloseCode(str, closeSeq, openSeq, keepClose) {
201211
202212// Matches #RGB or #RRGGBB
203213const hexColorRegExp = / ^ # (?: [ 0 - 9 a - f A - F ] { 3 } | [ 0 - 9 a - f A - F ] { 6 } ) $ / ;
214+ // Matches bg#RGB or bg#RRGGBB
215+ const bgHexColorRegExp = / ^ b g # (?: [ 0 - 9 a - f A - F ] { 3 } | [ 0 - 9 a - f A - F ] { 6 } ) $ / ;
204216
205217/**
206218 * Parses a hex color string into RGB components.
@@ -235,6 +247,17 @@ function rgbToAnsi24Bit(r, g, b) {
235247 return `38;2;${ r } ;${ g } ;${ b } ` ;
236248}
237249
250+ /**
251+ * Generates the ANSI TrueColor (24-bit) escape sequence for a background color.
252+ * @param {number } r Red component (0-255)
253+ * @param {number } g Green component (0-255)
254+ * @param {number } b Blue component (0-255)
255+ * @returns {string } The ANSI escape sequence
256+ */
257+ function bgRgbToAnsi24Bit ( r , g , b ) {
258+ return `48;2;${ r } ;${ g } ;${ b } ` ;
259+ }
260+
238261/**
239262 * @param {string | string[] } format
240263 * @param {string } text
@@ -256,10 +279,15 @@ function styleText(format, text, options) {
256279 return style . openSeq + processed + style . closeSeq ;
257280 }
258281
259- if ( format [ 0 ] === '#' ) {
282+ const isHex = format [ 0 ] === '#' ;
283+ const isBgHex = ! isHex && StringPrototypeStartsWith ( format , 'bg#' ) ;
284+ if ( isHex || isBgHex ) {
260285 let hexStyle = getHexStyleCache ( ) . get ( format ) ;
261- if ( hexStyle === undefined && RegExpPrototypeExec ( hexColorRegExp , format ) !== null ) {
262- hexStyle = getHexStyle ( format ) ;
286+ if ( hexStyle === undefined ) {
287+ const regExp = isHex ? hexColorRegExp : bgHexColorRegExp ;
288+ if ( RegExpPrototypeExec ( regExp , format ) !== null ) {
289+ hexStyle = getHexStyle ( format ) ;
290+ }
263291 }
264292 if ( hexStyle !== undefined ) {
265293 const processed = replaceCloseCode ( text , hexStyle . closeSeq , hexStyle . openSeq , false ) ;
@@ -297,17 +325,25 @@ function styleText(format, text, options) {
297325 for ( const key of formatArray ) {
298326 if ( key === 'none' ) continue ;
299327
300- if ( typeof key === 'string' && key [ 0 ] === '#' ) {
301- if ( RegExpPrototypeExec ( hexColorRegExp , key ) === null ) {
302- throw new ERR_INVALID_ARG_VALUE ( 'format' , key ,
303- 'must be a valid hex color (#RGB or #RRGGBB)' ) ;
328+ if ( typeof key === 'string' && ( key [ 0 ] === '#' || StringPrototypeStartsWith ( key , 'bg#' ) ) ) {
329+ const isBg = key [ 0 ] === 'b' ;
330+ const regExp = isBg ? bgHexColorRegExp : hexColorRegExp ;
331+ if ( RegExpPrototypeExec ( regExp , key ) === null ) {
332+ throw new ERR_INVALID_ARG_VALUE (
333+ 'format' ,
334+ key ,
335+ 'must be a valid hex color (#RGB or #RRGGBB) or ' +
336+ 'background hex color (bg#RGB or bg#RRGGBB)' ,
337+ ) ;
304338 }
305339 if ( skipColorize ) continue ;
306- const { 0 : r , 1 : g , 2 : b } = hexToRgb ( key ) ;
307- const hexOpenSeq = kEscape + rgbToAnsi24Bit ( r , g , b ) + kEscapeEnd ;
340+ const cleanHex = isBg ? StringPrototypeSlice ( key , 2 ) : key ;
341+ const { 0 : r , 1 : g , 2 : b } = hexToRgb ( cleanHex ) ;
342+ const hexOpenSeq = kEscape + ( isBg ? bgRgbToAnsi24Bit ( r , g , b ) : rgbToAnsi24Bit ( r , g , b ) ) + kEscapeEnd ;
343+ const closeSeq = isBg ? kBgHexCloseSeq : kHexCloseSeq ;
308344 openCodes += hexOpenSeq ;
309- closeCodes = kHexCloseSeq + closeCodes ;
310- processedText = replaceCloseCode ( processedText , kHexCloseSeq , hexOpenSeq , false ) ;
345+ closeCodes = closeSeq + closeCodes ;
346+ processedText = replaceCloseCode ( processedText , closeSeq , hexOpenSeq , false ) ;
311347 continue ;
312348 }
313349
0 commit comments