diff --git a/.changeset/loose-days-hug.md b/.changeset/loose-days-hug.md new file mode 100644 index 00000000..d19105cc --- /dev/null +++ b/.changeset/loose-days-hug.md @@ -0,0 +1,5 @@ +--- +"@clack/prompts": patch +--- + +Add `clear` method to spinner for stopping and clearing. diff --git a/packages/prompts/src/progress-bar.ts b/packages/prompts/src/progress-bar.ts index 88595141..0cadbf19 100644 --- a/packages/prompts/src/progress-bar.ts +++ b/packages/prompts/src/progress-bar.ts @@ -65,6 +65,7 @@ export function progress({ stop: spin.stop, cancel: spin.cancel, error: spin.error, + clear: spin.clear, advance, isCancelled: spin.isCancelled, message: (msg: string) => advance(0, msg), diff --git a/packages/prompts/src/spinner.ts b/packages/prompts/src/spinner.ts index 65dd591e..8b3f6d01 100644 --- a/packages/prompts/src/spinner.ts +++ b/packages/prompts/src/spinner.ts @@ -28,6 +28,7 @@ export interface SpinnerResult { cancel(msg?: string): void; error(msg?: string): void; message(msg?: string): void; + clear(): void; readonly isCancelled: boolean; } @@ -165,7 +166,7 @@ export const spinner = ({ }, delay); }; - const _stop = (msg = '', code = 0): void => { + const _stop = (msg = '', code = 0, silent: boolean = false): void => { if (!isSpinnerActive) return; isSpinnerActive = false; clearInterval(loop); @@ -177,10 +178,12 @@ export const spinner = ({ ? color.red(S_STEP_CANCEL) : color.red(S_STEP_ERROR); _message = msg ?? _message; - if (indicator === 'timer') { - output.write(`${step} ${_message} ${formatTimer(_origin)}\n`); - } else { - output.write(`${step} ${_message}\n`); + if (!silent) { + if (indicator === 'timer') { + output.write(`${step} ${_message} ${formatTimer(_origin)}\n`); + } else { + output.write(`${step} ${_message}\n`); + } } clearHooks(); unblock(); @@ -189,6 +192,10 @@ export const spinner = ({ const stop = (msg = ''): void => _stop(msg, 0); const cancel = (msg = ''): void => _stop(msg, 1); const error = (msg = ''): void => _stop(msg, 2); + // TODO (43081j): this will leave the initial S_BAR since we purposely + // don't erase that in `clearPrevMessage`. In future, we may want to treat + // `clear` as a special case and remove the bar too. + const clear = (): void => _stop('', 0, true); const message = (msg = ''): void => { _message = removeTrailingDots(msg ?? _message); @@ -200,6 +207,7 @@ export const spinner = ({ message, cancel, error, + clear, get isCancelled() { return isCancelled; }, diff --git a/packages/prompts/test/__snapshots__/spinner.test.ts.snap b/packages/prompts/test/__snapshots__/spinner.test.ts.snap index 3ee9e291..5132fe61 100644 --- a/packages/prompts/test/__snapshots__/spinner.test.ts.snap +++ b/packages/prompts/test/__snapshots__/spinner.test.ts.snap @@ -11,6 +11,18 @@ exports[`spinner (isCI = false) > can be aborted by a signal 1`] = ` ] `; +exports[`spinner (isCI = false) > clear > stops and clears the spinner from the output 1`] = ` +[ + "", + "│ +", + "◒ Loading", + "", + "", + "", +] +`; + exports[`spinner (isCI = false) > indicator customization > custom delay 1`] = ` [ "", @@ -570,6 +582,20 @@ exports[`spinner (isCI = true) > can be aborted by a signal 1`] = ` ] `; +exports[`spinner (isCI = true) > clear > stops and clears the spinner from the output 1`] = ` +[ + "", + "│ +", + "◒ Loading...", + " +", + "", + "", + "", +] +`; + exports[`spinner (isCI = true) > indicator customization > custom delay 1`] = ` [ "", diff --git a/packages/prompts/test/spinner.test.ts b/packages/prompts/test/spinner.test.ts index d0f771cc..f6ff68b1 100644 --- a/packages/prompts/test/spinner.test.ts +++ b/packages/prompts/test/spinner.test.ts @@ -425,4 +425,18 @@ describe.each(['true', 'false'])('spinner (isCI = %s)', (isCI) => { expect(output.buffer).toMatchSnapshot(); }); + + describe('clear', () => { + test('stops and clears the spinner from the output', () => { + const result = prompts.spinner({ output }); + + result.start('Loading'); + + vi.advanceTimersByTime(80); + + result.clear(); + + expect(output.buffer).toMatchSnapshot(); + }); + }); });