cli-menu-kit 0.1.22 → 0.1.26

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.
@@ -11,7 +11,83 @@ const terminal_js_1 = require("../../core/terminal.js");
11
11
  const colors_js_1 = require("../../core/colors.js");
12
12
  const terminal_js_2 = require("../../core/terminal.js");
13
13
  /**
14
- * Wrap text to fit within a specific width
14
+ * Wrap text to fit within a specific width, preserving ANSI color codes
15
+ */
16
+ function wrapTextWithColors(text, maxWidth) {
17
+ // Extract ANSI codes and plain text
18
+ const ansiRegex = /\x1b\[[0-9;]*m/g;
19
+ const plainText = text.replace(ansiRegex, '');
20
+ // If plain text fits, return as-is
21
+ if (plainText.length <= maxWidth) {
22
+ return [text];
23
+ }
24
+ // Find all ANSI codes and their positions in the original text
25
+ const codes = [];
26
+ let match;
27
+ let offset = 0;
28
+ const textCopy = text;
29
+ while ((match = ansiRegex.exec(textCopy)) !== null) {
30
+ // Calculate position in plain text
31
+ const plainPos = match.index - offset;
32
+ codes.push({ pos: plainPos, code: match[0] });
33
+ offset += match[0].length;
34
+ }
35
+ // Wrap plain text by words
36
+ const words = plainText.split(' ');
37
+ const lines = [];
38
+ let currentLine = '';
39
+ let currentPos = 0;
40
+ for (const word of words) {
41
+ const testLine = currentLine ? `${currentLine} ${word}` : word;
42
+ if (testLine.length <= maxWidth) {
43
+ currentLine = testLine;
44
+ }
45
+ else {
46
+ if (currentLine) {
47
+ lines.push(currentLine);
48
+ currentPos += currentLine.length + 1; // +1 for space
49
+ }
50
+ currentLine = word;
51
+ }
52
+ }
53
+ if (currentLine) {
54
+ lines.push(currentLine);
55
+ }
56
+ // Re-insert ANSI codes into wrapped lines
57
+ const result = [];
58
+ let lineStart = 0;
59
+ let activeColor = '';
60
+ for (const line of lines) {
61
+ const lineEnd = lineStart + line.length;
62
+ let coloredLine = activeColor; // Start with active color from previous line
63
+ // Find codes that apply to this line
64
+ let lastPos = 0;
65
+ for (const { pos, code } of codes) {
66
+ if (pos >= lineStart && pos < lineEnd) {
67
+ const relPos = pos - lineStart;
68
+ coloredLine += line.substring(lastPos, relPos) + code;
69
+ lastPos = relPos;
70
+ // Track active color (reset or new color)
71
+ if (code === '\x1b[0m') {
72
+ activeColor = '';
73
+ }
74
+ else {
75
+ activeColor = code;
76
+ }
77
+ }
78
+ }
79
+ coloredLine += line.substring(lastPos);
80
+ // Add reset at end if there's an active color
81
+ if (activeColor && activeColor !== '\x1b[0m') {
82
+ coloredLine += '\x1b[0m';
83
+ }
84
+ result.push(coloredLine);
85
+ lineStart = lineEnd + 1; // +1 for space between words
86
+ }
87
+ return result;
88
+ }
89
+ /**
90
+ * Wrap text to fit within a specific width (plain text only)
15
91
  */
16
92
  function wrapText(text, maxWidth) {
17
93
  const words = text.split(' ');
@@ -39,7 +115,16 @@ function wrapText(text, maxWidth) {
39
115
  * @param config - Summary table configuration
40
116
  */
41
117
  function renderSummaryTable(config) {
42
- const { title, titleAlign = 'center', sections, width } = config;
118
+ const { title, titleAlign = 'center', sections, width, colors: colorConfig } = config;
119
+ // Default colors
120
+ const defaultColors = {
121
+ title: 'cyan+bold',
122
+ sectionHeader: '', // No color (default/black)
123
+ key: 'cyan',
124
+ value: '' // No color (default/black)
125
+ };
126
+ // Merge with provided colors
127
+ const finalColors = { ...defaultColors, ...colorConfig };
43
128
  const termWidth = (0, terminal_js_2.getTerminalWidth)();
44
129
  // Use full terminal width minus padding, or specified width
45
130
  const boxWidth = width || Math.max(60, termWidth - 4);
@@ -51,53 +136,68 @@ function renderSummaryTable(config) {
51
136
  (0, terminal_js_1.writeLine)(`│${' '.repeat(boxWidth - 2)}│`);
52
137
  // Title if provided
53
138
  if (title) {
139
+ // Parse color configuration (supports "color" or "color+style" format)
140
+ let titleColor = '';
141
+ if (finalColors.title) {
142
+ const parts = finalColors.title.split('+');
143
+ titleColor = parts.map(part => colors_js_1.colors[part.trim()] || '').join('');
144
+ }
145
+ const resetColor = titleColor ? colors_js_1.colors.reset : '';
54
146
  let titleLine;
55
147
  let remainingSpace;
56
148
  if (titleAlign === 'left') {
57
- titleLine = ` ${colors_js_1.colors.cyan}${title}${colors_js_1.colors.reset}`;
149
+ titleLine = ` ${titleColor}${title}${resetColor}`;
58
150
  const plainTitle = title;
59
151
  remainingSpace = boxWidth - plainTitle.length - 4;
60
152
  }
61
153
  else if (titleAlign === 'right') {
62
154
  const plainTitle = title;
63
155
  const rightPadding = contentWidth - plainTitle.length;
64
- titleLine = ' '.repeat(rightPadding + 2) + colors_js_1.colors.cyan + title + colors_js_1.colors.reset;
156
+ titleLine = ' '.repeat(rightPadding + 2) + titleColor + title + resetColor;
65
157
  remainingSpace = 2;
66
158
  }
67
159
  else {
68
160
  // center (default)
69
161
  const titlePadding = Math.floor((contentWidth - title.length) / 2);
70
- titleLine = ' '.repeat(titlePadding + 2) + colors_js_1.colors.cyan + title + colors_js_1.colors.reset;
162
+ titleLine = ' '.repeat(titlePadding + 2) + titleColor + title + resetColor;
71
163
  remainingSpace = boxWidth - titlePadding - title.length - 4;
72
164
  }
73
165
  (0, terminal_js_1.writeLine)(`│${titleLine}${' '.repeat(remainingSpace)}│`);
74
166
  (0, terminal_js_1.writeLine)(`│${' '.repeat(boxWidth - 2)}│`);
75
167
  }
168
+ // Calculate global keyPadding based on longest key across ALL sections
169
+ const allKeys = sections.flatMap(section => section.items.map(item => item.key));
170
+ const maxKeyLength = Math.max(...allKeys.map(key => key.length));
171
+ const keyPadding = maxKeyLength + 3; // +3 for colon and spacing
76
172
  // Sections
77
173
  sections.forEach((section, sectionIndex) => {
78
174
  // Section header if provided
79
175
  if (section.header) {
80
- const headerLine = ` ${colors_js_1.colors.brightCyan}${section.header}${colors_js_1.colors.reset}`;
176
+ const headerColor = finalColors.sectionHeader ? colors_js_1.colors[finalColors.sectionHeader] || '' : '';
177
+ const resetColor = headerColor ? colors_js_1.colors.reset : '';
178
+ const headerLine = ` ${headerColor}${section.header}${resetColor}`;
81
179
  const remainingSpace = boxWidth - section.header.length - 4;
82
180
  (0, terminal_js_1.writeLine)(`│${headerLine}${' '.repeat(remainingSpace)}│`);
83
181
  }
84
- // Section items
182
+ // Section items (using global keyPadding)
85
183
  section.items.forEach(item => {
86
- const keyPadding = 15;
87
184
  const valueMaxWidth = contentWidth - keyPadding - 2; // 2 for leading spaces
88
185
  // Check if value needs wrapping
89
186
  const plainValue = item.value.replace(/\x1b\[[0-9;]*m/g, '');
90
187
  if (plainValue.length > valueMaxWidth) {
91
- // Wrap the value
92
- const wrappedLines = wrapText(plainValue, valueMaxWidth);
188
+ // Wrap the value (preserving colors if present)
189
+ const wrappedLines = wrapTextWithColors(item.value, valueMaxWidth);
93
190
  // First line with key
94
- const firstLine = ` ${item.key}:${' '.repeat(Math.max(1, keyPadding - item.key.length))}${wrappedLines[0]}`;
191
+ const keyColor = finalColors.key ? colors_js_1.colors[finalColors.key] || '' : '';
192
+ const keyResetColor = keyColor ? colors_js_1.colors.reset : '';
193
+ // wrappedLines already contain colors, don't add valueColor
194
+ const firstLine = ` ${keyColor}${item.key}:${keyResetColor}${' '.repeat(Math.max(1, keyPadding - item.key.length))}${wrappedLines[0]}`;
95
195
  const plainFirstLine = firstLine.replace(/\x1b\[[0-9;]*m/g, '');
96
196
  const remainingSpace = boxWidth - plainFirstLine.length - 2;
97
197
  (0, terminal_js_1.writeLine)(`│${firstLine}${' '.repeat(Math.max(0, remainingSpace))}│`);
98
198
  // Subsequent lines with indentation
99
199
  for (let i = 1; i < wrappedLines.length; i++) {
100
- const continuationLine = ` ${' '.repeat(keyPadding)}${wrappedLines[i]}`;
200
+ const continuationLine = ` ${' '.repeat(keyPadding + 1)}${wrappedLines[i]}`;
101
201
  const plainContinuationLine = continuationLine.replace(/\x1b\[[0-9;]*m/g, '');
102
202
  const contRemainingSpace = boxWidth - plainContinuationLine.length - 2;
103
203
  (0, terminal_js_1.writeLine)(`│${continuationLine}${' '.repeat(Math.max(0, contRemainingSpace))}│`);
@@ -105,7 +205,13 @@ function renderSummaryTable(config) {
105
205
  }
106
206
  else {
107
207
  // No wrapping needed
108
- const itemLine = ` ${item.key}:${' '.repeat(Math.max(1, keyPadding - item.key.length))}${item.value}`;
208
+ const keyColor = finalColors.key ? colors_js_1.colors[finalColors.key] || '' : '';
209
+ const keyResetColor = keyColor ? colors_js_1.colors.reset : '';
210
+ const valueColor = finalColors.value ? colors_js_1.colors[finalColors.value] || '' : '';
211
+ const valueResetColor = valueColor ? colors_js_1.colors.reset : '';
212
+ // Only wrap value with color if valueColor is set, otherwise preserve original colors in item.value
213
+ const coloredValue = valueColor ? `${valueColor}${item.value}${valueResetColor}` : item.value;
214
+ const itemLine = ` ${keyColor}${item.key}:${keyResetColor}${' '.repeat(Math.max(1, keyPadding - item.key.length))}${coloredValue}`;
109
215
  const plainItemLine = itemLine.replace(/\x1b\[[0-9;]*m/g, '');
110
216
  const remainingSpace = boxWidth - plainItemLine.length - 2;
111
217
  (0, terminal_js_1.writeLine)(`│${itemLine}${' '.repeat(Math.max(0, remainingSpace))}│`);
@@ -55,4 +55,10 @@ export interface SummaryTableConfig {
55
55
  }>;
56
56
  }>;
57
57
  width?: number;
58
+ colors?: {
59
+ title?: string;
60
+ sectionHeader?: string;
61
+ key?: string;
62
+ value?: string;
63
+ };
58
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cli-menu-kit",
3
- "version": "0.1.22",
3
+ "version": "0.1.26",
4
4
  "description": "A lightweight, customizable CLI menu system with keyboard shortcuts and real-time rendering",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",