@heinrichb/console-toolkit 1.0.8 → 1.0.9

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.
@@ -0,0 +1,75 @@
1
+ import { PrintLine, PrintStyle } from "../core/types";
2
+ export interface ProgressBarOptions {
3
+ /**
4
+ * Progress value between 0.0 and 1.0.
5
+ */
6
+ progress: number;
7
+ /**
8
+ * Width of the progress bar (excluding brackets and percentage).
9
+ * Defaults to 20.
10
+ */
11
+ width?: number;
12
+ /**
13
+ * Base style for the entire progress bar.
14
+ */
15
+ style?: PrintStyle;
16
+ /**
17
+ * Style for the brackets (start and end characters).
18
+ * Defaults to `style`.
19
+ */
20
+ bracketStyle?: PrintStyle;
21
+ /**
22
+ * Specific style for the start bracket. Overrides `bracketStyle`.
23
+ */
24
+ startStyle?: PrintStyle;
25
+ /**
26
+ * Specific style for the end bracket. Overrides `bracketStyle`.
27
+ */
28
+ endStyle?: PrintStyle;
29
+ /**
30
+ * Style for the bar (filled and empty parts).
31
+ * Defaults to `style`.
32
+ */
33
+ barStyle?: PrintStyle;
34
+ /**
35
+ * Specific style for the filled part. Overrides `barStyle`.
36
+ */
37
+ fillStyle?: PrintStyle;
38
+ /**
39
+ * Specific style for the empty part. Overrides `barStyle`.
40
+ */
41
+ emptyStyle?: PrintStyle;
42
+ /**
43
+ * Style for the percentage text.
44
+ * Defaults to `style`.
45
+ */
46
+ percentageStyle?: PrintStyle;
47
+ /**
48
+ * Character to use for the start bracket. Defaults to `[`.
49
+ */
50
+ startChar?: string;
51
+ /**
52
+ * Character to use for the end bracket. Defaults to `]`.
53
+ */
54
+ endChar?: string;
55
+ /**
56
+ * Character to use for the filled part. Defaults to `█`.
57
+ */
58
+ fillChar?: string;
59
+ /**
60
+ * Character to use for the empty part. Defaults to `░`.
61
+ */
62
+ emptyChar?: string;
63
+ /**
64
+ * Whether to show the percentage text. Defaults to `true`.
65
+ */
66
+ showPercentage?: boolean;
67
+ /**
68
+ * Custom formatter for the percentage text.
69
+ */
70
+ formatPercentage?: (progress: number) => string;
71
+ }
72
+ /**
73
+ * Creates a PrintLine representing a progress bar.
74
+ */
75
+ export declare function createProgressBar(options: ProgressBarOptions): PrintLine;
@@ -0,0 +1,36 @@
1
+ export type SpinnerFrames = string[];
2
+ export interface SpinnerOptions {
3
+ /**
4
+ * The array of frames to cycle through.
5
+ */
6
+ frames: SpinnerFrames;
7
+ /**
8
+ * The interval in milliseconds between frames.
9
+ * Defaults to 80ms.
10
+ */
11
+ interval?: number;
12
+ }
13
+ /**
14
+ * Common spinner frame presets.
15
+ */
16
+ export declare const SPINNERS: {
17
+ dots: string[];
18
+ lines: string[];
19
+ arrows: string[];
20
+ circle: string[];
21
+ square: string[];
22
+ };
23
+ /**
24
+ * A stateful spinner that calculates the current frame based on elapsed time.
25
+ * Designed to be used within a render loop (e.g. Printer.print loop).
26
+ */
27
+ export declare class Spinner {
28
+ private frames;
29
+ private interval;
30
+ private startTime;
31
+ constructor(options: SpinnerOptions);
32
+ /**
33
+ * Returns the current frame based on the elapsed time.
34
+ */
35
+ getFrame(): string;
36
+ }
@@ -0,0 +1,25 @@
1
+ import { PrintLine, PrintStyle } from "./types";
2
+ import { Printer } from "./printer";
3
+ /**
4
+ * Merges multiple columns of PrintLines into a single layout.
5
+ * Ensures proper alignment by padding shorter lines.
6
+ *
7
+ * @param columns - Array of columns, where each column is an array of PrintLines.
8
+ * @param separator - String used to separate columns.
9
+ * @param defaultStyle - Style to apply to the separator and padding.
10
+ * @param widths - Optional fixed widths for each column.
11
+ * @returns A single array of PrintLines representing the merged output.
12
+ */
13
+ export declare function mergeColumns(columns: PrintLine[][], separator?: string, defaultStyle?: PrintStyle, widths?: number[]): PrintLine[];
14
+ /**
15
+ * Prints multiple columns of styled content to the console.
16
+ * A convenience wrapper around `mergeColumns` and `Printer.print`.
17
+ *
18
+ * @param columns - Array of columns to print.
19
+ * @param options - Layout options (widths, separator, custom printer).
20
+ */
21
+ export declare function printColumns(columns: PrintLine[][], options?: {
22
+ widths?: number[];
23
+ separator?: string;
24
+ printer?: Printer;
25
+ }): void;
@@ -0,0 +1,33 @@
1
+ import { PrintBlock, PrinterOptions } from "./types";
2
+ /**
3
+ * Handles rendering PrintBlocks to the terminal with support for interactive/live overwriting.
4
+ */
5
+ export declare class Printer {
6
+ private linesRendered;
7
+ private isLive;
8
+ private data?;
9
+ constructor(options?: PrinterOptions);
10
+ /**
11
+ * Generates the clear sequence to move cursor and clear previously rendered lines.
12
+ */
13
+ private getClearSequence;
14
+ /**
15
+ * Clears the console using the stored line count.
16
+ */
17
+ clear(): void;
18
+ /**
19
+ * Renders the PrintBlock to the standard output.
20
+ * If data is provided, updates the internal state.
21
+ *
22
+ * @param data - Optional data to update the printer with.
23
+ */
24
+ print(data?: PrintBlock): void;
25
+ /**
26
+ * Resolves the block's vertical gradient (if any) to a solid color for the specific line.
27
+ */
28
+ private resolveBlockColorForLine;
29
+ /**
30
+ * Renders a single line.
31
+ */
32
+ private renderLine;
33
+ }
@@ -0,0 +1,53 @@
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;
@@ -0,0 +1,72 @@
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
+ }
@@ -0,0 +1,25 @@
1
+ import { PrintLine, PrintStyle } from "./types";
2
+ /**
3
+ * Gets the plain text length of a PrintLine (ignoring ANSI codes).
4
+ *
5
+ * @param line - The PrintLine to measure.
6
+ * @returns The length of the text content.
7
+ */
8
+ export declare function getLineLength(line: PrintLine): number;
9
+ /**
10
+ * Computes the maximum width among an array of PrintLines.
11
+ * Useful for aligning columns.
12
+ *
13
+ * @param lines - Array of PrintLines.
14
+ * @returns The maximum line length found.
15
+ */
16
+ export declare function computeMaxWidth(lines: PrintLine[]): number;
17
+ /**
18
+ * Pads a PrintLine to a target width by adding an empty segment at the end.
19
+ *
20
+ * @param line - The line to pad.
21
+ * @param targetWidth - The desired minimum width.
22
+ * @param padStyle - The style to apply to the padding spaces.
23
+ * @returns A new PrintLine with padding added if necessary.
24
+ */
25
+ export declare function padLine(line: PrintLine, targetWidth: number, padStyle?: PrintStyle): PrintLine;
@@ -0,0 +1,8 @@
1
+ export * from "./core/types";
2
+ export * from "./core/style";
3
+ export * from "./core/utils";
4
+ export * from "./core/printer";
5
+ export * from "./core/layout";
6
+ export * from "./components/progress";
7
+ export * from "./components/spinner";
8
+ export * from "./presets/ascii";
package/dist/index.js ADDED
@@ -0,0 +1,385 @@
1
+ // src/core/style.ts
2
+ var ESC = "\x1B";
3
+ var RESET = `${ESC}[0m`;
4
+ var STANDARD_COLORS = {
5
+ black: "#000000",
6
+ red: "#EF4444",
7
+ green: "#10B981",
8
+ yellow: "#F59E0B",
9
+ blue: "#3B82F6",
10
+ magenta: "#EC4899",
11
+ cyan: "#06B6D4",
12
+ white: "#FFFFFF",
13
+ gray: "#6B7280",
14
+ grey: "#6B7280"
15
+ };
16
+ var MODIFIER_CODES = {
17
+ default: "0",
18
+ bold: "1",
19
+ dim: "2",
20
+ italic: "3",
21
+ underline: "4",
22
+ inverse: "7",
23
+ hidden: "8",
24
+ strikethrough: "9"
25
+ };
26
+ function colorToHex(color) {
27
+ if (color.startsWith("#"))
28
+ return color;
29
+ const hex = STANDARD_COLORS[color.toLowerCase()];
30
+ if (!hex)
31
+ return "#FFFFFF";
32
+ return hex;
33
+ }
34
+ function hexToRgb(hex) {
35
+ const h = hex.replace(/^#/, "");
36
+ if (h.length !== 6)
37
+ return { r: 255, g: 255, b: 255 };
38
+ return {
39
+ r: parseInt(h.substring(0, 2), 16),
40
+ g: parseInt(h.substring(2, 4), 16),
41
+ b: parseInt(h.substring(4, 6), 16)
42
+ };
43
+ }
44
+ function rgbToAnsi(r, g, b) {
45
+ return `${ESC}[38;2;${r};${g};${b}m`;
46
+ }
47
+ function resolveColorToAnsi(color) {
48
+ const hex = colorToHex(color);
49
+ const { r, g, b } = hexToRgb(hex);
50
+ return rgbToAnsi(r, g, b);
51
+ }
52
+ function resolveModifiersToAnsi(modifiers) {
53
+ if (!modifiers || modifiers.length === 0)
54
+ return "";
55
+ return modifiers.map((m) => {
56
+ const code = MODIFIER_CODES[m];
57
+ return code ? `${ESC}[${code}m` : "";
58
+ }).join("");
59
+ }
60
+ function toHex(c) {
61
+ const hex = Math.max(0, Math.min(255, Math.round(c))).toString(16);
62
+ return hex.length === 1 ? "0" + hex : hex;
63
+ }
64
+ function interpolateHex(color1, color2, factor) {
65
+ const f = Math.max(0, Math.min(1, factor));
66
+ const c1 = hexToRgb(color1);
67
+ const c2 = hexToRgb(color2);
68
+ const r = c1.r + f * (c2.r - c1.r);
69
+ const g = c1.g + f * (c2.g - c1.g);
70
+ const b = c1.b + f * (c2.b - c1.b);
71
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
72
+ }
73
+ function interpolateColor(color1, color2, factor) {
74
+ return interpolateHex(colorToHex(color1), colorToHex(color2), factor);
75
+ }
76
+ function getGradientColor(colors, factor) {
77
+ if (colors.length === 0)
78
+ return "";
79
+ if (colors.length === 1)
80
+ return resolveColorToAnsi(colors[0]);
81
+ const f = Math.max(0, Math.min(1, factor));
82
+ const segmentLength = 1 / (colors.length - 1);
83
+ const segmentIndex = Math.min(Math.floor(f / segmentLength), colors.length - 2);
84
+ const segmentFactor = (f - segmentIndex * segmentLength) / segmentLength;
85
+ const c1 = colors[segmentIndex];
86
+ const c2 = colors[segmentIndex + 1];
87
+ const hex = interpolateColor(c1, c2, segmentFactor);
88
+ return resolveColorToAnsi(hex);
89
+ }
90
+ function mergeStyles(parent, child) {
91
+ if (!parent && !child)
92
+ return {};
93
+ if (!parent)
94
+ return child ?? {};
95
+ if (!child)
96
+ return parent;
97
+ const mergedModifiers = Array.from(new Set([...parent.modifiers ?? [], ...child.modifiers ?? []]));
98
+ return {
99
+ modifiers: mergedModifiers,
100
+ color: child.color ?? parent.color
101
+ };
102
+ }
103
+ function resolveStyle(style, gradientFactor = 0) {
104
+ if (!style)
105
+ return "";
106
+ let ansi = "";
107
+ if (style.modifiers) {
108
+ ansi += resolveModifiersToAnsi(style.modifiers);
109
+ }
110
+ if (style.color) {
111
+ if (Array.isArray(style.color)) {
112
+ const hex = getGradientColor(style.color, gradientFactor);
113
+ ansi += hex;
114
+ } else {
115
+ ansi += resolveColorToAnsi(style.color);
116
+ }
117
+ }
118
+ return ansi;
119
+ }
120
+ // src/core/utils.ts
121
+ function getLineLength(line) {
122
+ return line.segments.reduce((acc, seg) => acc + seg.text.length, 0);
123
+ }
124
+ function computeMaxWidth(lines) {
125
+ return lines.length > 0 ? Math.max(...lines.map(getLineLength)) : 0;
126
+ }
127
+ function padLine(line, targetWidth, padStyle) {
128
+ const currentLength = getLineLength(line);
129
+ if (currentLength < targetWidth) {
130
+ return {
131
+ segments: [...line.segments, { text: " ".repeat(targetWidth - currentLength), style: padStyle }]
132
+ };
133
+ }
134
+ return line;
135
+ }
136
+ // src/core/printer.ts
137
+ var ESC2 = "\x1B";
138
+
139
+ class Printer {
140
+ linesRendered = 0;
141
+ isLive;
142
+ data;
143
+ constructor(options = {}) {
144
+ this.isLive = options.live ?? false;
145
+ this.data = options.data;
146
+ }
147
+ getClearSequence() {
148
+ if (!this.isLive || this.linesRendered === 0)
149
+ return "";
150
+ return `${ESC2}[1A${ESC2}[2K\r`.repeat(this.linesRendered);
151
+ }
152
+ clear() {
153
+ if (this.linesRendered > 0) {
154
+ process.stdout.write(this.getClearSequence());
155
+ this.linesRendered = 0;
156
+ }
157
+ }
158
+ print(data) {
159
+ if (data) {
160
+ this.data = data;
161
+ }
162
+ if (!this.data) {
163
+ return;
164
+ }
165
+ let output = this.getClearSequence();
166
+ const lines = this.data.lines;
167
+ const blockStyle = this.data.style ?? {};
168
+ lines.forEach((line, lineIndex) => {
169
+ output += this.renderLine(line, lineIndex, lines.length, blockStyle);
170
+ output += `
171
+ `;
172
+ });
173
+ process.stdout.write(output);
174
+ this.linesRendered = lines.length;
175
+ }
176
+ resolveBlockColorForLine(blockStyle, lineIndex, totalLines) {
177
+ if (!blockStyle.color)
178
+ return;
179
+ if (Array.isArray(blockStyle.color)) {
180
+ if (totalLines <= 1)
181
+ return blockStyle.color[0];
182
+ const colors = blockStyle.color;
183
+ const factor = lineIndex / (totalLines - 1);
184
+ const f = Math.max(0, Math.min(1, factor));
185
+ const segmentLength = 1 / (colors.length - 1);
186
+ const segmentIndex = Math.min(Math.floor(f / segmentLength), colors.length - 2);
187
+ const segmentFactor = (f - segmentIndex * segmentLength) / segmentLength;
188
+ const c1 = colors[segmentIndex];
189
+ const c2 = colors[segmentIndex + 1];
190
+ return interpolateColor(c1, c2, segmentFactor);
191
+ }
192
+ return blockStyle.color;
193
+ }
194
+ renderLine(line, lineIndex, totalLines, parentBlockStyle) {
195
+ const blockColorForLine = this.resolveBlockColorForLine(parentBlockStyle, lineIndex, totalLines);
196
+ const baseLineStyle = {
197
+ modifiers: parentBlockStyle.modifiers,
198
+ color: blockColorForLine
199
+ };
200
+ const effectiveLineStyle = mergeStyles(baseLineStyle, line.style);
201
+ const totalChars = line.segments.reduce((acc, seg) => acc + seg.text.length, 0);
202
+ let currentCharIndex = 0;
203
+ let lineOutput = "";
204
+ line.segments.forEach((seg) => {
205
+ const effectiveSegmentStyle = mergeStyles(effectiveLineStyle, seg.style);
206
+ if (Array.isArray(effectiveSegmentStyle.color)) {
207
+ const colors = effectiveSegmentStyle.color;
208
+ const text = seg.text;
209
+ const isGlobalGradient = effectiveSegmentStyle.color === effectiveLineStyle.color;
210
+ const modifiersAnsi = resolveModifiersToAnsi(effectiveSegmentStyle.modifiers);
211
+ for (let i = 0;i < text.length; i++) {
212
+ let factor = 0;
213
+ if (isGlobalGradient && totalChars > 1) {
214
+ factor = (currentCharIndex + i) / (totalChars - 1);
215
+ } else if (!isGlobalGradient && text.length > 1) {
216
+ factor = i / (text.length - 1);
217
+ }
218
+ const colorAnsi = getGradientColor(colors, factor);
219
+ lineOutput += `${modifiersAnsi}${colorAnsi}${text[i]}`;
220
+ }
221
+ lineOutput += RESET;
222
+ } else {
223
+ const ansi = resolveStyle(effectiveSegmentStyle);
224
+ lineOutput += `${ansi}${seg.text}${RESET}`;
225
+ }
226
+ currentCharIndex += seg.text.length;
227
+ });
228
+ return lineOutput;
229
+ }
230
+ }
231
+ // src/core/layout.ts
232
+ function mergeColumns(columns, separator = " ", defaultStyle, widths) {
233
+ if (columns.length === 0)
234
+ return [];
235
+ const maxLines = Math.max(...columns.map((c) => c.length));
236
+ const colWidths = columns.map((col, i) => {
237
+ if (widths?.[i] !== undefined)
238
+ return widths[i];
239
+ return computeMaxWidth(col);
240
+ });
241
+ const output = [];
242
+ for (let i = 0;i < maxLines; i++) {
243
+ let segments = [];
244
+ for (let j = 0;j < columns.length; j++) {
245
+ const line = columns[j][i] || { segments: [] };
246
+ if (j < columns.length - 1) {
247
+ const padded = padLine(line, colWidths[j], defaultStyle);
248
+ segments = [...segments, ...padded.segments, { text: separator, style: defaultStyle }];
249
+ } else {
250
+ segments = [...segments, ...line.segments];
251
+ }
252
+ }
253
+ output.push({ segments });
254
+ }
255
+ return output;
256
+ }
257
+ function printColumns(columns, options = {}) {
258
+ const { widths, separator = " ", printer = new Printer } = options;
259
+ const mergedLines = mergeColumns(columns, separator, undefined, widths);
260
+ printer.print({ lines: mergedLines });
261
+ }
262
+ // src/components/progress.ts
263
+ function createProgressBar(options) {
264
+ const {
265
+ progress,
266
+ width = 20,
267
+ style,
268
+ bracketStyle,
269
+ startStyle,
270
+ endStyle,
271
+ barStyle,
272
+ fillStyle,
273
+ emptyStyle,
274
+ percentageStyle,
275
+ startChar = "[",
276
+ endChar = "]",
277
+ fillChar = "█",
278
+ emptyChar = "░",
279
+ showPercentage = true,
280
+ formatPercentage
281
+ } = options;
282
+ const p = Math.max(0, Math.min(1, progress));
283
+ const filledWidth = Math.round(p * width);
284
+ const emptyWidth = width - filledWidth;
285
+ const resolvedBracketStyle = bracketStyle ?? style;
286
+ const resolvedStartStyle = startStyle ?? resolvedBracketStyle;
287
+ const resolvedEndStyle = endStyle ?? resolvedBracketStyle;
288
+ const resolvedBarStyle = barStyle ?? style;
289
+ const resolvedFillStyle = fillStyle ?? resolvedBarStyle;
290
+ const resolvedEmptyStyle = emptyStyle ?? resolvedBarStyle;
291
+ const resolvedPercentageStyle = percentageStyle ?? style;
292
+ const segments = [];
293
+ if (startChar) {
294
+ segments.push({ text: startChar, style: resolvedStartStyle });
295
+ }
296
+ if (filledWidth > 0) {
297
+ segments.push({ text: fillChar.repeat(filledWidth), style: resolvedFillStyle });
298
+ }
299
+ if (emptyWidth > 0) {
300
+ segments.push({ text: emptyChar.repeat(emptyWidth), style: resolvedEmptyStyle });
301
+ }
302
+ if (endChar) {
303
+ segments.push({ text: endChar, style: resolvedEndStyle });
304
+ }
305
+ if (showPercentage) {
306
+ const percentageText = formatPercentage ? formatPercentage(p) : ` ${Math.round(p * 100)}%`;
307
+ segments.push({ text: percentageText, style: resolvedPercentageStyle });
308
+ }
309
+ return { segments };
310
+ }
311
+ // src/components/spinner.ts
312
+ var SPINNERS = {
313
+ dots: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
314
+ lines: ["-", "\\", "|", "/"],
315
+ arrows: ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"],
316
+ circle: ["◐", "◓", "◑", "◒"],
317
+ square: ["▖", "▘", "▝", "▗"]
318
+ };
319
+
320
+ class Spinner {
321
+ frames;
322
+ interval;
323
+ startTime;
324
+ constructor(options) {
325
+ this.frames = options.frames;
326
+ this.interval = options.interval ?? 80;
327
+ this.startTime = Date.now();
328
+ }
329
+ getFrame() {
330
+ const now = Date.now();
331
+ const elapsed = now - this.startTime;
332
+ const index = Math.floor(elapsed / this.interval) % this.frames.length;
333
+ return this.frames[index];
334
+ }
335
+ }
336
+ // src/presets/ascii.ts
337
+ function getDragon(startColor = "#EF4444", endColor = "#F59E0B") {
338
+ const rawDragon = [
339
+ " ^ ^",
340
+ " / \\ //\\",
341
+ " |\\___/| / \\// .\\",
342
+ " /O O \\__ / // | \\ \\",
343
+ "/ / \\_/_/ // | \\ \\",
344
+ "@___@' \\_// // | \\ \\ ",
345
+ " | \\_// // | \\ \\ ",
346
+ " | \\/// | \\ \\ ",
347
+ " _|_ / ) // | \\ _\\",
348
+ " '/,_ _ _/ ( ; -. | _ _\\.-~ .-~~~^-.",
349
+ " ,-{ _ `-.|.-~-. .~ `.",
350
+ " '/\\ / ~-. _ .-~ .-~^-. \\",
351
+ " `. { } / \\ \\",
352
+ " .----~-\\. \\-' .~ \\ `. \\^-.",
353
+ " ///.----..> c \\ _ -~ `. ^-` ^-_",
354
+ " ///-._ _ _ _ _ _ _}^ - - - - ~ ~--, .-~",
355
+ " /.-'"
356
+ ];
357
+ return rawDragon.map((text, i) => {
358
+ const factor = rawDragon.length <= 1 ? 0 : i / (rawDragon.length - 1);
359
+ const colorStyle = interpolateColor(startColor, endColor, factor);
360
+ return { segments: [{ text, style: { color: colorStyle } }] };
361
+ });
362
+ }
363
+ export {
364
+ rgbToAnsi,
365
+ resolveStyle,
366
+ resolveModifiersToAnsi,
367
+ resolveColorToAnsi,
368
+ printColumns,
369
+ padLine,
370
+ mergeStyles,
371
+ mergeColumns,
372
+ interpolateHex,
373
+ interpolateColor,
374
+ hexToRgb,
375
+ getLineLength,
376
+ getGradientColor,
377
+ getDragon,
378
+ createProgressBar,
379
+ computeMaxWidth,
380
+ colorToHex,
381
+ Spinner,
382
+ SPINNERS,
383
+ RESET,
384
+ Printer
385
+ };
@@ -0,0 +1,2 @@
1
+ var A="\x1B",T=`${A}[0m`,a={black:"#000000",red:"#EF4444",green:"#10B981",yellow:"#F59E0B",blue:"#3B82F6",magenta:"#EC4899",cyan:"#06B6D4",white:"#FFFFFF",gray:"#6B7280",grey:"#6B7280"},i={default:"0",bold:"1",dim:"2",italic:"3",underline:"4",inverse:"7",hidden:"8",strikethrough:"9"};function W(q){if(q.startsWith("#"))return q;let z=a[q.toLowerCase()];if(!z)return"#FFFFFF";return z}function E(q){let z=q.replace(/^#/,"");if(z.length!==6)return{r:255,g:255,b:255};return{r:parseInt(z.substring(0,2),16),g:parseInt(z.substring(2,4),16),b:parseInt(z.substring(4,6),16)}}function s(q,z,J){return`${A}[38;2;${q};${z};${J}m`}function P(q){let z=W(q),{r:J,g:K,b:Q}=E(z);return s(J,K,Q)}function w(q){if(!q||q.length===0)return"";return q.map((z)=>{let J=i[z];return J?`${A}[${J}m`:""}).join("")}function R(q){let z=Math.max(0,Math.min(255,Math.round(q))).toString(16);return z.length===1?"0"+z:z}function r(q,z,J){let K=Math.max(0,Math.min(1,J)),Q=E(q),U=E(z),Y=Q.r+K*(U.r-Q.r),V=Q.g+K*(U.g-Q.g),X=Q.b+K*(U.b-Q.b);return`#${R(Y)}${R(V)}${R(X)}`}function O(q,z,J){return r(W(q),W(z),J)}function I(q,z){if(q.length===0)return"";if(q.length===1)return P(q[0]);let J=Math.max(0,Math.min(1,z)),K=1/(q.length-1),Q=Math.min(Math.floor(J/K),q.length-2),U=(J-Q*K)/K,Y=q[Q],V=q[Q+1],X=O(Y,V,U);return P(X)}function k(q,z){if(!q&&!z)return{};if(!q)return z??{};if(!z)return q;return{modifiers:Array.from(new Set([...q.modifiers??[],...z.modifiers??[]])),color:z.color??q.color}}function C(q,z=0){if(!q)return"";let J="";if(q.modifiers)J+=w(q.modifiers);if(q.color)if(Array.isArray(q.color)){let K=I(q.color,z);J+=K}else J+=P(q.color);return J}function f(q){return q.segments.reduce((z,J)=>z+J.text.length,0)}function u(q){return q.length>0?Math.max(...q.map(f)):0}function S(q,z,J){let K=f(q);if(K<z)return{segments:[...q.segments,{text:" ".repeat(z-K),style:J}]};return q}var m="\x1B";class v{linesRendered=0;isLive;data;constructor(q={}){this.isLive=q.live??!1,this.data=q.data}getClearSequence(){if(!this.isLive||this.linesRendered===0)return"";return`${m}[1A${m}[2K\r`.repeat(this.linesRendered)}clear(){if(this.linesRendered>0)process.stdout.write(this.getClearSequence()),this.linesRendered=0}print(q){if(q)this.data=q;if(!this.data)return;let z=this.getClearSequence(),J=this.data.lines,K=this.data.style??{};J.forEach((Q,U)=>{z+=this.renderLine(Q,U,J.length,K),z+=`
2
+ `}),process.stdout.write(z),this.linesRendered=J.length}resolveBlockColorForLine(q,z,J){if(!q.color)return;if(Array.isArray(q.color)){if(J<=1)return q.color[0];let K=q.color,Q=z/(J-1),U=Math.max(0,Math.min(1,Q)),Y=1/(K.length-1),V=Math.min(Math.floor(U/Y),K.length-2),X=(U-V*Y)/Y,Z=K[V],$=K[V+1];return O(Z,$,X)}return q.color}renderLine(q,z,J,K){let Q=this.resolveBlockColorForLine(K,z,J),U={modifiers:K.modifiers,color:Q},Y=k(U,q.style),V=q.segments.reduce(($,_)=>$+_.text.length,0),X=0,Z="";return q.segments.forEach(($)=>{let _=k(Y,$.style);if(Array.isArray(_.color)){let j=_.color,G=$.text,D=_.color===Y.color,B=w(_.modifiers);for(let N=0;N<G.length;N++){let M=0;if(D&&V>1)M=(X+N)/(V-1);else if(!D&&G.length>1)M=N/(G.length-1);let H=I(j,M);Z+=`${B}${H}${G[N]}`}Z+=T}else{let j=C(_);Z+=`${j}${$.text}${T}`}X+=$.text.length}),Z}}function n(q,z=" ",J,K){if(q.length===0)return[];let Q=Math.max(...q.map((V)=>V.length)),U=q.map((V,X)=>{if(K?.[X]!==void 0)return K[X];return u(V)}),Y=[];for(let V=0;V<Q;V++){let X=[];for(let Z=0;Z<q.length;Z++){let $=q[Z][V]||{segments:[]};if(Z<q.length-1){let _=S($,U[Z],J);X=[...X,..._.segments,{text:z,style:J}]}else X=[...X,...$.segments]}Y.push({segments:X})}return Y}function Kq(q,z={}){let{widths:J,separator:K=" ",printer:Q=new v}=z,U=n(q,K,void 0,J);Q.print({lines:U})}function Uq(q){let{progress:z,width:J=20,style:K,bracketStyle:Q,startStyle:U,endStyle:Y,barStyle:V,fillStyle:X,emptyStyle:Z,percentageStyle:$,startChar:_="[",endChar:j="]",fillChar:G="█",emptyChar:D="░",showPercentage:B=!0,formatPercentage:N}=q,M=Math.max(0,Math.min(1,z)),H=Math.round(M*J),b=J-H,L=Q??K,h=U??L,p=Y??L,x=V??K,y=X??x,g=Z??x,d=$??K,F=[];if(_)F.push({text:_,style:h});if(H>0)F.push({text:G.repeat(H),style:y});if(b>0)F.push({text:D.repeat(b),style:g});if(j)F.push({text:j,style:p});if(B){let c=N?N(M):` ${Math.round(M*100)}%`;F.push({text:c,style:d})}return{segments:F}}var Xq={dots:["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"],lines:["-","\\","|","/"],arrows:["←","↖","↑","↗","→","↘","↓","↙"],circle:["◐","◓","◑","◒"],square:["▖","▘","▝","▗"]};class l{frames;interval;startTime;constructor(q){this.frames=q.frames,this.interval=q.interval??80,this.startTime=Date.now()}getFrame(){let z=Date.now()-this.startTime,J=Math.floor(z/this.interval)%this.frames.length;return this.frames[J]}}function $q(q="#EF4444",z="#F59E0B"){let J=[" ^ ^"," / \\ //\\"," |\\___/| / \\// .\\"," /O O \\__ / // | \\ \\","/ / \\_/_/ // | \\ \\","@___@' \\_// // | \\ \\ "," | \\_// // | \\ \\ "," | \\/// | \\ \\ "," _|_ / ) // | \\ _\\"," '/,_ _ _/ ( ; -. | _ _\\.-~ .-~~~^-."," ,-{ _ `-.|.-~-. .~ `."," '/\\ / ~-. _ .-~ .-~^-. \\"," `. { } / \\ \\"," .----~-\\. \\-' .~ \\ `. \\^-."," ///.----..> c \\ _ -~ `. ^-` ^-_"," ///-._ _ _ _ _ _ _}^ - - - - ~ ~--, .-~"," /.-'"];return J.map((K,Q)=>{let U=J.length<=1?0:Q/(J.length-1),Y=O(q,z,U);return{segments:[{text:K,style:{color:Y}}]}})}export{s as rgbToAnsi,C as resolveStyle,w as resolveModifiersToAnsi,P as resolveColorToAnsi,Kq as printColumns,S as padLine,k as mergeStyles,n as mergeColumns,r as interpolateHex,O as interpolateColor,E as hexToRgb,f as getLineLength,I as getGradientColor,$q as getDragon,Uq as createProgressBar,u as computeMaxWidth,W as colorToHex,l as Spinner,Xq as SPINNERS,T as RESET,v as Printer};
@@ -0,0 +1,5 @@
1
+ import { PrintLine, Color } from "../core/types";
2
+ /**
3
+ * Returns the classic Dragon ASCII art as PrintLines with a vertical color gradient.
4
+ */
5
+ export declare function getDragon(startColor?: Color, endColor?: Color): PrintLine[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heinrichb/console-toolkit",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "A versatile TypeScript utility library for enhanced console logging, formatting, and layout management.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,6 +17,7 @@
17
17
  "test": "bun test src",
18
18
  "test:coverage": "bun test src --coverage --coverage-threshold=100",
19
19
  "build": "bun run scripts/build.ts",
20
+ "publish": "bun run build && bun publish --tag latest",
20
21
  "typecheck": "bun x tsc --noEmit",
21
22
  "format:check": "prettier --check \"src/**/*.{ts,json,md}\"",
22
23
  "format:write": "prettier --write \"src/**/*.{ts,json,md}\"",