@@ -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
183184export 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>(
534566const strip = ( str : string ) => str . replace ( ansiRegex ( ) , '' ) ;
535567export 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
605638export 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