@heinrichb/console-toolkit 1.0.7 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/dist/components/progress.d.ts +0 -75
- package/dist/components/progress.js +0 -42
- package/dist/components/progress.test.d.ts +0 -1
- package/dist/components/progress.test.js +0 -105
- package/dist/components/spinner.d.ts +0 -36
- package/dist/components/spinner.js +0 -33
- package/dist/components/spinner.test.d.ts +0 -1
- package/dist/components/spinner.test.js +0 -53
- package/dist/core/layout.d.ts +0 -25
- package/dist/core/layout.js +0 -55
- package/dist/core/layout.test.d.ts +0 -1
- package/dist/core/layout.test.js +0 -52
- package/dist/core/printer.d.ts +0 -33
- package/dist/core/printer.js +0 -129
- package/dist/core/printer.test.d.ts +0 -1
- package/dist/core/printer.test.js +0 -124
- package/dist/core/style.d.ts +0 -53
- package/dist/core/style.js +0 -171
- package/dist/core/style.test.d.ts +0 -1
- package/dist/core/style.test.js +0 -108
- package/dist/core/types.d.ts +0 -72
- package/dist/core/types.js +0 -4
- package/dist/core/utils.d.ts +0 -25
- package/dist/core/utils.js +0 -36
- package/dist/core/utils.test.d.ts +0 -1
- package/dist/core/utils.test.js +0 -25
- package/dist/demo.d.ts +0 -1
- package/dist/demo.js +0 -145
- package/dist/demo.test.d.ts +0 -1
- package/dist/demo.test.js +0 -28
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -14
- package/dist/presets/ascii.d.ts +0 -5
- package/dist/presets/ascii.js +0 -35
- package/dist/presets/ascii.test.d.ts +0 -1
- package/dist/presets/ascii.test.js +0 -21
package/dist/core/printer.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { getGradientColor, mergeStyles, resolveStyle, RESET, interpolateColor, resolveModifiersToAnsi } from "./style";
|
|
2
|
-
const ESC = "\x1b";
|
|
3
|
-
/**
|
|
4
|
-
* Handles rendering PrintBlocks to the terminal with support for interactive/live overwriting.
|
|
5
|
-
*/
|
|
6
|
-
export class Printer {
|
|
7
|
-
linesRendered = 0;
|
|
8
|
-
isLive;
|
|
9
|
-
data;
|
|
10
|
-
constructor(options = {}) {
|
|
11
|
-
this.isLive = options.live ?? false;
|
|
12
|
-
this.data = options.data;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Generates the clear sequence to move cursor and clear previously rendered lines.
|
|
16
|
-
*/
|
|
17
|
-
getClearSequence() {
|
|
18
|
-
if (!this.isLive || this.linesRendered === 0)
|
|
19
|
-
return "";
|
|
20
|
-
return `${ESC}[1A${ESC}[2K\r`.repeat(this.linesRendered);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Clears the console using the stored line count.
|
|
24
|
-
*/
|
|
25
|
-
clear() {
|
|
26
|
-
if (this.linesRendered > 0) {
|
|
27
|
-
process.stdout.write(this.getClearSequence());
|
|
28
|
-
this.linesRendered = 0;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Renders the PrintBlock to the standard output.
|
|
33
|
-
* If data is provided, updates the internal state.
|
|
34
|
-
*
|
|
35
|
-
* @param data - Optional data to update the printer with.
|
|
36
|
-
*/
|
|
37
|
-
print(data) {
|
|
38
|
-
if (data) {
|
|
39
|
-
this.data = data;
|
|
40
|
-
}
|
|
41
|
-
if (!this.data) {
|
|
42
|
-
return; // Nothing to print
|
|
43
|
-
}
|
|
44
|
-
let output = this.getClearSequence();
|
|
45
|
-
const lines = this.data.lines;
|
|
46
|
-
const blockStyle = this.data.style ?? {};
|
|
47
|
-
lines.forEach((line, lineIndex) => {
|
|
48
|
-
output += this.renderLine(line, lineIndex, lines.length, blockStyle);
|
|
49
|
-
output += "\n";
|
|
50
|
-
});
|
|
51
|
-
process.stdout.write(output);
|
|
52
|
-
this.linesRendered = lines.length;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Resolves the block's vertical gradient (if any) to a solid color for the specific line.
|
|
56
|
-
*/
|
|
57
|
-
resolveBlockColorForLine(blockStyle, lineIndex, totalLines) {
|
|
58
|
-
if (!blockStyle.color)
|
|
59
|
-
return undefined;
|
|
60
|
-
if (Array.isArray(blockStyle.color)) {
|
|
61
|
-
// Vertical Gradient
|
|
62
|
-
if (totalLines <= 1)
|
|
63
|
-
return blockStyle.color[0]; // Single line, use first color
|
|
64
|
-
const colors = blockStyle.color;
|
|
65
|
-
const factor = lineIndex / (totalLines - 1);
|
|
66
|
-
// Interpolate manually to obtain Hex Color
|
|
67
|
-
const f = Math.max(0, Math.min(1, factor));
|
|
68
|
-
const segmentLength = 1 / (colors.length - 1);
|
|
69
|
-
const segmentIndex = Math.min(Math.floor(f / segmentLength), colors.length - 2);
|
|
70
|
-
const segmentFactor = (f - segmentIndex * segmentLength) / segmentLength;
|
|
71
|
-
const c1 = colors[segmentIndex];
|
|
72
|
-
const c2 = colors[segmentIndex + 1];
|
|
73
|
-
return interpolateColor(c1, c2, segmentFactor);
|
|
74
|
-
}
|
|
75
|
-
return blockStyle.color;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Renders a single line.
|
|
79
|
-
*/
|
|
80
|
-
renderLine(line, lineIndex, totalLines, parentBlockStyle) {
|
|
81
|
-
// 1. Resolve Block Gradient to Solid Color for this line
|
|
82
|
-
const blockColorForLine = this.resolveBlockColorForLine(parentBlockStyle, lineIndex, totalLines);
|
|
83
|
-
// 2. Create Base Line Style (Block Modifiers + Resolved Block Color)
|
|
84
|
-
const baseLineStyle = {
|
|
85
|
-
modifiers: parentBlockStyle.modifiers,
|
|
86
|
-
color: blockColorForLine
|
|
87
|
-
};
|
|
88
|
-
// 3. Merge with Line's own style
|
|
89
|
-
// If line.style has color, it overrides baseLineStyle.color
|
|
90
|
-
const effectiveLineStyle = mergeStyles(baseLineStyle, line.style);
|
|
91
|
-
// 4. Pre-calculate total chars for horizontal gradients
|
|
92
|
-
const totalChars = line.segments.reduce((acc, seg) => acc + seg.text.length, 0);
|
|
93
|
-
let currentCharIndex = 0;
|
|
94
|
-
let lineOutput = "";
|
|
95
|
-
line.segments.forEach((seg) => {
|
|
96
|
-
const effectiveSegmentStyle = mergeStyles(effectiveLineStyle, seg.style);
|
|
97
|
-
if (Array.isArray(effectiveSegmentStyle.color)) {
|
|
98
|
-
// Gradient Handling (Horizontal)
|
|
99
|
-
const colors = effectiveSegmentStyle.color;
|
|
100
|
-
const text = seg.text;
|
|
101
|
-
// Determine if we are using the Line's gradient (Global) or Segment's gradient (Local)
|
|
102
|
-
// If effectiveSegmentStyle.color === effectiveLineStyle.color, it's inherited (Global)
|
|
103
|
-
// Otherwise it's the segment's own gradient (Local)
|
|
104
|
-
const isGlobalGradient = effectiveSegmentStyle.color === effectiveLineStyle.color;
|
|
105
|
-
// Iterate characters to apply gradient
|
|
106
|
-
const modifiersAnsi = resolveModifiersToAnsi(effectiveSegmentStyle.modifiers);
|
|
107
|
-
for (let i = 0; i < text.length; i++) {
|
|
108
|
-
let factor = 0;
|
|
109
|
-
if (isGlobalGradient && totalChars > 1) {
|
|
110
|
-
factor = (currentCharIndex + i) / (totalChars - 1);
|
|
111
|
-
}
|
|
112
|
-
else if (!isGlobalGradient && text.length > 1) {
|
|
113
|
-
factor = i / (text.length - 1);
|
|
114
|
-
}
|
|
115
|
-
const colorAnsi = getGradientColor(colors, factor); // Returns ANSI Color Code
|
|
116
|
-
lineOutput += `${modifiersAnsi}${colorAnsi}${text[i]}`;
|
|
117
|
-
}
|
|
118
|
-
lineOutput += RESET;
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
// Solid Color Handling
|
|
122
|
-
const ansi = resolveStyle(effectiveSegmentStyle);
|
|
123
|
-
lineOutput += `${ansi}${seg.text}${RESET}`;
|
|
124
|
-
}
|
|
125
|
-
currentCharIndex += seg.text.length;
|
|
126
|
-
});
|
|
127
|
-
return lineOutput;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { expect, test, describe, spyOn, afterEach } from "bun:test";
|
|
2
|
-
import { Printer } from "./printer";
|
|
3
|
-
import { resolveColorToAnsi } from "./style";
|
|
4
|
-
const ESC = "\x1b";
|
|
5
|
-
describe("Printer", () => {
|
|
6
|
-
// Mock process.stdout.write to prevent actual output during tests
|
|
7
|
-
const stdoutSpy = spyOn(process.stdout, "write").mockImplementation(() => true);
|
|
8
|
-
afterEach(() => {
|
|
9
|
-
stdoutSpy.mockClear();
|
|
10
|
-
});
|
|
11
|
-
test("Printer.print outputs basic text with solid styles", () => {
|
|
12
|
-
const printer = new Printer();
|
|
13
|
-
const block = {
|
|
14
|
-
lines: [{ segments: [{ text: "Hello", style: { color: "red" } }] }]
|
|
15
|
-
};
|
|
16
|
-
printer.print(block);
|
|
17
|
-
expect(stdoutSpy).toHaveBeenCalledTimes(1);
|
|
18
|
-
const output = stdoutSpy.mock.calls[0][0];
|
|
19
|
-
// Check for Red ANSI + Text + Reset
|
|
20
|
-
expect(output).toContain(`${ESC}[38;2;239;68;68mHello${ESC}[0m`);
|
|
21
|
-
});
|
|
22
|
-
test("Printer.print applies Block style to lines (inheritance)", () => {
|
|
23
|
-
const printer = new Printer();
|
|
24
|
-
const block = {
|
|
25
|
-
style: { color: "blue", modifiers: ["bold"] },
|
|
26
|
-
lines: [{ segments: [{ text: "Line 1" }] }, { segments: [{ text: "Line 2" }] }]
|
|
27
|
-
};
|
|
28
|
-
printer.print(block);
|
|
29
|
-
const output = stdoutSpy.mock.calls[0][0];
|
|
30
|
-
const blueAnsi = resolveColorToAnsi("blue");
|
|
31
|
-
const boldAnsi = `${ESC}[1m`;
|
|
32
|
-
// Both lines should be Blue + Bold
|
|
33
|
-
// Matches: Bold + Blue + Text + Reset
|
|
34
|
-
expect(output).toContain(`${boldAnsi}${blueAnsi}Line 1${ESC}[0m`);
|
|
35
|
-
expect(output).toContain(`${boldAnsi}${blueAnsi}Line 2${ESC}[0m`);
|
|
36
|
-
});
|
|
37
|
-
test("Printer handles Block Vertical Gradient (Lines inherit solid colors)", () => {
|
|
38
|
-
const printer = new Printer();
|
|
39
|
-
const block = {
|
|
40
|
-
style: { color: ["#000000", "#FFFFFF"] }, // Black to White
|
|
41
|
-
lines: [
|
|
42
|
-
{ segments: [{ text: "Start" }] }, // Should be Black
|
|
43
|
-
{ segments: [{ text: "Middle" }] }, // Should be Gray
|
|
44
|
-
{ segments: [{ text: "End" }] } // Should be White
|
|
45
|
-
]
|
|
46
|
-
};
|
|
47
|
-
printer.print(block);
|
|
48
|
-
const output = stdoutSpy.mock.calls[0][0];
|
|
49
|
-
const blackAnsi = resolveColorToAnsi("#000000");
|
|
50
|
-
const whiteAnsi = resolveColorToAnsi("#FFFFFF");
|
|
51
|
-
const grayAnsi = resolveColorToAnsi("#808080");
|
|
52
|
-
expect(output).toContain(`${blackAnsi}Start${ESC}[0m`);
|
|
53
|
-
expect(output).toContain(`${grayAnsi}Middle${ESC}[0m`);
|
|
54
|
-
expect(output).toContain(`${whiteAnsi}End${ESC}[0m`);
|
|
55
|
-
});
|
|
56
|
-
test("Printer handles Line Override (Horizontal Gradient)", () => {
|
|
57
|
-
const printer = new Printer();
|
|
58
|
-
const block = {
|
|
59
|
-
lines: [
|
|
60
|
-
{
|
|
61
|
-
style: { color: ["#FF0000", "#0000FF"] }, // Red to Blue
|
|
62
|
-
segments: [{ text: "GB" }] // Gradient applies to these 2 chars
|
|
63
|
-
}
|
|
64
|
-
]
|
|
65
|
-
};
|
|
66
|
-
printer.print(block);
|
|
67
|
-
const output = stdoutSpy.mock.calls[0][0];
|
|
68
|
-
// First char 'G' should be Red (start)
|
|
69
|
-
// Second char 'B' should be Blue (end) - wait, factor logic?
|
|
70
|
-
// Length 2. index 0 -> factor 0. index 1 -> factor 1.
|
|
71
|
-
const redAnsi = resolveColorToAnsi("#FF0000");
|
|
72
|
-
const blueAnsi = resolveColorToAnsi("#0000FF");
|
|
73
|
-
expect(output).toContain(`${redAnsi}G`);
|
|
74
|
-
expect(output).toContain(`${blueAnsi}B`);
|
|
75
|
-
});
|
|
76
|
-
test("Printer handles Segment Override (Solid overrides Line Gradient)", () => {
|
|
77
|
-
const printer = new Printer();
|
|
78
|
-
const block = {
|
|
79
|
-
lines: [
|
|
80
|
-
{
|
|
81
|
-
style: { color: ["#FF0000", "#0000FF"] }, // Line Gradient
|
|
82
|
-
segments: [
|
|
83
|
-
{ text: "A" }, // Inherits Gradient (Red)
|
|
84
|
-
{ text: "B", style: { color: "green" } } // Override Solid (Green)
|
|
85
|
-
]
|
|
86
|
-
}
|
|
87
|
-
]
|
|
88
|
-
};
|
|
89
|
-
printer.print(block);
|
|
90
|
-
const output = stdoutSpy.mock.calls[0][0];
|
|
91
|
-
const redAnsi = resolveColorToAnsi("#FF0000");
|
|
92
|
-
const greenAnsi = resolveColorToAnsi("green");
|
|
93
|
-
expect(output).toContain(`${redAnsi}A`);
|
|
94
|
-
expect(output).toContain(`${greenAnsi}B`);
|
|
95
|
-
});
|
|
96
|
-
test("Printer handles live clearing", () => {
|
|
97
|
-
const printer = new Printer({ live: true });
|
|
98
|
-
// Print 2 lines
|
|
99
|
-
printer.print({
|
|
100
|
-
lines: [{ segments: [{ text: "1" }] }, { segments: [{ text: "2" }] }]
|
|
101
|
-
});
|
|
102
|
-
// Print again (should clear 2 lines)
|
|
103
|
-
printer.print({
|
|
104
|
-
lines: [{ segments: [{ text: "New" }] }]
|
|
105
|
-
});
|
|
106
|
-
const clearSeq = `${ESC}[1A${ESC}[2K\r`;
|
|
107
|
-
const expectedClear = clearSeq.repeat(2);
|
|
108
|
-
const secondCallOutput = stdoutSpy.mock.calls[1][0];
|
|
109
|
-
expect(secondCallOutput.startsWith(expectedClear)).toBe(true);
|
|
110
|
-
});
|
|
111
|
-
test("Printer.clear() manually clears the output", () => {
|
|
112
|
-
const printer = new Printer({ live: true });
|
|
113
|
-
printer.print({
|
|
114
|
-
lines: [{ segments: [{ text: "Line 1" }] }]
|
|
115
|
-
});
|
|
116
|
-
expect(stdoutSpy).toHaveBeenCalledTimes(1);
|
|
117
|
-
printer.clear();
|
|
118
|
-
expect(stdoutSpy).toHaveBeenCalledTimes(2);
|
|
119
|
-
const clearSeq = `${ESC}[1A${ESC}[2K\r`;
|
|
120
|
-
const expectedClear = clearSeq.repeat(1);
|
|
121
|
-
const clearOutput = stdoutSpy.mock.calls[1][0];
|
|
122
|
-
expect(clearOutput).toBe(expectedClear);
|
|
123
|
-
});
|
|
124
|
-
});
|
package/dist/core/style.d.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { Color, HexColor, PrintStyle, StyleModifier } from "./types";
|
|
2
|
-
/**
|
|
3
|
-
* ANSI escape sequence to reset all styles.
|
|
4
|
-
*/
|
|
5
|
-
export declare const RESET = "\u001B[0m";
|
|
6
|
-
/**
|
|
7
|
-
* Converts any Color (hex or standard name) to a Hex string.
|
|
8
|
-
*/
|
|
9
|
-
export declare function colorToHex(color: Color): string;
|
|
10
|
-
/**
|
|
11
|
-
* Converts a hex color string to an RGB object.
|
|
12
|
-
*/
|
|
13
|
-
export declare function hexToRgb(hex: string): {
|
|
14
|
-
r: number;
|
|
15
|
-
g: number;
|
|
16
|
-
b: number;
|
|
17
|
-
};
|
|
18
|
-
/**
|
|
19
|
-
* Converts RGB to a 24-bit ANSI foreground color escape sequence.
|
|
20
|
-
*/
|
|
21
|
-
export declare function rgbToAnsi(r: number, g: number, b: number): string;
|
|
22
|
-
/**
|
|
23
|
-
* Converts any Color to an ANSI escape sequence.
|
|
24
|
-
*/
|
|
25
|
-
export declare function resolveColorToAnsi(color: Color): string;
|
|
26
|
-
/**
|
|
27
|
-
* Converts a list of modifiers to an ANSI escape sequence.
|
|
28
|
-
*/
|
|
29
|
-
export declare function resolveModifiersToAnsi(modifiers?: StyleModifier[]): string;
|
|
30
|
-
/**
|
|
31
|
-
* Interpolates between two hex colors.
|
|
32
|
-
*/
|
|
33
|
-
export declare function interpolateHex(color1: string, color2: string, factor: number): string;
|
|
34
|
-
/**
|
|
35
|
-
* Public interpolation function that accepts any Color type.
|
|
36
|
-
*/
|
|
37
|
-
export declare function interpolateColor(color1: Color, color2: Color, factor: number): HexColor;
|
|
38
|
-
/**
|
|
39
|
-
* logic to get a specific color from a multi-stop gradient array at a specific factor (0-1).
|
|
40
|
-
*/
|
|
41
|
-
export declare function getGradientColor(colors: Color[], factor: number): string;
|
|
42
|
-
/**
|
|
43
|
-
* Merges a child style into a parent style.
|
|
44
|
-
* - Modifiers are combined (union).
|
|
45
|
-
* - Child color overrides parent color.
|
|
46
|
-
*/
|
|
47
|
-
export declare function mergeStyles(parent?: PrintStyle, child?: PrintStyle): PrintStyle;
|
|
48
|
-
/**
|
|
49
|
-
* Resolves a style object into an ANSI string.
|
|
50
|
-
* If the color is a gradient (array), it uses the provided factor (0-1) to pick the color.
|
|
51
|
-
* Defaults to factor 0 if not provided.
|
|
52
|
-
*/
|
|
53
|
-
export declare function resolveStyle(style?: PrintStyle, gradientFactor?: number): string;
|
package/dist/core/style.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
// -----------------
|
|
2
|
-
// Color Utilities
|
|
3
|
-
// -----------------
|
|
4
|
-
const ESC = "\x1b";
|
|
5
|
-
/**
|
|
6
|
-
* ANSI escape sequence to reset all styles.
|
|
7
|
-
*/
|
|
8
|
-
export const RESET = `${ESC}[0m`;
|
|
9
|
-
const STANDARD_COLORS = {
|
|
10
|
-
black: "#000000",
|
|
11
|
-
red: "#EF4444", // Tailwind Red-500
|
|
12
|
-
green: "#10B981", // Tailwind Emerald-500
|
|
13
|
-
yellow: "#F59E0B", // Tailwind Amber-500
|
|
14
|
-
blue: "#3B82F6", // Tailwind Blue-500
|
|
15
|
-
magenta: "#EC4899", // Tailwind Pink-500
|
|
16
|
-
cyan: "#06B6D4", // Tailwind Cyan-500
|
|
17
|
-
white: "#FFFFFF",
|
|
18
|
-
gray: "#6B7280", // Tailwind Gray-500
|
|
19
|
-
grey: "#6B7280"
|
|
20
|
-
};
|
|
21
|
-
const MODIFIER_CODES = {
|
|
22
|
-
default: "0",
|
|
23
|
-
bold: "1",
|
|
24
|
-
dim: "2",
|
|
25
|
-
italic: "3",
|
|
26
|
-
underline: "4",
|
|
27
|
-
inverse: "7",
|
|
28
|
-
hidden: "8",
|
|
29
|
-
strikethrough: "9"
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* Converts any Color (hex or standard name) to a Hex string.
|
|
33
|
-
*/
|
|
34
|
-
export function colorToHex(color) {
|
|
35
|
-
if (color.startsWith("#"))
|
|
36
|
-
return color;
|
|
37
|
-
const hex = STANDARD_COLORS[color.toLowerCase()];
|
|
38
|
-
if (!hex)
|
|
39
|
-
return "#FFFFFF"; // Fallback
|
|
40
|
-
return hex;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Converts a hex color string to an RGB object.
|
|
44
|
-
*/
|
|
45
|
-
export function hexToRgb(hex) {
|
|
46
|
-
const h = hex.replace(/^#/, "");
|
|
47
|
-
if (h.length !== 6)
|
|
48
|
-
return { r: 255, g: 255, b: 255 }; // Fallback for invalid hex
|
|
49
|
-
return {
|
|
50
|
-
r: parseInt(h.substring(0, 2), 16),
|
|
51
|
-
g: parseInt(h.substring(2, 4), 16),
|
|
52
|
-
b: parseInt(h.substring(4, 6), 16)
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Converts RGB to a 24-bit ANSI foreground color escape sequence.
|
|
57
|
-
*/
|
|
58
|
-
export function rgbToAnsi(r, g, b) {
|
|
59
|
-
return `${ESC}[38;2;${r};${g};${b}m`;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Converts any Color to an ANSI escape sequence.
|
|
63
|
-
*/
|
|
64
|
-
export function resolveColorToAnsi(color) {
|
|
65
|
-
const hex = colorToHex(color);
|
|
66
|
-
const { r, g, b } = hexToRgb(hex);
|
|
67
|
-
return rgbToAnsi(r, g, b);
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Converts a list of modifiers to an ANSI escape sequence.
|
|
71
|
-
*/
|
|
72
|
-
export function resolveModifiersToAnsi(modifiers) {
|
|
73
|
-
if (!modifiers || modifiers.length === 0)
|
|
74
|
-
return "";
|
|
75
|
-
return modifiers
|
|
76
|
-
.map((m) => {
|
|
77
|
-
const code = MODIFIER_CODES[m];
|
|
78
|
-
return code ? `${ESC}[${code}m` : "";
|
|
79
|
-
})
|
|
80
|
-
.join("");
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Helper to convert number to 2-digit hex.
|
|
84
|
-
*/
|
|
85
|
-
function toHex(c) {
|
|
86
|
-
const hex = Math.max(0, Math.min(255, Math.round(c))).toString(16);
|
|
87
|
-
return hex.length === 1 ? "0" + hex : hex;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Interpolates between two hex colors.
|
|
91
|
-
*/
|
|
92
|
-
export function interpolateHex(color1, color2, factor) {
|
|
93
|
-
const f = Math.max(0, Math.min(1, factor));
|
|
94
|
-
const c1 = hexToRgb(color1);
|
|
95
|
-
const c2 = hexToRgb(color2);
|
|
96
|
-
const r = c1.r + f * (c2.r - c1.r);
|
|
97
|
-
const g = c1.g + f * (c2.g - c1.g);
|
|
98
|
-
const b = c1.b + f * (c2.b - c1.b);
|
|
99
|
-
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Public interpolation function that accepts any Color type.
|
|
103
|
-
*/
|
|
104
|
-
export function interpolateColor(color1, color2, factor) {
|
|
105
|
-
return interpolateHex(colorToHex(color1), colorToHex(color2), factor);
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* logic to get a specific color from a multi-stop gradient array at a specific factor (0-1).
|
|
109
|
-
*/
|
|
110
|
-
export function getGradientColor(colors, factor) {
|
|
111
|
-
if (colors.length === 0)
|
|
112
|
-
return "";
|
|
113
|
-
if (colors.length === 1)
|
|
114
|
-
return resolveColorToAnsi(colors[0]);
|
|
115
|
-
const f = Math.max(0, Math.min(1, factor));
|
|
116
|
-
// Map factor to segments between colors
|
|
117
|
-
// e.g. 3 colors: [0, 0.5, 1]. factor 0.25 is in first segment (0.5 of way through)
|
|
118
|
-
const segmentLength = 1 / (colors.length - 1);
|
|
119
|
-
const segmentIndex = Math.min(Math.floor(f / segmentLength), colors.length - 2);
|
|
120
|
-
const segmentFactor = (f - segmentIndex * segmentLength) / segmentLength;
|
|
121
|
-
const c1 = colors[segmentIndex];
|
|
122
|
-
const c2 = colors[segmentIndex + 1];
|
|
123
|
-
const hex = interpolateColor(c1, c2, segmentFactor);
|
|
124
|
-
return resolveColorToAnsi(hex);
|
|
125
|
-
}
|
|
126
|
-
// -----------------
|
|
127
|
-
// Style Merging
|
|
128
|
-
// -----------------
|
|
129
|
-
/**
|
|
130
|
-
* Merges a child style into a parent style.
|
|
131
|
-
* - Modifiers are combined (union).
|
|
132
|
-
* - Child color overrides parent color.
|
|
133
|
-
*/
|
|
134
|
-
export function mergeStyles(parent, child) {
|
|
135
|
-
if (!parent && !child)
|
|
136
|
-
return {};
|
|
137
|
-
if (!parent)
|
|
138
|
-
return child ?? {};
|
|
139
|
-
if (!child)
|
|
140
|
-
return parent;
|
|
141
|
-
const mergedModifiers = Array.from(new Set([...(parent.modifiers ?? []), ...(child.modifiers ?? [])]));
|
|
142
|
-
return {
|
|
143
|
-
modifiers: mergedModifiers,
|
|
144
|
-
color: child.color ?? parent.color
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Resolves a style object into an ANSI string.
|
|
149
|
-
* If the color is a gradient (array), it uses the provided factor (0-1) to pick the color.
|
|
150
|
-
* Defaults to factor 0 if not provided.
|
|
151
|
-
*/
|
|
152
|
-
export function resolveStyle(style, gradientFactor = 0) {
|
|
153
|
-
if (!style)
|
|
154
|
-
return "";
|
|
155
|
-
let ansi = "";
|
|
156
|
-
if (style.modifiers) {
|
|
157
|
-
ansi += resolveModifiersToAnsi(style.modifiers);
|
|
158
|
-
}
|
|
159
|
-
if (style.color) {
|
|
160
|
-
if (Array.isArray(style.color)) {
|
|
161
|
-
// Gradient
|
|
162
|
-
const hex = getGradientColor(style.color, gradientFactor);
|
|
163
|
-
ansi += hex;
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
// Solid
|
|
167
|
-
ansi += resolveColorToAnsi(style.color);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return ansi;
|
|
171
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/core/style.test.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { expect, test, describe } from "bun:test";
|
|
2
|
-
import { hexToRgb, rgbToAnsi, interpolateColor, resolveStyle, resolveColorToAnsi, resolveModifiersToAnsi, getGradientColor, mergeStyles } from "./style";
|
|
3
|
-
const ESC = "\x1b";
|
|
4
|
-
describe("Color Utilities", () => {
|
|
5
|
-
test("hexToRgb converts correctly", () => {
|
|
6
|
-
expect(hexToRgb("#FFFFFF")).toEqual({ r: 255, g: 255, b: 255 });
|
|
7
|
-
expect(hexToRgb("#000000")).toEqual({ r: 0, g: 0, b: 0 });
|
|
8
|
-
});
|
|
9
|
-
test("hexToRgb handles invalid hex gracefully", () => {
|
|
10
|
-
expect(hexToRgb("invalid")).toEqual({ r: 255, g: 255, b: 255 }); // Fallback
|
|
11
|
-
expect(hexToRgb("#FFF")).toEqual({ r: 255, g: 255, b: 255 }); // Fallback
|
|
12
|
-
});
|
|
13
|
-
test("rgbToAnsi converts correctly", () => {
|
|
14
|
-
expect(rgbToAnsi(255, 255, 255)).toBe(`${ESC}[38;2;255;255;255m`);
|
|
15
|
-
expect(rgbToAnsi(0, 0, 0)).toBe(`${ESC}[38;2;0;0;0m`);
|
|
16
|
-
});
|
|
17
|
-
test("resolveColorToAnsi converts hex and standard colors", () => {
|
|
18
|
-
expect(resolveColorToAnsi("#FFFFFF")).toBe(`${ESC}[38;2;255;255;255m`);
|
|
19
|
-
expect(resolveColorToAnsi("red")).toBe(`${ESC}[38;2;239;68;68m`); // Updated to Tailwind value
|
|
20
|
-
});
|
|
21
|
-
test("interpolateColor returns hex string for hex inputs", () => {
|
|
22
|
-
const start = "#000000";
|
|
23
|
-
const end = "#ffffff";
|
|
24
|
-
expect(interpolateColor(start, end, 0.5)).toBe("#808080");
|
|
25
|
-
});
|
|
26
|
-
test("interpolateColor handles standard color names", () => {
|
|
27
|
-
expect(interpolateColor("black", "white", 0.5)).toBe("#808080");
|
|
28
|
-
});
|
|
29
|
-
test("interpolateColor clamps factors", () => {
|
|
30
|
-
expect(interpolateColor("#000000", "#ffffff", -1)).toBe("#000000");
|
|
31
|
-
expect(interpolateColor("#000000", "#ffffff", 2)).toBe("#ffffff");
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
describe("Style Resolution", () => {
|
|
35
|
-
test("resolveModifiersToAnsi handles modifiers", () => {
|
|
36
|
-
expect(resolveModifiersToAnsi(["bold"])).toBe(`${ESC}[1m`);
|
|
37
|
-
expect(resolveModifiersToAnsi(["bold", "italic"])).toBe(`${ESC}[1m${ESC}[3m`);
|
|
38
|
-
expect(resolveModifiersToAnsi([])).toBe("");
|
|
39
|
-
});
|
|
40
|
-
test("resolveStyle handles modifiers only", () => {
|
|
41
|
-
expect(resolveStyle({ modifiers: ["bold"] })).toBe(`${ESC}[1m`);
|
|
42
|
-
});
|
|
43
|
-
test("resolveStyle handles solid color", () => {
|
|
44
|
-
const style = { color: "#FF0000" };
|
|
45
|
-
expect(resolveStyle(style)).toBe(`${ESC}[38;2;255;0;0m`);
|
|
46
|
-
});
|
|
47
|
-
test("resolveStyle handles modifiers and solid color", () => {
|
|
48
|
-
const style = { modifiers: ["bold"], color: "#FF0000" };
|
|
49
|
-
expect(resolveStyle(style)).toBe(`${ESC}[1m${ESC}[38;2;255;0;0m`);
|
|
50
|
-
});
|
|
51
|
-
test("resolveStyle handles gradient (uses factor 0 by default)", () => {
|
|
52
|
-
const colors = ["#000000", "#FFFFFF"];
|
|
53
|
-
const style = { color: colors };
|
|
54
|
-
expect(resolveStyle(style)).toBe(`${ESC}[38;2;0;0;0m`); // Factor 0 is black
|
|
55
|
-
});
|
|
56
|
-
test("resolveStyle handles gradient with explicit factor", () => {
|
|
57
|
-
const colors = ["#000000", "#FFFFFF"];
|
|
58
|
-
const style = { color: colors };
|
|
59
|
-
expect(resolveStyle(style, 1)).toBe(`${ESC}[38;2;255;255;255m`); // Factor 1 is white
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
describe("Gradient Utilities", () => {
|
|
63
|
-
test("getGradientColor handles 2 colors", () => {
|
|
64
|
-
const colors = ["#000000", "#FFFFFF"];
|
|
65
|
-
expect(getGradientColor(colors, 0)).toBe(`${ESC}[38;2;0;0;0m`);
|
|
66
|
-
expect(getGradientColor(colors, 0.5)).toBe(`${ESC}[38;2;128;128;128m`);
|
|
67
|
-
expect(getGradientColor(colors, 1)).toBe(`${ESC}[38;2;255;255;255m`);
|
|
68
|
-
});
|
|
69
|
-
test("getGradientColor handles 3 colors", () => {
|
|
70
|
-
const colors = ["#000000", "#808080", "#FFFFFF"];
|
|
71
|
-
// 0 -> black
|
|
72
|
-
expect(getGradientColor(colors, 0)).toBe(`${ESC}[38;2;0;0;0m`);
|
|
73
|
-
// 0.5 -> middle color (gray)
|
|
74
|
-
expect(getGradientColor(colors, 0.5)).toBe(`${ESC}[38;2;128;128;128m`);
|
|
75
|
-
// 1 -> white
|
|
76
|
-
expect(getGradientColor(colors, 1)).toBe(`${ESC}[38;2;255;255;255m`);
|
|
77
|
-
// 0.25 -> between black and gray
|
|
78
|
-
expect(getGradientColor(colors, 0.25)).toBe(`${ESC}[38;2;64;64;64m`);
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
describe("Style Merging", () => {
|
|
82
|
-
test("mergeStyles combines modifiers", () => {
|
|
83
|
-
const p = { modifiers: ["bold"] };
|
|
84
|
-
const c = { modifiers: ["italic"] };
|
|
85
|
-
const merged = mergeStyles(p, c);
|
|
86
|
-
expect(merged.modifiers).toContain("bold");
|
|
87
|
-
expect(merged.modifiers).toContain("italic");
|
|
88
|
-
});
|
|
89
|
-
test("mergeStyles overrides color (Child wins)", () => {
|
|
90
|
-
const p = { color: "red" };
|
|
91
|
-
const c = { color: "blue" };
|
|
92
|
-
const merged = mergeStyles(p, c);
|
|
93
|
-
expect(merged.color).toBe("blue");
|
|
94
|
-
});
|
|
95
|
-
test("mergeStyles inherits parent color if child has none", () => {
|
|
96
|
-
const p = { color: "red" };
|
|
97
|
-
const c = { modifiers: ["bold"] };
|
|
98
|
-
const merged = mergeStyles(p, c);
|
|
99
|
-
expect(merged.color).toBe("red");
|
|
100
|
-
expect(merged.modifiers).toContain("bold");
|
|
101
|
-
});
|
|
102
|
-
test("mergeStyles handles null/undefined inputs", () => {
|
|
103
|
-
const p = { color: "red" };
|
|
104
|
-
expect(mergeStyles(p, undefined)).toEqual(p);
|
|
105
|
-
expect(mergeStyles(undefined, p)).toEqual(p);
|
|
106
|
-
expect(mergeStyles(undefined, undefined)).toEqual({});
|
|
107
|
-
});
|
|
108
|
-
});
|
package/dist/core/types.d.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Standard colors supported by most terminals.
|
|
3
|
-
*/
|
|
4
|
-
export type StandardColor = "black" | "red" | "green" | "yellow" | "blue" | "magenta" | "cyan" | "white" | "gray" | "grey";
|
|
5
|
-
/**
|
|
6
|
-
* Text style modifiers.
|
|
7
|
-
*/
|
|
8
|
-
export type StyleModifier = "bold" | "dim" | "italic" | "underline" | "default" | "hidden" | "inverse" | "strikethrough";
|
|
9
|
-
/**
|
|
10
|
-
* A valid Hex color string (e.g., "#FF0000").
|
|
11
|
-
*/
|
|
12
|
-
export type HexColor = `#${string}`;
|
|
13
|
-
/**
|
|
14
|
-
* A color can be a standard color name or a hex color string.
|
|
15
|
-
*/
|
|
16
|
-
export type Color = StandardColor | HexColor;
|
|
17
|
-
/**
|
|
18
|
-
* Represents the style configuration for a text segment, line, or block.
|
|
19
|
-
*/
|
|
20
|
-
export interface PrintStyle {
|
|
21
|
-
/**
|
|
22
|
-
* The color to apply.
|
|
23
|
-
* - A single color (string) applies a solid color.
|
|
24
|
-
* - An array of colors applies a gradient.
|
|
25
|
-
*/
|
|
26
|
-
color?: Color | Color[];
|
|
27
|
-
/**
|
|
28
|
-
* A list of style modifiers (e.g., bold, italic) to apply.
|
|
29
|
-
*/
|
|
30
|
-
modifiers?: StyleModifier[];
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Represents a segment of text with applied styles.
|
|
34
|
-
*/
|
|
35
|
-
export interface PrintSegment {
|
|
36
|
-
/** The text content of the segment. */
|
|
37
|
-
text: string;
|
|
38
|
-
/** The style specific to this segment. Merges with parent styles. */
|
|
39
|
-
style?: PrintStyle;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Represents a line of text composed of multiple styled segments.
|
|
43
|
-
*/
|
|
44
|
-
export interface PrintLine {
|
|
45
|
-
/** Array of segments that make up the line. */
|
|
46
|
-
segments: PrintSegment[];
|
|
47
|
-
/** The style specific to this line. Merges with parent block style and applies to all children. */
|
|
48
|
-
style?: PrintStyle;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Represents a block of lines managed by the printer.
|
|
52
|
-
*/
|
|
53
|
-
export interface PrintBlock {
|
|
54
|
-
/** Array of lines that make up the block. */
|
|
55
|
-
lines: PrintLine[];
|
|
56
|
-
/** The style specific to this block. Applies to all children. */
|
|
57
|
-
style?: PrintStyle;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Configuration for the Printer engine.
|
|
61
|
-
*/
|
|
62
|
-
export interface PrinterOptions {
|
|
63
|
-
/**
|
|
64
|
-
* If true, the printer will overwrite previous output on subsequent print calls.
|
|
65
|
-
* Useful for live-updating displays (spinners, progress bars).
|
|
66
|
-
*/
|
|
67
|
-
live?: boolean;
|
|
68
|
-
/**
|
|
69
|
-
* Initial data to load into the printer.
|
|
70
|
-
*/
|
|
71
|
-
data?: PrintBlock;
|
|
72
|
-
}
|
package/dist/core/types.js
DELETED