Skip to content

Commit 03ee8e5

Browse files
committed
Merge branch 'main' of https://github.com/Mist3rBru/clack into fix/spinner-exit
2 parents 5f1af37 + 1c61e1a commit 03ee8e5

File tree

8 files changed

+97
-12
lines changed

8 files changed

+97
-12
lines changed

.changeset/eight-bikes-repair.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clack/prompts': minor
3+
---
4+
5+
add maxItems option to select prompt

.changeset/loud-bugs-move.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clack/prompts': minor
3+
---
4+
5+
added a new method called `spinner.message(msg: string)`

.changeset/odd-avocados-smile.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clack/prompts': patch
3+
---
4+
5+
Fixes cases where the note title length was miscalculated due to ansi characters
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clack/core': patch
3+
---
4+
5+
fix: restore raw mode on unblock

examples/basic/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"picocolors": "^1.0.0"
1010
},
1111
"scripts": {
12-
"start": "jiti ./index.ts"
12+
"start": "jiti ./index.ts",
13+
"spinner": "jiti ./spinner.ts"
1314
},
1415
"devDependencies": {
1516
"jiti": "^1.17.0"

examples/basic/spinner.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as p from '@clack/prompts';
2+
3+
p.intro('spinner start...');
4+
5+
const spin = p.spinner();
6+
const total = 10000;
7+
let progress = 0;
8+
spin.start();
9+
10+
new Promise((resolve) => {
11+
const timer = setInterval(() => {
12+
progress = Math.min(total, progress + 100);
13+
if (progress >= total) {
14+
clearInterval(timer);
15+
resolve(true);
16+
}
17+
spin.message(`Loading packages [${progress}/${total}]`); // <===
18+
}, 100);
19+
}).then(() => {
20+
spin.stop(`Done`);
21+
p.outro('spinner stop...');
22+
});

packages/core/src/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export function block({
4040
return () => {
4141
input.off('keypress', clear);
4242
if (hideCursor) process.stdout.write(cursor.show);
43+
if (input.isTTY) input.setRawMode(false);
4344

4445
// @ts-expect-error fix for https://github.com/nodejs/node/issues/31762#issuecomment-1441223907
4546
rl.terminal = false;

packages/prompts/src/index.ts

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ export interface SelectOptions<Options extends Option<Value>[], Value> {
178178
message: string;
179179
options: Options;
180180
initialValue?: Value;
181+
maxItems?: number;
181182
}
182183

183184
export const select = <Options extends Option<Value>[], Value>(
@@ -197,6 +198,8 @@ export const select = <Options extends Option<Value>[], Value>(
197198
return `${color.dim(S_RADIO_INACTIVE)} ${color.dim(label)}`;
198199
};
199200

201+
let slidingWindowLocation = 0;
202+
200203
return new SelectPrompt({
201204
options: opts.options,
202205
initialValue: opts.initialValue,
@@ -212,8 +215,37 @@ export const select = <Options extends Option<Value>[], Value>(
212215
'cancelled'
213216
)}\n${color.gray(S_BAR)}`;
214217
default: {
218+
// We clamp to minimum 5 because anything less doesn't make sense UX wise
219+
const maxItems = opts.maxItems === undefined ? Infinity : Math.max(opts.maxItems, 5);
220+
if (this.cursor >= slidingWindowLocation + maxItems - 3) {
221+
slidingWindowLocation = Math.max(
222+
Math.min(this.cursor - maxItems + 3, this.options.length - maxItems),
223+
0
224+
);
225+
} else if (this.cursor < slidingWindowLocation + 2) {
226+
slidingWindowLocation = Math.max(this.cursor - 2, 0);
227+
}
228+
229+
const shouldRenderTopEllipsis =
230+
maxItems < this.options.length && slidingWindowLocation > 0;
231+
const shouldRenderBottomEllipsis =
232+
maxItems < this.options.length &&
233+
slidingWindowLocation + maxItems < this.options.length;
234+
215235
return `${title}${color.cyan(S_BAR)} ${this.options
216-
.map((option, i) => opt(option, i === this.cursor ? 'active' : 'inactive'))
236+
.slice(slidingWindowLocation, slidingWindowLocation + maxItems)
237+
.map((option, i, arr) => {
238+
if (i === 0 && shouldRenderTopEllipsis) {
239+
return color.dim('...');
240+
} else if (i === arr.length - 1 && shouldRenderBottomEllipsis) {
241+
return color.dim('...');
242+
} else {
243+
return opt(
244+
option,
245+
i + slidingWindowLocation === this.cursor ? 'active' : 'inactive'
246+
);
247+
}
248+
})
217249
.join(`\n${color.cyan(S_BAR)} `)}\n${color.cyan(S_BAR_END)}\n`;
218250
}
219251
}
@@ -534,13 +566,14 @@ export const groupMultiselect = <Options extends Option<Value>[], Value>(
534566
const strip = (str: string) => str.replace(ansiRegex(), '');
535567
export const note = (message = '', title = '') => {
536568
const lines = `\n${message}\n`.split('\n');
569+
const titleLen = strip(title).length;
537570
const len =
538571
Math.max(
539572
lines.reduce((sum, ln) => {
540573
ln = strip(ln);
541574
return ln.length > sum ? ln.length : sum;
542575
}, 0),
543-
strip(title).length
576+
titleLen
544577
) + 2;
545578
const msg = lines
546579
.map(
@@ -552,7 +585,7 @@ export const note = (message = '', title = '') => {
552585
.join('\n');
553586
process.stdout.write(
554587
`${color.gray(S_BAR)}\n${color.green(S_STEP_SUBMIT)} ${color.reset(title)} ${color.gray(
555-
S_BAR_H.repeat(Math.max(len - title.length - 1, 1)) + S_CORNER_TOP_RIGHT
588+
S_BAR_H.repeat(Math.max(len - titleLen - 1, 1)) + S_CORNER_TOP_RIGHT
556589
)}\n${msg}\n${color.gray(S_CONNECT_LEFT + S_BAR_H.repeat(len + 2) + S_CORNER_BOTTOM_RIGHT)}\n`
557590
);
558591
};
@@ -603,16 +636,18 @@ export const log = {
603636
};
604637

605638
export const spinner = () => {
639+
const frames = unicode ? ['◒', '◐', '◓', '◑'] : ['•', 'o', 'O', '0'];
640+
const delay = unicode ? 80 : 120;
641+
606642
let unblock: () => void;
607643
let loop: NodeJS.Timer;
608644
let isSpinnerActive: boolean = false;
609-
const frames = unicode ? ['◒', '◐', '◓', '◑'] : ['•', 'o', 'O', '0'];
610-
const delay = unicode ? 80 : 120;
645+
let _message: string = '';
611646

612-
const start = (message: string = ''): void => {
647+
const start = (msg: string = ''): void => {
613648
isSpinnerActive = true;
614649
unblock = block();
615-
message = message.replace(/\.+$/, '');
650+
_message = msg.replace(/\.+$/, '');
616651
process.stdout.write(`${color.gray(S_BAR)}\n`);
617652
let frameIndex = 0;
618653
let dotsTimer = 0;
@@ -621,13 +656,14 @@ export const spinner = () => {
621656
const loadingDots = '.'.repeat(Math.floor(dotsTimer)).slice(0, 3);
622657
process.stdout.write(cursor.move(-999, 0));
623658
process.stdout.write(erase.down(1));
624-
process.stdout.write(`${frame} ${message}${loadingDots}`);
659+
process.stdout.write(`${frame} ${_message}${loadingDots}`);
625660
frameIndex = frameIndex + 1 < frames.length ? frameIndex + 1 : 0;
626661
dotsTimer = dotsTimer < frames.length ? dotsTimer + 0.125 : 0;
627662
}, delay);
628663
};
629664

630-
const stop = (message: string = '', code: number = 0): void => {
665+
const stop = (msg: string = '', code: number = 0): void => {
666+
_message = msg ?? _message
631667
isSpinnerActive = false;
632668
clearInterval(loop);
633669
const step =
@@ -642,9 +678,13 @@ export const spinner = () => {
642678
unblock();
643679
};
644680

681+
const message = (msg: string = ''): void => {
682+
_message = msg ?? _message;
683+
};
684+
645685
const handleExit = (code: number) => {
646-
const message = code > 1 ? 'Something went wrong' : 'Canceled';
647-
if (isSpinnerActive) stop(message, code);
686+
const msg = code > 1 ? 'Something went wrong' : 'Canceled';
687+
if (isSpinnerActive) stop(msg, code);
648688
};
649689

650690
// Reference: https://nodejs.org/api/process.html#event-uncaughtexception
@@ -659,6 +699,7 @@ export const spinner = () => {
659699
return {
660700
start,
661701
stop,
702+
message,
662703
};
663704
};
664705

0 commit comments

Comments
 (0)