console-toolkit 1.2.15 → 1.3.0

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.
Files changed (87) hide show
  1. package/README.md +3 -0
  2. package/llms-full.txt +64 -4
  3. package/package.json +6 -6
  4. package/src/alphanumeric/arrows.js +0 -23
  5. package/src/alphanumeric/fractions.js +0 -49
  6. package/src/alphanumeric/number-formatters.js +0 -44
  7. package/src/alphanumeric/roman.js +0 -12
  8. package/src/alphanumeric/unicode-cultural-numbers.js +0 -1
  9. package/src/alphanumeric/unicode-letters.js +0 -8
  10. package/src/alphanumeric/unicode-numbers.js +0 -21
  11. package/src/alphanumeric/utils.js +0 -26
  12. package/src/ansi/csi.js +0 -49
  13. package/src/ansi/sgr-state.js +7 -50
  14. package/src/ansi/sgr.js +0 -420
  15. package/src/box.d.ts +4 -1
  16. package/src/box.js +15 -115
  17. package/src/charts/bars/block-frac-grouped.js +0 -6
  18. package/src/charts/bars/block-frac.js +1 -14
  19. package/src/charts/bars/block-grouped.js +0 -6
  20. package/src/charts/bars/block.js +1 -14
  21. package/src/charts/bars/draw-grouped.js +0 -4
  22. package/src/charts/bars/draw-stacked.js +0 -4
  23. package/src/charts/bars/frac-grouped.js +2 -14
  24. package/src/charts/bars/plain-grouped.js +0 -6
  25. package/src/charts/bars/plain.js +1 -28
  26. package/src/charts/columns/block-frac-grouped.js +0 -6
  27. package/src/charts/columns/block-frac.js +0 -13
  28. package/src/charts/columns/block-grouped.js +0 -6
  29. package/src/charts/columns/block.js +0 -13
  30. package/src/charts/columns/draw-grouped.js +1 -5
  31. package/src/charts/columns/draw-stacked.js +0 -4
  32. package/src/charts/columns/frac-grouped.js +1 -13
  33. package/src/charts/columns/plain-grouped.js +0 -6
  34. package/src/charts/columns/plain.js +0 -13
  35. package/src/charts/themes/default.js +0 -1
  36. package/src/charts/themes/rainbow-reversed.js +0 -1
  37. package/src/charts/themes/rainbow.js +0 -1
  38. package/src/charts/utils.js +2 -28
  39. package/src/draw-block-frac.js +0 -14
  40. package/src/draw-block.js +0 -24
  41. package/src/meta.js +0 -64
  42. package/src/output/show.d.ts +8 -3
  43. package/src/output/show.js +7 -38
  44. package/src/output/updater.d.ts +8 -3
  45. package/src/output/updater.js +4 -51
  46. package/src/output/writer.js +1 -53
  47. package/src/panel.d.ts +16 -10
  48. package/src/panel.js +21 -276
  49. package/src/plot/bitmap.js +5 -33
  50. package/src/plot/draw-line.js +0 -8
  51. package/src/plot/draw-rect.js +25 -103
  52. package/src/plot/index.js +0 -22
  53. package/src/plot/to-quads.js +3 -6
  54. package/src/spinner/spin.js +23 -20
  55. package/src/spinner/spinner.d.ts +16 -2
  56. package/src/spinner/spinner.js +22 -34
  57. package/src/spinner/spinners.js +0 -16
  58. package/src/strings/clip.js +0 -10
  59. package/src/strings/parse.js +0 -7
  60. package/src/strings/split.js +0 -15
  61. package/src/strings.d.ts +2 -0
  62. package/src/strings.js +2 -32
  63. package/src/style.js +2 -58
  64. package/src/symbols.js +0 -84
  65. package/src/table/draw-borders.js +0 -9
  66. package/src/table/index.d.ts +1 -1
  67. package/src/table/index.js +0 -1
  68. package/src/table/table.js +3 -58
  69. package/src/themes/blocks/unicode-half.js +0 -1
  70. package/src/themes/blocks/unicode-thin.js +0 -1
  71. package/src/themes/lines/ascii-compact.js +5 -9
  72. package/src/themes/lines/ascii-dots.js +2 -7
  73. package/src/themes/lines/ascii-girder.js +4 -7
  74. package/src/themes/lines/ascii-github.js +5 -9
  75. package/src/themes/lines/ascii-reddit.js +5 -9
  76. package/src/themes/lines/ascii-rounded.js +5 -9
  77. package/src/themes/lines/ascii.js +5 -9
  78. package/src/themes/lines/unicode-bold.js +8 -14
  79. package/src/themes/lines/unicode-rounded.js +8 -14
  80. package/src/themes/lines/unicode.js +8 -14
  81. package/src/themes/utils.d.ts +6 -0
  82. package/src/themes/utils.js +6 -7
  83. package/src/turtle/draw-line-art.js +26 -18
  84. package/src/turtle/draw-unicode.js +0 -8
  85. package/src/turtle/index.d.ts +1 -1
  86. package/src/turtle/index.js +0 -8
  87. package/src/turtle/turtle.js +0 -120
@@ -2,16 +2,7 @@ import Box from '../box.js';
2
2
  import {addAlias} from '../meta.js';
3
3
  import {fullBlock} from '../symbols.js';
4
4
 
5
- /** A bitmap image using a compact bit-packed representation.
6
- * Supports setting/getting individual bits and converting to a Box for console display.
7
- */
8
5
  export class Bitmap {
9
- /** Creates a new Bitmap.
10
- * @param {number} width - The width in pixels.
11
- * @param {number} height - The height in pixels.
12
- * @param {number} [blockWidth=5] - The width of each internal block (blockWidth * blockHeight <= 32).
13
- * @param {number} [blockHeight=5] - The height of each internal block.
14
- */
15
6
  constructor(width, height, blockWidth = 5, blockHeight = 5) {
16
7
  // parameters check
17
8
  width = Math.round(width);
@@ -58,11 +49,6 @@ export class Bitmap {
58
49
  return 1 << (shiftX + this.blockWidth * shiftY);
59
50
  }
60
51
 
61
- /** Gets the value of a bit at the given position.
62
- * @param {number} x - The x coordinate.
63
- * @param {number} y - The y coordinate.
64
- * @returns {number} Non-zero if the bit is set, 0 otherwise.
65
- */
66
52
  getBit(x, y) {
67
53
  if (x < 0 || x >= this.width || y < 0 || y >= this.height) return 0;
68
54
  const index = this.getWordIndex(x, y),
@@ -70,12 +56,6 @@ export class Bitmap {
70
56
  return this.bitmap[index] & mask;
71
57
  }
72
58
 
73
- /** Sets or clears a bit at the given position.
74
- * @param {number} x - The x coordinate.
75
- * @param {number} y - The y coordinate.
76
- * @param {number} [value=1] - Positive to set, 0 to clear, negative to toggle.
77
- * @returns {this}
78
- */
79
59
  setBit(x, y, value = 1) {
80
60
  if (x < 0 || x >= this.width || y < 0 || y >= this.height) return this;
81
61
  const index = this.getWordIndex(x, y),
@@ -85,10 +65,6 @@ export class Bitmap {
85
65
  return this;
86
66
  }
87
67
 
88
- /** Clears the entire bitmap.
89
- * @param {boolean} [value=false] - If true, sets all bits; if false, clears all bits.
90
- * @returns {this}
91
- */
92
68
  clear(value) {
93
69
  this.bitmap.fill(value ? ~0 : 0);
94
70
  return this;
@@ -105,27 +81,23 @@ export class Bitmap {
105
81
  // return result;
106
82
  // }
107
83
 
108
- /** Converts the bitmap to a Box for console display.
109
- * @param {string} [one=fullBlock] - Character for set bits.
110
- * @param {string} [zero=' '] - Character for unset bits.
111
- * @returns {import('../box.js').Box} A Box representation of the bitmap.
112
- */
113
84
  toBox(one = fullBlock, zero = ' ') {
114
- const result = [];
85
+ const result = [],
86
+ chars = new Array(this.width);
115
87
  for (let k = 0, kBase = 0; k < this.lineCount; ++k, kBase += this.blockHeight) {
116
88
  const iLimit = Math.min(this.blockHeight, this.height - kBase);
117
89
  for (let i = 0; i < iLimit; ++i) {
118
- let row = '';
90
+ let pos = 0;
119
91
  for (let j = 0, jBase = 0; j < this.lineSize; ++j, jBase += this.blockWidth) {
120
92
  const index = k * this.lineSize + j,
121
93
  word = this.bitmap[index],
122
94
  mLimit = Math.min(this.blockWidth, this.width - jBase);
123
95
  let mask = 1 << (this.blockWidth * i);
124
96
  for (let m = 0; m < mLimit; ++m, mask <<= 1) {
125
- row += word & mask ? one : zero;
97
+ chars[pos++] = word & mask ? one : zero;
126
98
  }
127
99
  }
128
- result.push(row);
100
+ result.push(chars.join(''));
129
101
  }
130
102
  }
131
103
  return new Box(result, true);
@@ -1,14 +1,6 @@
1
1
  // Drawing lines using Bresenham's line algorithm
2
2
  // The canonic version from https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
3
3
 
4
- /** Draws a line on a Bitmap using Bresenham's line algorithm.
5
- * @param {import('./bitmap.js').Bitmap} bmp - The bitmap to draw on.
6
- * @param {number} x0 - Start x coordinate.
7
- * @param {number} y0 - Start y coordinate.
8
- * @param {number} x1 - End x coordinate.
9
- * @param {number} y1 - End y coordinate.
10
- * @param {number} [value=1] - Bit value: positive to set, 0 to clear, negative to toggle.
11
- */
12
4
  export const drawLine = (bmp, x0, y0, x1, y1, value = 1) => {
13
5
  const dx = Math.abs(x1 - x0),
14
6
  sx = x0 < x1 ? 1 : -1,
@@ -1,15 +1,16 @@
1
- /** Draws a filled rectangle on a Bitmap by efficiently manipulating the underlying bit array.
2
- * @param {import('./bitmap.js').Bitmap} bmp - The bitmap to draw on.
3
- * @param {number} x0 - First corner x coordinate.
4
- * @param {number} y0 - First corner y coordinate.
5
- * @param {number} x1 - Opposite corner x coordinate.
6
- * @param {number} y1 - Opposite corner y coordinate.
7
- * @param {number} [value=1] - Bit value: positive to set, 0 to clear, negative to toggle.
8
- */
9
1
  export const drawRect = (bmp, x0, y0, x1, y1, value = 1) => {
10
2
  if (x1 < x0) [x0, x1] = [x1, x0];
11
3
  if (y1 < y0) [y0, y1] = [y1, y0];
12
4
 
5
+ // hoist op-mode once: + = set (OR), - = toggle (XOR), 0 = clear (AND~)
6
+ const apply =
7
+ value > 0
8
+ ? (current, mask) => current | mask
9
+ : value < 0
10
+ ? (current, mask) => current ^ mask
11
+ : (current, mask) => current & ~mask;
12
+ const applyFull = value > 0 ? () => ~0 : value < 0 ? current => ~current : () => 0;
13
+
13
14
  let indexL = bmp.getWordIndex(x0, y0),
14
15
  indexR = bmp.getWordIndex(x1, y0);
15
16
 
@@ -30,12 +31,7 @@ export const drawRect = (bmp, x0, y0, x1, y1, value = 1) => {
30
31
  for (let i = 1; i < height; ++i) fullMask = (fullMask << bmp.blockWidth) | mask;
31
32
  fullMask <<= firstRowShift;
32
33
 
33
- bmp.bitmap[indexL] =
34
- value > 0
35
- ? bmp.bitmap[indexL] | fullMask
36
- : value < 0
37
- ? bmp.bitmap[indexL] ^ fullMask
38
- : bmp.bitmap[indexL] & ~fullMask;
34
+ bmp.bitmap[indexL] = apply(bmp.bitmap[indexL], fullMask);
39
35
  return;
40
36
  }
41
37
 
@@ -56,25 +52,9 @@ export const drawRect = (bmp, x0, y0, x1, y1, value = 1) => {
56
52
  fullMaskR <<= firstRowShift;
57
53
 
58
54
  // apply masks
59
- bmp.bitmap[indexL] =
60
- value > 0
61
- ? bmp.bitmap[indexL] | fullMaskL
62
- : value < 0
63
- ? bmp.bitmap[indexL] ^ fullMaskL
64
- : bmp.bitmap[indexL] & ~fullMaskL;
65
- for (let index = indexL + 1; index < indexR; ++index)
66
- bmp.bitmap[index] =
67
- value > 0
68
- ? bmp.bitmap[index] | fullMaskM
69
- : value < 0
70
- ? bmp.bitmap[index] ^ fullMaskM
71
- : bmp.bitmap[index] & ~fullMaskM;
72
- bmp.bitmap[indexR] =
73
- value > 0
74
- ? bmp.bitmap[indexR] | fullMaskR
75
- : value < 0
76
- ? bmp.bitmap[indexR] ^ fullMaskR
77
- : bmp.bitmap[indexR] & ~fullMaskR;
55
+ bmp.bitmap[indexL] = apply(bmp.bitmap[indexL], fullMaskL);
56
+ for (let index = indexL + 1; index < indexR; ++index) bmp.bitmap[index] = apply(bmp.bitmap[index], fullMaskM);
57
+ bmp.bitmap[indexR] = apply(bmp.bitmap[indexR], fullMaskR);
78
58
  return;
79
59
  }
80
60
 
@@ -90,12 +70,7 @@ export const drawRect = (bmp, x0, y0, x1, y1, value = 1) => {
90
70
  let fullMask = mask;
91
71
  for (let i = 1, n = bmp.blockHeight - firstRows; i < n; ++i) fullMask = (fullMask << bmp.blockWidth) | mask;
92
72
  fullMask <<= firstRowShift;
93
- bmp.bitmap[indexL] =
94
- value > 0
95
- ? bmp.bitmap[indexL] | fullMask
96
- : value < 0
97
- ? bmp.bitmap[indexL] ^ fullMask
98
- : bmp.bitmap[indexL] & ~fullMask;
73
+ bmp.bitmap[indexL] = apply(bmp.bitmap[indexL], fullMask);
99
74
  indexL += bmp.lineSize;
100
75
  }
101
76
  if (fullLines) {
@@ -104,23 +79,13 @@ export const drawRect = (bmp, x0, y0, x1, y1, value = 1) => {
104
79
  for (let i = 1; i < bmp.blockHeight; ++i) fullMask = (fullMask << bmp.blockWidth) | mask;
105
80
  // apply the mask to full blocks
106
81
  for (let i = 0; i < fullLines; ++i, indexL += bmp.lineSize)
107
- bmp.bitmap[indexL] =
108
- value > 0
109
- ? bmp.bitmap[indexL] | fullMask
110
- : value < 0
111
- ? bmp.bitmap[indexL] ^ fullMask
112
- : bmp.bitmap[indexL] & ~fullMask;
82
+ bmp.bitmap[indexL] = apply(bmp.bitmap[indexL], fullMask);
113
83
  }
114
84
  if (lastRows) {
115
85
  // apply the mask to the last incomplete block
116
86
  let fullMask = mask;
117
87
  for (let i = 1; i < lastRows; ++i) fullMask = (fullMask << bmp.blockWidth) | mask;
118
- bmp.bitmap[indexL] =
119
- value > 0
120
- ? bmp.bitmap[indexL] | fullMask
121
- : value < 0
122
- ? bmp.bitmap[indexL] ^ fullMask
123
- : bmp.bitmap[indexL] & ~fullMask;
88
+ bmp.bitmap[indexL] = apply(bmp.bitmap[indexL], fullMask);
124
89
  }
125
90
  return;
126
91
  }
@@ -140,25 +105,9 @@ export const drawRect = (bmp, x0, y0, x1, y1, value = 1) => {
140
105
  }
141
106
  fullMaskL <<= firstRowShift;
142
107
  fullMaskR <<= firstRowShift;
143
- bmp.bitmap[indexL] =
144
- value > 0
145
- ? bmp.bitmap[indexL] | fullMaskL
146
- : value < 0
147
- ? bmp.bitmap[indexL] ^ fullMaskL
148
- : bmp.bitmap[indexL] & ~fullMaskL;
149
- for (let index = indexL + 1; index < indexR; ++index)
150
- bmp.bitmap[index] =
151
- value > 0
152
- ? bmp.bitmap[index] | fullMaskM
153
- : value < 0
154
- ? bmp.bitmap[index] ^ fullMaskM
155
- : bmp.bitmap[index] & ~fullMaskM;
156
- bmp.bitmap[indexR] =
157
- value > 0
158
- ? bmp.bitmap[indexR] | fullMaskR
159
- : value < 0
160
- ? bmp.bitmap[indexR] ^ fullMaskR
161
- : bmp.bitmap[indexR] & ~fullMaskR;
108
+ bmp.bitmap[indexL] = apply(bmp.bitmap[indexL], fullMaskL);
109
+ for (let index = indexL + 1; index < indexR; ++index) bmp.bitmap[index] = apply(bmp.bitmap[index], fullMaskM);
110
+ bmp.bitmap[indexR] = apply(bmp.bitmap[indexR], fullMaskR);
162
111
  indexL += bmp.lineSize;
163
112
  indexR += bmp.lineSize;
164
113
  }
@@ -173,20 +122,9 @@ export const drawRect = (bmp, x0, y0, x1, y1, value = 1) => {
173
122
  }
174
123
  // apply masks
175
124
  for (let i = 0; i < fullLines; ++i, indexL += bmp.lineSize, indexR += bmp.lineSize) {
176
- bmp.bitmap[indexL] =
177
- value > 0
178
- ? bmp.bitmap[indexL] | fullMaskL
179
- : value < 0
180
- ? bmp.bitmap[indexL] ^ fullMaskL
181
- : bmp.bitmap[indexL] & ~fullMaskL;
182
- for (let index = indexL + 1; index < indexR; ++index)
183
- bmp.bitmap[index] = value > 0 ? ~0 : value < 0 ? ~bmp.bitmap[index] : 0;
184
- bmp.bitmap[indexR] =
185
- value > 0
186
- ? bmp.bitmap[indexR] | fullMaskR
187
- : value < 0
188
- ? bmp.bitmap[indexR] ^ fullMaskR
189
- : bmp.bitmap[indexR] & ~fullMaskR;
125
+ bmp.bitmap[indexL] = apply(bmp.bitmap[indexL], fullMaskL);
126
+ for (let index = indexL + 1; index < indexR; ++index) bmp.bitmap[index] = applyFull(bmp.bitmap[index]);
127
+ bmp.bitmap[indexR] = apply(bmp.bitmap[indexR], fullMaskR);
190
128
  }
191
129
  }
192
130
 
@@ -199,25 +137,9 @@ export const drawRect = (bmp, x0, y0, x1, y1, value = 1) => {
199
137
  fullMaskL = (fullMaskL << bmp.blockWidth) | maskL;
200
138
  fullMaskR = (fullMaskR << bmp.blockWidth) | maskR;
201
139
  }
202
- bmp.bitmap[indexL] =
203
- value > 0
204
- ? bmp.bitmap[indexL] | fullMaskL
205
- : value < 0
206
- ? bmp.bitmap[indexL] ^ fullMaskL
207
- : bmp.bitmap[indexL] & ~fullMaskL;
208
- for (let index = indexL + 1; index < indexR; ++index)
209
- bmp.bitmap[index] =
210
- value > 0
211
- ? bmp.bitmap[index] | fullMaskM
212
- : value < 0
213
- ? bmp.bitmap[index] ^ fullMaskM
214
- : bmp.bitmap[index] & ~fullMaskM;
215
- bmp.bitmap[indexR] =
216
- value > 0
217
- ? bmp.bitmap[indexR] | fullMaskR
218
- : value < 0
219
- ? bmp.bitmap[indexR] ^ fullMaskR
220
- : bmp.bitmap[indexR] & ~fullMaskR;
140
+ bmp.bitmap[indexL] = apply(bmp.bitmap[indexL], fullMaskL);
141
+ for (let index = indexL + 1; index < indexR; ++index) bmp.bitmap[index] = apply(bmp.bitmap[index], fullMaskM);
142
+ bmp.bitmap[indexR] = apply(bmp.bitmap[indexR], fullMaskR);
221
143
  }
222
144
  };
223
145
 
package/src/plot/index.js CHANGED
@@ -5,39 +5,17 @@ import toQuads from './to-quads.js';
5
5
 
6
6
  // patch Bitmap
7
7
 
8
- /** Draws a line on this bitmap.
9
- * @param {number} x0 - Start X.
10
- * @param {number} y0 - Start Y.
11
- * @param {number} x1 - End X.
12
- * @param {number} y1 - End Y.
13
- * @param {number} [value=1] - Bit value.
14
- * @returns {this}
15
- */
16
8
  Bitmap.prototype.line = function (...args) {
17
9
  drawLine(this, ...args);
18
10
  return this;
19
11
  };
20
- /** Draws a filled rectangle on this bitmap.
21
- * @param {number} x0 - First corner x coordinate.
22
- * @param {number} y0 - First corner y coordinate.
23
- * @param {number} x1 - Opposite corner x coordinate.
24
- * @param {number} y1 - Opposite corner y coordinate.
25
- * @param {number} [value=1] - Bit value.
26
- * @returns {this}
27
- */
28
12
  Bitmap.prototype.rect = function (...args) {
29
13
  drawRect(this, ...args);
30
14
  return this;
31
15
  };
32
- /** Converts this bitmap to a Box using quadrant characters.
33
- * @returns {import('../box.js').default}
34
- */
35
16
  Bitmap.prototype.toQuads = function () {
36
17
  return toQuads(this);
37
18
  };
38
- /** Converts this bitmap to a string array via `toBox()`.
39
- * @returns {string[]}
40
- */
41
19
  Bitmap.prototype.toStrings = function () {
42
20
  return this.toBox().toStrings();
43
21
  };
@@ -1,11 +1,6 @@
1
1
  import Box from '../box.js';
2
2
  import {quadrants} from '../symbols.js';
3
3
 
4
- /** Converts a Bitmap to a Box using Unicode quadrant characters (2x2 pixels per character).
5
- * Provides higher resolution representation than `toBox()`.
6
- * @param {import('./bitmap.js').Bitmap} bmp - The bitmap to convert.
7
- * @returns {import('../box.js').Box} A Box with quadrant characters.
8
- */
9
4
  export const toQuads = bmp => {
10
5
  const result = [],
11
6
  rowSize = Math.floor((bmp.width + 1) / 2),
@@ -23,7 +18,9 @@ export const toQuads = bmp => {
23
18
  }
24
19
  }
25
20
  if ((kBase + i) & 1) {
26
- result.push(accumulator.map(i => quadrants[i]).join(''));
21
+ let row = '';
22
+ for (const k of accumulator) row += quadrants[k];
23
+ result.push(row);
27
24
  if (kBase + i + 1 < bmp.height) accumulator.fill(0);
28
25
  }
29
26
  }
@@ -1,10 +1,6 @@
1
1
  import {SpinnerBase} from './spinner.js';
2
2
 
3
- /** Internal composite spinner that handles multiple spinner parts and functions via tagged template literals. */
4
3
  class Spinner extends SpinnerBase {
5
- /** @param {TemplateStringsArray} strings - Template literal strings.
6
- * @param {any[]} args - Interpolated values.
7
- */
8
4
  constructor(strings, args) {
9
5
  super(false);
10
6
  this.strings = strings;
@@ -12,9 +8,28 @@ class Spinner extends SpinnerBase {
12
8
  this.indices = new WeakMap();
13
9
  }
14
10
 
15
- /** Returns the current composite frame string.
16
- * @returns {string}
17
- */
11
+ tick() {
12
+ for (const arg of this.args) {
13
+ if (arg instanceof SpinnerBase) {
14
+ arg.active = this.active;
15
+ arg.paused = this.paused;
16
+ arg.finished = this.finished;
17
+ arg.tick();
18
+ continue;
19
+ }
20
+ if (Array.isArray(arg)) {
21
+ if (!this.indices.has(arg)) {
22
+ this.indices.set(arg, 0);
23
+ continue;
24
+ }
25
+ if (!this.finished && !this.paused && this.active) {
26
+ this.indices.set(arg, (this.indices.get(arg) + 1) % arg.length);
27
+ }
28
+ }
29
+ }
30
+ return this;
31
+ }
32
+
18
33
  getFrame() {
19
34
  let result = '';
20
35
  for (let i = 0; i < this.args.length; ++i) {
@@ -28,18 +43,12 @@ class Spinner extends SpinnerBase {
28
43
  }
29
44
 
30
45
  if (arg instanceof SpinnerBase) {
31
- arg.active = this.active;
32
- arg.paused = this.paused;
33
- arg.finished = this.finished;
34
46
  result += arg.getFrame();
35
47
  continue;
36
48
  }
37
49
 
38
50
  if (Array.isArray(arg)) {
39
- if (!this.indices.has(arg)) this.indices.set(arg, 0);
40
- const index = this.indices.get(arg);
41
- if (!this.finished && !this.paused && this.active) this.indices.set(arg, (index + 1) % arg.length);
42
- result += String(arg[index]);
51
+ result += String(arg[this.indices.get(arg) ?? 0]);
43
52
  continue;
44
53
  }
45
54
 
@@ -51,12 +60,6 @@ class Spinner extends SpinnerBase {
51
60
  }
52
61
  }
53
62
 
54
- /** Tagged template literal function that creates a composite spinner.
55
- * Arguments can be string arrays (cycled through), functions `(state) => string`, or SpinnerBase instances.
56
- * @param {TemplateStringsArray} strings - Template literal strings.
57
- * @param {...(string[]|Function|import('./spinner.js').SpinnerBase|*)} args - Interpolated values.
58
- * @returns {import('./spinner.js').SpinnerBase} A composite spinner.
59
- */
60
63
  const spin = (strings, ...args) => new Spinner(strings, args);
61
64
 
62
65
  export default spin;
@@ -31,10 +31,19 @@ export class SpinnerBase {
31
31
  * @returns The next frame index.
32
32
  */
33
33
  nextFrameIndex(length: number): number;
34
- /** Returns the current frame string.
34
+ /** Advances internal state by one step (state-aware: only advances when active+!paused or in finished/notStarted modes).
35
+ * Pure mutation — call `getFrame()` after to read the new frame.
36
+ * @returns This instance.
37
+ */
38
+ tick(): this;
39
+ /** Returns the frame string at the current index. Read-only — does not mutate.
35
40
  * @returns The frame string.
36
41
  */
37
42
  getFrame(): string;
43
+ /** Convenience: calls `tick()` then `getFrame()`. Equivalent to the pre-1.3 `getFrame()` advance-and-return.
44
+ * @returns The frame string at the new index.
45
+ */
46
+ nextFrame(): string;
38
47
  }
39
48
 
40
49
  /** Definition of spinner frame sets. */
@@ -60,7 +69,12 @@ export class Spinner extends SpinnerBase {
60
69
  */
61
70
  constructor(spinnerDefinition?: SpinnerDefinition, isStarted?: boolean);
62
71
 
63
- /** Returns the current frame string based on state.
72
+ /** Advances frame index for the active state-array (frames / notStarted / finished).
73
+ * No-op in `paused` state. Read-only-call `getFrame()` after.
74
+ * @returns This instance.
75
+ */
76
+ tick(): this;
77
+ /** Returns the frame string at the current index for the active state-array. Read-only.
64
78
  * @returns The frame string.
65
79
  */
66
80
  getFrame(): string;
@@ -1,10 +1,6 @@
1
1
  import {checkMarkHeavy} from '../symbols.js';
2
2
 
3
- /** Base class for spinners. Manages spinner state (active, paused, finished) and frame index. */
4
3
  export class SpinnerBase {
5
- /**
6
- * @param {boolean} [isStarted=false] - Whether the spinner starts in the active state.
7
- */
8
4
  constructor(isStarted) {
9
5
  this.active = isStarted;
10
6
  this.paused = false;
@@ -12,31 +8,24 @@ export class SpinnerBase {
12
8
  this.frameIndex = 0;
13
9
  }
14
10
 
15
- /** Whether the spinner has been started (active or finished). */
16
11
  get isStarted() {
17
12
  return this.active || this.finished;
18
13
  }
19
14
 
20
- /** Whether the spinner is currently active. */
21
15
  get isActive() {
22
16
  return this.active;
23
17
  }
24
18
 
25
- /** Whether the spinner has finished. */
26
19
  get isFinished() {
27
20
  return this.finished;
28
21
  }
29
22
 
30
- /** The current state: `''`, `'active'`, `'paused'`, or `'finished'`. */
31
23
  get state() {
32
24
  if (this.finished) return 'finished';
33
25
  if (this.active) return this.paused ? 'paused' : 'active';
34
26
  return '';
35
27
  }
36
28
 
37
- /** Sets the spinner state.
38
- * @param {''|'active'|'paused'|'finished'} value
39
- */
40
29
  set state(value) {
41
30
  this.finished = this.active = this.paused = false;
42
31
  switch (value) {
@@ -52,51 +41,50 @@ export class SpinnerBase {
52
41
  }
53
42
  }
54
43
 
55
- /** Resets the spinner to its initial (not started) state. */
56
44
  reset() {
57
45
  this.active = this.paused = this.finished = false;
58
46
  }
59
47
 
60
- /** Advances and returns the next frame index.
61
- * @param {number} length - The total number of frames.
62
- * @returns {number} The new frame index.
63
- */
64
48
  nextFrameIndex(length) {
65
49
  return (this.frameIndex = (this.frameIndex + 1) % length);
66
50
  }
67
51
 
68
- /** Returns the current frame string. Override in subclasses.
69
- * @returns {string}
70
- */
52
+ tick() {
53
+ return this;
54
+ }
55
+
71
56
  getFrame() {
72
57
  return 'X';
73
58
  }
59
+
60
+ nextFrame() {
61
+ this.tick();
62
+ return this.getFrame();
63
+ }
74
64
  }
75
65
 
76
66
  const defaultSpinnerDefinition = {notStarted: [' '], finished: [checkMarkHeavy], frames: [...'⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏']};
77
67
 
78
- /** A spinner with configurable frame sets for active, not-started, and finished states. */
79
68
  export class Spinner extends SpinnerBase {
80
- /**
81
- * @param {object} [spinnerDefinition] - Spinner definition with `frames`, `notStarted`, and `finished` arrays.
82
- * @param {string[]} [spinnerDefinition.frames] - Animation frames for the active state.
83
- * @param {string[]} [spinnerDefinition.notStarted] - Frames shown before the spinner starts.
84
- * @param {string[]} [spinnerDefinition.finished] - Frames shown after the spinner finishes.
85
- * @param {boolean} [isStarted=false] - Whether the spinner starts active.
86
- */
87
69
  constructor(spinnerDefinition, isStarted) {
88
70
  super(isStarted);
89
71
  this.spinner = {...defaultSpinnerDefinition, ...spinnerDefinition};
90
72
  }
91
73
 
92
- /** Returns the current frame based on the spinner's state.
93
- * @returns {string}
94
- */
74
+ tick() {
75
+ if (this.finished) {
76
+ this.nextFrameIndex(this.spinner.finished.length);
77
+ } else if (!this.active) {
78
+ this.nextFrameIndex(this.spinner.notStarted.length);
79
+ } else if (!this.paused) {
80
+ this.nextFrameIndex(this.spinner.frames.length);
81
+ }
82
+ return this;
83
+ }
84
+
95
85
  getFrame() {
96
- if (this.finished) return this.spinner.finished[this.nextFrameIndex(this.spinner.finished.length)];
97
- if (!this.active) return this.spinner.notStarted[this.nextFrameIndex(this.spinner.notStarted.length)];
98
- if (!this.paused) this.nextFrameIndex(this.spinner.frames.length);
99
- return this.spinner.frames[this.frameIndex];
86
+ const arr = this.finished ? this.spinner.finished : !this.active ? this.spinner.notStarted : this.spinner.frames;
87
+ return arr[this.frameIndex % arr.length];
100
88
  }
101
89
  }
102
90
 
@@ -2,41 +2,26 @@
2
2
  // Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
3
3
  // See `cli-spinners` for more great options.
4
4
 
5
- /** Braille dots spinner. */
6
5
  export const dots = {frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']};
7
- /** Sand/hourglass spinner. */
8
6
  export const sand = {frames: [...'⠁⠂⠄⡀⡈⡐⡠⣀⣁⣂⣄⣌⣔⣤⣥⣦⣮⣶⣷⣿⡿⠿⢟⠟⡛⠛⠫⢋⠋⠍⡉⠉⠑⠡⢁']};
9
- /** Line spinner (|/-\). */
10
7
  export const line = {frames: ['-', '\\', '|', '/']};
11
- /** Pipe spinner. */
12
8
  export const pipe = {frames: ['┤', '┘', '┴', '└', '├', '┌', '┬', '┐']};
13
- /** Vertical growing bar spinner. */
14
9
  export const growVertical = {frames: ['▁', '▃', '▄', '▅', '▆', '▇', '▆', '▅', '▄', '▃']};
15
- /** Horizontal growing bar spinner. */
16
10
  export const growHorizontal = {frames: ['▏', '▎', '▍', '▌', '▋', '▊', '▉', '▊', '▋', '▌', '▍', '▎']};
17
- /** Random noise spinner. */
18
11
  export const noise = {frames: ['▓', '▒', '░']};
19
- /** Bouncing spinner. */
20
12
  export const bounce = {frames: ['⠁', '⠂', '⠄', '⠂']};
21
- /** Arc spinner. */
22
13
  export const arc = {frames: ['◜', '◠', '◝', '◞', '◡', '◟']};
23
- /** Square quarters spinner. */
24
14
  export const squareQuarters = {frames: ['◰', '◳', '◲', '◱']};
25
- /** Circle quarters spinner. */
26
15
  export const circleQuarters = {frames: ['◴', '◷', '◶', '◵']};
27
- /** Circle halves spinner. */
28
16
  export const circleHalves = {frames: ['◐', '◓', '◑', '◒']};
29
- /** Arrows spinner. */
30
17
  export const arrows = {frames: ['←', '↖', '↑', '↗', '→', '↘', '↓', '↙']};
31
18
 
32
- /** Clock spinner. */
33
19
  export const clock = {
34
20
  frames: ['🕛 ', '🕐 ', '🕑 ', '🕒 ', '🕓 ', '🕔 ', '🕕 ', '🕖 ', '🕗 ', '🕘 ', '🕙 ', '🕚 '],
35
21
  notStarted: [' '],
36
22
  finished: ['✔ ']
37
23
  };
38
24
 
39
- /** Bouncing bar spinner. */
40
25
  export const bouncingBar = {
41
26
  frames: [
42
27
  '[ ]',
@@ -60,7 +45,6 @@ export const bouncingBar = {
60
45
  finished: ['[####]']
61
46
  };
62
47
 
63
- /** Bouncing ball spinner. */
64
48
  export const bouncingBall = {
65
49
  frames: [
66
50
  '( ● )',
@@ -1,16 +1,6 @@
1
1
  import parse, {matchCsiNoGroups} from './parse.js';
2
2
  import {split} from './split.js';
3
3
 
4
- /** Clips a string to a specified display width, correctly handling ANSI escape codes and multi-width characters.
5
- * @param {string} s - The string to clip.
6
- * @param {number} width - The maximum display width.
7
- * @param {object} [options] - Options.
8
- * @param {boolean} [options.includeLastCommand=false] - If true, include the last (invisible) ANSI command in the clipped result.
9
- * @param {RegExp} [options.matcher=matchCsiNoGroups] - The regular expression used to match escape sequences.
10
- * @param {boolean} [options.ignoreControlSymbols] - If true, control symbols are ignored during width calculation.
11
- * @param {boolean} [options.ambiguousAsWide] - If true, ambiguous East Asian characters are treated as double-wide.
12
- * @returns {string} The clipped string.
13
- */
14
4
  export const clip = (s, width, options = {}) => {
15
5
  const {includeLastCommand = false, matcher = matchCsiNoGroups} = options;
16
6
 
@@ -1,13 +1,6 @@
1
- /** RegExp that matches CSI sequences with no capture groups. */
2
1
  export const matchCsiNoGroups = /\x1B\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]/g;
3
- /** RegExp that matches CSI sequences excluding SGR commands, with no capture groups. */
4
2
  export const matchCsiNoSgrNoGroups = /\x1B\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x6C\x6E-\x7E]/g;
5
3
 
6
- /** Parses a string yielding segments of text and matched ANSI escape sequences.
7
- * @param {string} s - The string to parse.
8
- * @param {RegExp} [matcher=matchCsiNoGroups] - The regular expression used to match escape sequences.
9
- * @yields {{start: number, string: string, match: RegExpMatchArray | null}} Parsed segments.
10
- */
11
4
  export function* parse(s, matcher = matchCsiNoGroups) {
12
5
  s = String(s);
13
6
  let start = 0;