nhb-toolbox
Version:
A versatile collection of smart, efficient, and reusable utility functions, classes and types for everyday development needs.
287 lines (286 loc) • 10.6 kB
JavaScript
var _a;
import { convertHexToRgb, convertHslToRgb } from '../colors/convert.js';
import { CSS_COLORS } from '../colors/css-colors.js';
import { isHex6, isRGB } from '../colors/guards.js';
import { _isValidHue, _isValidPercentage, _isValidRGBComponent } from '../colors/helpers.js';
import { isNumber, isString } from '../guards/primitives.js';
import { isBrowser } from '../guards/specials.js';
import { _logToConsole } from './console.log.js';
import { ANSI_16_COLORS, ANSI_TEXT_STYLES, CSS_TEXT_STYLES } from './constants.js';
import { _css16ToHex, _extractColorName, _isAnsi16ColorValue, _isAnsiSequence, _isCSS16Color, } from './helpers.js';
export function detectColorSupport() {
if ('NO_COLOR' in process.env)
return 0;
if ('FORCE_COLOR' in process.env)
return 3;
if (!process.stdout.isTTY)
return 0;
const term = process.env.TERM ?? process.env.COLORTERM ?? '';
if (term === 'dumb')
return 0;
if (/\b256(color)?\b/i.test(term))
return 2;
if (/\btruecolor\b|\b24bit\b/i.test(term))
return 3;
return 1;
}
export function rgbToAnsi(r, g, b, isBg = false) {
const open = `\x1b[${isBg ? 48 : 38};2;${r};${g};${b}m`;
const close = `\x1b[${isBg ? 49 : 39}m`;
return [open, close];
}
export function hexToAnsi(hex, isBg = false) {
const rgb = (convertHexToRgb(hex).match(/\d+/g) || []).map(parseFloat);
return rgbToAnsi(...rgb, isBg);
}
export function isCSSColor(value) {
return value in CSS_COLORS;
}
export function isBGColor(value) {
return value?.startsWith('bg') && isCSSColor(value.slice(2).toLowerCase());
}
export function isTextStyle(value) {
return value in CSS_TEXT_STYLES || value in ANSI_TEXT_STYLES;
}
export class LogStyler {
#styles;
constructor(styles = []) {
this.#styles = styles;
}
#applyStyles(...style) {
return createStylogProxy(new _a([...this.#styles, ...style]));
}
style(...style) {
return this.#applyStyles(...style);
}
ansi16(color) {
return this.#applyStyles(ANSI_16_COLORS[color], `css-${color}`);
}
toCSS(input, stringify = false) {
const stringified = stringify === true ? JSON.stringify(input) : `${input}`;
const cssList = [];
for (const style of this.#styles) {
if (isString(style)) {
if (isTextStyle(style)) {
cssList.push(CSS_TEXT_STYLES[style]);
}
else if (isBGColor(style)) {
const color = CSS_COLORS[_extractColorName(style)];
cssList.push(`background: ${color}`);
}
else if (isCSSColor(style)) {
const color = CSS_COLORS[style];
cssList.push(`color: ${color}`);
}
else if (this.#isValidHexOrRGB(style)) {
if (style.startsWith('bg-')) {
cssList.push(`background: ${style?.replace('bg-', '')}`);
}
else {
cssList.push(`color: ${style}`);
}
}
else if (_isCSS16Color(style)) {
const color = _css16ToHex(style);
const colorValue = style.startsWith('css-bg')
? `background: ${color}`
: `color: ${color}`;
cssList.push(colorValue);
}
}
}
return [`%c${stringified}`, cssList];
}
toANSI(input, stringify = false) {
const stringified = stringify === true ? JSON.stringify(input) : `${input}`;
let openSeq = '', closeSeq = '';
let fgOpenSeq = '', bgOpenSeq = '';
const reopenSequences = new Map();
for (const style of this.#styles) {
if (isString(style)) {
if (isTextStyle(style)) {
const [open, close] = ANSI_TEXT_STYLES[style];
openSeq += open;
closeSeq = close + closeSeq;
reopenSequences.set(close, (reopenSequences.get(close) ?? '') + open);
}
else if (isBGColor(style)) {
const hex = CSS_COLORS[_extractColorName(style)];
const [open, close] = hexToAnsi(hex, true);
openSeq += open;
closeSeq = close + closeSeq;
bgOpenSeq = open;
}
else if (isCSSColor(style)) {
const hex = CSS_COLORS[style];
const [open, close] = hexToAnsi(hex, false);
openSeq += open;
closeSeq = close + closeSeq;
fgOpenSeq = open;
}
}
else if (_isAnsiSequence(style)) {
openSeq += style[0];
closeSeq = style[1] + closeSeq;
if (style[1] === '\x1b[49m') {
bgOpenSeq = style[0];
}
else if (style[1] === '\x1b[39m') {
fgOpenSeq = style[0];
}
}
else if (_isAnsi16ColorValue(style)) {
const [open, close] = style.map((s) => `\x1b[${s}m`);
openSeq += open;
closeSeq = close + closeSeq;
if (close === '\x1b[49m') {
bgOpenSeq = open;
}
else if (close === '\x1b[39m') {
fgOpenSeq = open;
}
}
}
if (!detectColorSupport()) {
return stringified;
}
else {
let nestedStr = stringified;
if (nestedStr.includes('\x1b[')) {
if (fgOpenSeq) {
nestedStr = nestedStr.replaceAll('\x1b[39m', `\x1b[39m${fgOpenSeq}`);
}
if (bgOpenSeq) {
nestedStr = nestedStr.replaceAll('\x1b[49m', `\x1b[49m${bgOpenSeq}`);
}
for (const [close, reopen] of reopenSequences) {
nestedStr = nestedStr.replaceAll(close, `${close}${reopen}`);
}
if (openSeq) {
nestedStr = nestedStr.replaceAll('\x1b[0m', `\x1b[0m${openSeq}`);
}
}
return openSeq.concat(nestedStr, closeSeq);
}
}
log(input, stringify = false) {
if (isBrowser()) {
const [fmt, cssList] = this.toCSS(input, stringify);
_logToConsole(fmt, cssList.join(';'));
}
else {
_logToConsole(this.toANSI(input, stringify));
}
}
#isValidHexOrRGB(color) {
const pure = color?.replace('bg-', '');
return isHex6(pure) || isRGB(pure);
}
#sanitizeHex(code) {
return code?.trim()?.startsWith('#') ? code?.trim() : `#${code?.trim()}`;
}
#handleHex(code, isBg = false) {
const sanitized = this.#sanitizeHex(code);
if (!isHex6(sanitized)) {
return this.#applyStyles();
}
const ansi = hexToAnsi(sanitized, isBg);
return this.#applyStyles(isBg ? `bg-${sanitized}` : sanitized, ansi);
}
hex(code) {
return this.#handleHex(code, false);
}
bgHex(code) {
return this.#handleHex(code, true);
}
#extractColorValues(code) {
const trimmed = code?.trim();
return (trimmed?.match(/[\d.]+%?/g) || []).map(parseFloat);
}
#isValidRGB(...value) {
return value.every(_isValidRGBComponent);
}
#handleRGB(code, green, blue, isBg = false) {
if (isString(code)) {
const rgb = this.#extractColorValues(code);
if (this.#isValidRGB(...rgb)) {
return this.#applyStyles(rgbToAnsi(...rgb, isBg), isBg
? `bg-rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`
: `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`);
}
else {
return this.#applyStyles();
}
}
else if (isNumber(code) && isNumber(green) && isNumber(blue)) {
if (this.#isValidRGB(code, green, blue)) {
return this.#applyStyles(rgbToAnsi(code, green, blue, isBg), isBg
? `bg-rgb(${code}, ${green}, ${blue})`
: `rgb(${code}, ${green}, ${blue})`);
}
else {
return this.#applyStyles();
}
}
else {
return this.#applyStyles();
}
}
rgb(code, green, blue) {
return this.#handleRGB(code, green, blue, false);
}
bgRGB(code, green, blue) {
return this.#handleRGB(code, green, blue, true);
}
#isValidHSL(h, s, l) {
return _isValidHue(h) && _isValidPercentage(s) && _isValidPercentage(l);
}
#handleHSL(code, saturation, lightness, isBg = false) {
if (isString(code)) {
const hsl = this.#extractColorValues(code);
if (this.#isValidHSL(...hsl)) {
return this.#handleRGB(convertHslToRgb(...hsl), undefined, undefined, isBg);
}
else {
return this.#applyStyles();
}
}
else if (isNumber(code) && isNumber(saturation) && isNumber(lightness)) {
if (this.#isValidHSL(code, saturation, lightness)) {
return this.#handleRGB(convertHslToRgb(code, saturation, lightness), undefined, undefined, isBg);
}
else {
return this.#applyStyles();
}
}
else {
return this.#applyStyles();
}
}
hsl(code, saturation, lightness) {
return this.#handleHSL(code, saturation, lightness, false);
}
bgHSL(code, saturation, lightness) {
return this.#handleHSL(code, saturation, lightness, true);
}
}
_a = LogStyler;
function createStylogProxy(styler) {
return new Proxy(styler, {
get(target, prop) {
if (prop in target) {
const value = target[prop];
if (typeof value === 'function') {
return value.bind(target);
}
else {
return value;
}
}
if (isCSSColor(prop) || isBGColor(prop) || isTextStyle(prop)) {
return createStylogProxy(target.style(prop));
}
},
});
}
export const Stylog = createStylogProxy(new LogStyler());