@@ -2,26 +2,46 @@ import { State } from "@clack/core";
22import  {  MultiSelectPrompt ,  TextPrompt ,  SelectPrompt ,  ConfirmPrompt ,  block  }  from  "@clack/core" ; 
33import  color  from  "picocolors" ; 
44import  {  cursor ,  erase  }  from  "sisteransi" ; 
5+ import  isUnicodeSupported  from  "is-unicode-supported" ; 
6+ 
57export  {  isCancel  }  from  "@clack/core" ; 
68
9+ const  unicode  =  isUnicodeSupported ( ) ; 
10+ const  s  =  ( c : string ,  fallback : string )  =>  unicode  ? c  : fallback ; 
11+ const  S_STEP_ACTIVE  =  s ( "◆" ,  "*" ) ; 
12+ const  S_STEP_CANCEL  =  s ( "■" ,  "x" ) ; 
13+ const  S_STEP_ERROR  =  s ( "▲" ,  "x" ) ; 
14+ const  S_STEP_SUBMIT  =  s ( "◇" ,  "o" ) ; 
15+ 
16+ const  S_BAR_START  =  s ( "┌" ,  "T" ) ; 
17+ const  S_BAR  =  s ( "│" ,  "|" ) ; 
18+ const  S_BAR_END  =  s ( "└" ,  "—" ) ; 
19+ 
20+ const  S_RADIO_ACTIVE  =  s ( "●" ,  ">" ) ; 
21+ const  S_RADIO_INACTIVE  =  s ( "○" ,  " " ) ; 
22+ const  S_CHECKBOX_ACTIVE  =  s ( "◻" ,  "[•]" ) ; 
23+ const  S_CHECKBOX_SELECTED  =  s ( "◼" ,  "[+]" ) ; 
24+ const  S_CHECKBOX_INACTIVE  =  s ( "◻" ,  "[ ]" ) ; 
25+ 
26+ const  S_BAR_H  =  s ( '─' ,  '-' ) ; 
27+ const  S_CORNER_TOP_RIGHT  =  s ( '╮' ,  '+' ) ; 
28+ const  S_CONNECT_LEFT  =  s ( '├' ,  '+' ) ; 
29+ const  S_CORNER_BOTTOM_RIGHT  =  s ( '╯' ,  '+' ) ; 
30+ 
731const  symbol  =  ( state : State )  =>  { 
832  switch  ( state )  { 
933    case  "initial" :
1034    case  "active" :
11-       return  color . cyan ( "●" ) ; 
35+       return  color . cyan ( S_STEP_ACTIVE ) ; 
1236    case  "cancel" :
13-       return  color . red ( "■" ) ; 
37+       return  color . red ( S_STEP_CANCEL ) ; 
1438    case  "error" :
15-       return  color . yellow ( "▲" ) ; 
39+       return  color . yellow ( S_STEP_ERROR ) ; 
1640    case  "submit" :
17-       return  color . green ( "○" ) ; 
41+       return  color . green ( S_STEP_SUBMIT ) ; 
1842  } 
1943} ; 
2044
21- const  barStart  =  "┌" ; 
22- const  bar  =  "│" ; 
23- const  barEnd  =  "└" ; 
24- 
2545export  interface  TextOptions  { 
2646  message : string ; 
2747  placeholder ?: string ; 
@@ -34,7 +54,7 @@ export const text = (opts: TextOptions) => {
3454    placeholder : opts . placeholder , 
3555    initialValue : opts . initialValue , 
3656    render ( )  { 
37-       const  title  =  `${ color . gray ( bar ) }  \n${ symbol ( this . state ) }    ${  
57+       const  title  =  `${ color . gray ( S_BAR ) }  \n${ symbol ( this . state ) }    ${  
3858        opts . message  
3959      }  \n`; 
4060      const  placeholder  =  opts . placeholder 
@@ -46,17 +66,17 @@ export const text = (opts: TextOptions) => {
4666      switch  ( this . state )  { 
4767        case  "error" :
4868          return  `${ title . trim ( ) }  \n${ color . yellow (  
49-             bar  
50-           ) }    ${ value }  \n${ color . yellow ( barEnd ) }    ${ color . yellow ( this . error ) }  \n`; 
69+             S_BAR  
70+           ) }    ${ value }  \n${ color . yellow ( S_BAR_END ) }    ${ color . yellow ( this . error ) }  \n`; 
5171        case  "submit" :
52-           return  `${ title } ${ color . gray ( bar ) }    ${ color . dim ( this . value ) }  ` ; 
72+           return  `${ title } ${ color . gray ( S_BAR ) }    ${ color . dim ( this . value ) }  ` ; 
5373        case  "cancel" :
54-           return  `${ title } ${ color . gray ( bar ) }    ${ color . strikethrough (  
74+           return  `${ title } ${ color . gray ( S_BAR ) }    ${ color . strikethrough (  
5575            color . dim ( this . value )  
56-           ) }  ${ this . value . trim ( )  ? "\n"  +  color . gray ( bar )  : "" }  `; 
76+           ) }  ${ this . value . trim ( )  ? "\n"  +  color . gray ( S_BAR )  : "" }  `; 
5777        default :
58-           return  `${ title } ${ color . cyan ( bar ) }    ${ value }  \n${ color . cyan (  
59-             barEnd  
78+           return  `${ title } ${ color . cyan ( S_BAR ) }    ${ value }  \n${ color . cyan (  
79+             S_BAR_END  
6080          ) }  \n`; 
6181      } 
6282    } , 
@@ -77,28 +97,28 @@ export const confirm = (opts: ConfirmOptions) => {
7797    inactive, 
7898    initialValue : opts . initialValue  ??  true , 
7999    render ( )  { 
80-       const  title  =  `${ color . gray ( bar ) }  \n${ symbol ( this . state ) }    ${  
100+       const  title  =  `${ color . gray ( S_BAR ) }  \n${ symbol ( this . state ) }    ${  
81101        opts . message  
82102      }  \n`; 
83103      const  value  =  this . value  ? active  : inactive ; 
84104
85105      switch  ( this . state )  { 
86106        case  "submit" :
87-           return  `${ title } ${ color . gray ( bar ) }    ${ color . dim ( value ) }  ` ; 
107+           return  `${ title } ${ color . gray ( S_BAR ) }    ${ color . dim ( value ) }  ` ; 
88108        case  "cancel" :
89-           return  `${ title } ${ color . gray ( bar ) }    ${ color . strikethrough (  
109+           return  `${ title } ${ color . gray ( S_BAR ) }    ${ color . strikethrough (  
90110            color . dim ( value )  
91-           ) }  \n${ color . gray ( bar ) }  `; 
111+           ) }  \n${ color . gray ( S_BAR ) }  `; 
92112        default : { 
93-           return  `${ title } ${ color . cyan ( bar ) }    ${  
113+           return  `${ title } ${ color . cyan ( S_BAR ) }    ${  
94114            this . value  
95-               ? `${ color . green ( "●" ) }   ${ active }  `  
96-               : `${ color . dim ( "○" ) }   ${ color . dim ( active ) }  `  
115+               ? `${ color . green ( S_RADIO_ACTIVE ) }   ${ active }  `  
116+               : `${ color . dim ( S_RADIO_INACTIVE ) }   ${ color . dim ( active ) }  `  
97117          }   ${ color . dim ( "/" ) }   ${ 
98118            ! this . value  
99-               ? `${ color . green ( "●" ) }   ${ inactive }  `  
100-               : `${ color . dim ( "○" ) }   ${ color . dim ( inactive ) }  `  
101-           }  \n${ color . cyan ( barEnd ) }  \n`; 
119+               ? `${ color . green ( S_RADIO_ACTIVE ) }   ${ inactive }  `  
120+               : `${ color . dim ( S_RADIO_INACTIVE ) }   ${ color . dim ( inactive ) }  `  
121+           }  \n${ color . cyan ( S_BAR_END ) }  \n`; 
102122        } 
103123      } 
104124    } , 
@@ -133,42 +153,42 @@ export const select = <Options extends Option<Value>[], Value extends Primitive>
133153  )  =>  { 
134154    const  label  =  option . label  ??  String ( option . value ) ; 
135155    if  ( state  ===  "active" )  { 
136-       return  `${ color . green ( "●" ) }   ${ label }   ${  
156+       return  `${ color . green ( S_RADIO_ACTIVE ) }   ${ label }   ${  
137157        option . hint  ? color . dim ( `(${ option . hint }  )` )  : ""  
138158      }  `; 
139159    }  else  if  ( state  ===  "selected" )  { 
140160      return  `${ color . dim ( label ) }  ` ; 
141161    }  else  if  ( state  ===  "cancelled" )  { 
142162      return  `${ color . strikethrough ( color . dim ( label ) ) }  ` ; 
143163    } 
144-     return  `${ color . dim ( "○" ) }   ${ color . dim ( label ) }  ` ; 
164+     return  `${ color . dim ( S_RADIO_INACTIVE ) }   ${ color . dim ( label ) }  ` ; 
145165  } ; 
146166
147167  return  new  SelectPrompt ( { 
148168    options : opts . options , 
149169    initialValue : opts . initialValue , 
150170    render ( )  { 
151-       const  title  =  `${ color . gray ( bar ) }  \n${ symbol ( this . state ) }    ${  
171+       const  title  =  `${ color . gray ( S_BAR ) }  \n${ symbol ( this . state ) }    ${  
152172        opts . message  
153173      }  \n`; 
154174
155175      switch  ( this . state )  { 
156176        case  "submit" :
157-           return  `${ title } ${ color . gray ( bar ) }    ${ opt (  
177+           return  `${ title } ${ color . gray ( S_BAR ) }    ${ opt (  
158178            this . options [ this . cursor ] ,  
159179            "selected"  
160180          ) }  `; 
161181        case  "cancel" :
162-           return  `${ title } ${ color . gray ( bar ) }    ${ opt (  
182+           return  `${ title } ${ color . gray ( S_BAR ) }    ${ opt (  
163183            this . options [ this . cursor ] ,  
164184            "cancelled"  
165-           ) }  \n${ color . gray ( bar ) }  `; 
185+           ) }  \n${ color . gray ( S_BAR ) }  `; 
166186        default : { 
167-           return  `${ title } ${ color . cyan ( bar ) }    ${ this . options  
187+           return  `${ title } ${ color . cyan ( S_BAR ) }    ${ this . options  
168188            . map ( ( option ,  i )  =>  
169189              opt ( option ,  i  ===  this . cursor  ? "active"  : "inactive" )  
170190            )  
171-             . join ( `\n${ color . cyan ( bar ) }    ` ) }  \n${ color . cyan ( barEnd ) }  \n`; 
191+             . join ( `\n${ color . cyan ( S_BAR ) }    ` ) }  \n${ color . cyan ( S_BAR_END ) }  \n`; 
172192        } 
173193      } 
174194    } , 
@@ -179,39 +199,39 @@ export const multiselect = <Options extends Option<Value>[], Value extends Primi
179199    const  opt  =  ( option : Options [ number ] ,  state : 'inactive'  |  'active'  |  'selected'  |  'active-selected'  |  'submitted'  |  'cancelled' )  =>  { 
180200        const  label  =   option . label  ??  String ( option . value ) ; 
181201        if  ( state  ===  'active' )  { 
182-             return  `${ color . cyan ( '◻' ) }   ${ label }   ${ option . hint  ? color . dim ( `(${ option . hint }  )` )  : '' }  ` 
202+             return  `${ color . cyan ( S_CHECKBOX_ACTIVE ) }   ${ label }   ${ option . hint  ? color . dim ( `(${ option . hint }  )` )  : '' }  ` 
183203        }  else  if  ( state  ===  'selected' )  { 
184-             return  `${ color . green ( '◼' ) }   ${ color . dim ( label ) }  ` 
204+             return  `${ color . green ( S_CHECKBOX_SELECTED ) }   ${ color . dim ( label ) }  ` 
185205        }  else  if  ( state  ===  'cancelled' )  { 
186206            return  `${ color . strikethrough ( color . dim ( label ) ) }  ` ; 
187207        }  else  if  ( state  ===  'active-selected' )  { 
188-             return  `${ color . green ( '◼' ) }   ${ label }   ${ option . hint  ? color . dim ( `(${ option . hint }  )` )  : '' }  ` 
208+             return  `${ color . green ( S_CHECKBOX_SELECTED ) }   ${ label }   ${ option . hint  ? color . dim ( `(${ option . hint }  )` )  : '' }  ` 
189209        }  else  if  ( state  ===  'submitted' )  { 
190210            return  `${ color . dim ( label ) }  ` ; 
191211        } 
192-         return  `${ color . dim ( '◻' ) }   ${ color . dim ( label ) }  ` ; 
212+         return  `${ color . dim ( S_CHECKBOX_INACTIVE ) }   ${ color . dim ( label ) }  ` ; 
193213    } 
194214
195215    return  new  MultiSelectPrompt ( { 
196216        options : opts . options , 
197217        initialValue : opts . initialValue , 
198218        cursorAt : opts . cursorAt , 
199219        render ( )  { 
200-             let  title  =  `${ color . gray ( bar ) }  \n${ symbol ( this . state ) }    ${ opts . message }  \n` ; 
220+             let  title  =  `${ color . gray ( S_BAR ) }  \n${ symbol ( this . state ) }    ${ opts . message }  \n` ; 
201221
202222            switch  ( this . state )  { 
203223                case  'submit' : { 
204224                    const  selectedOptions  =  this . options . filter ( option  =>  this . selectedValues . some ( selectedValue  =>  selectedValue  ===  option . value  as  any ) ) ; 
205-                     return  `${ title } ${ color . gray ( bar ) }    ${ selectedOptions . map ( ( option ,  i )  =>  opt ( option ,  'submitted' ) ) . join ( color . dim ( ", " ) ) }  ` ; 
225+                     return  `${ title } ${ color . gray ( S_BAR ) }    ${ selectedOptions . map ( ( option ,  i )  =>  opt ( option ,  'submitted' ) ) . join ( color . dim ( ", " ) ) }  ` ; 
206226                } ; 
207227                case  'cancel' : { 
208228                    const  selectedOptions  =  this . options . filter ( option  =>  this . selectedValues . some ( selectedValue  =>  selectedValue  ===  option . value  as  any ) ) ; 
209229                    const  label  =  selectedOptions . map ( ( option ,  i )  =>  opt ( option ,  'cancelled' ) ) . join ( color . dim ( ", " ) ) ; 
210-                     return  `${ title } ${ color . gray ( bar ) }    ${ label . trim ( )  ? `${ label }  \n${ color . gray ( bar ) }  `  : '' }  ` 
230+                     return  `${ title } ${ color . gray ( S_BAR ) }    ${ label . trim ( )  ? `${ label }  \n${ color . gray ( S_BAR ) }  `  : '' }  ` 
211231                } ; 
212232                case  'error' : { 
213-                     const  footer  =  this . error . split ( '\n' ) . map ( ( ln ,  i )  =>  i  ===  0  ? `${ color . yellow ( barEnd ) }    ${ color . yellow ( ln ) }  `  : `   ${ ln }  ` ) . join ( '\n' ) ; 
214-                     return  `${ title } ${ color . yellow ( bar ) }    ${ this . options . map ( ( option ,  i )  =>  {  
233+                     const  footer  =  this . error . split ( '\n' ) . map ( ( ln ,  i )  =>  i  ===  0  ? `${ color . yellow ( S_BAR_END ) }    ${ color . yellow ( ln ) }  `  : `   ${ ln }  ` ) . join ( '\n' ) ; 
234+                     return  `${ title } ${ color . yellow ( S_BAR ) }    ${ this . options . map ( ( option ,  i )  =>  {  
215235                        const  isOptionSelected  =  this . selectedValues . includes ( option . value  as  any ) ;   
216236                        const  isOptionHovered  =  i  ===  this . cursor ;  
217237                        if ( isOptionHovered  &&  isOptionSelected )   {  
@@ -221,10 +241,10 @@ export const multiselect = <Options extends Option<Value>[], Value extends Primi
221241                            return  opt ( option ,  'selected' ) ;  
222242                        }  
223243                        return  opt ( option ,  isOptionHovered  ? 'active'  : 'inactive' ) ;  
224-                     } ) . join ( `\n${ color . yellow ( bar ) }    ` ) }  \n${ footer }  \n`; 
244+                     } ) . join ( `\n${ color . yellow ( S_BAR ) }    ` ) }  \n${ footer }  \n`; 
225245                } 
226246                default : { 
227-                     return  `${ title } ${ color . cyan ( bar ) }    ${ this . options . map ( ( option ,  i )  =>  {  
247+                     return  `${ title } ${ color . cyan ( S_BAR ) }    ${ this . options . map ( ( option ,  i )  =>  {  
228248                        const  isOptionSelected  =  this . selectedValues . includes ( option . value  as  any ) ;   
229249                        const  isOptionHovered  =  i  ===  this . cursor ;  
230250                        if ( isOptionHovered  &&  isOptionSelected )   {  
@@ -234,7 +254,7 @@ export const multiselect = <Options extends Option<Value>[], Value extends Primi
234254                            return  opt ( option ,  'selected' ) ;  
235255                        }  
236256                        return  opt ( option ,  isOptionHovered  ? 'active'  : 'inactive' ) ;  
237-                     } ) . join ( `\n${ color . cyan ( bar ) }    ` ) }  \n${ color . cyan ( barEnd ) }  \n`; 
257+                     } ) . join ( `\n${ color . cyan ( S_BAR ) }    ` ) }  \n${ color . cyan ( S_BAR_END ) }  \n`; 
238258                } 
239259            } 
240260        } 
@@ -248,39 +268,36 @@ export const note = (message = "", title = '') => {
248268    ln  =  strip ( ln ) ; 
249269    return  ln . length  >  sum  ? ln . length  : sum 
250270  } ,  0 )  +  2 ; 
251-   const  msg  =  lines . map ( ( ln )  =>  `${ color . gray ( bar ) }    ${ color . dim ( ln ) } ${ ' ' . repeat ( len  -  strip ( ln ) . length ) } ${ color . gray ( bar ) }  ` ) . join ( '\n' ) ; 
252-   process . stdout . write ( `${ color . gray ( bar ) }  \n${ color . green ( '○' ) }    ${ color . reset ( title ) }   ${ color . gray ( '─' . repeat ( len  -  title . length  -  1 )  +  '╮' ) }  \n${ msg }  \n${ color . gray ( '├'  +  '─' . repeat ( len  +  2 )  +  '╯' ) }  \n` ) ; 
271+   const  msg  =  lines . map ( ( ln )  =>  `${ color . gray ( S_BAR ) }    ${ color . dim ( ln ) } ${ ' ' . repeat ( len  -  strip ( ln ) . length ) } ${ color . gray ( S_BAR ) }  ` ) . join ( '\n' ) ; 
272+   process . stdout . write ( `${ color . gray ( S_BAR ) }  \n${ color . green ( S_STEP_SUBMIT ) }    ${ color . reset ( title ) }   ${ color . gray ( S_BAR_H . repeat ( len  -  title . length  -  1 )  +  S_CORNER_TOP_RIGHT ) }  \n${ msg }  \n${ color . gray ( S_CONNECT_LEFT  +  S_BAR_H . repeat ( len  +  2 )  +  S_CORNER_BOTTOM_RIGHT ) }  \n` ) ; 
253273} ; 
254274
255275export  const  cancel  =  ( message  =  "" )  =>  { 
256-   process . stdout . write ( `${ color . gray ( barEnd ) }    ${ color . red ( message ) }  \n\n` ) ; 
276+   process . stdout . write ( `${ color . gray ( S_BAR_END ) }    ${ color . red ( message ) }  \n\n` ) ; 
257277} ; 
258278
259279export  const  intro  =  ( title  =  "" )  =>  { 
260-   process . stdout . write ( `${ color . gray ( barStart ) }    ${ title }  \n` ) ; 
280+   process . stdout . write ( `${ color . gray ( S_BAR_START ) }    ${ title }  \n` ) ; 
261281} ; 
262282
263283export  const  outro  =  ( message  =  "" )  =>  { 
264284  process . stdout . write ( 
265-     `${ color . gray ( bar ) }  \n${ color . gray ( barEnd ) }    ${ message }  \n\n` 
285+     `${ color . gray ( S_BAR ) }  \n${ color . gray ( S_BAR_END ) }    ${ message }  \n\n` 
266286  ) ; 
267287} ; 
268288
269- const  arc  =  [ 
270-     '◒' ,  '◐' ,  '◓' ,  '◑' 
271- ] 
289+ const  frames  =  unicode  ? [ '◒' ,  '◐' ,  '◓' ,  '◑' ]  : [ '•' , 'o' , 'O' , '0' ] 
272290
273291export  const  spinner  =  ( )  =>  { 
274292  let  unblock : ( )  =>  void ; 
275293  let  loop : NodeJS . Timer ; 
276-   const  frames  =  arc ; 
277-   const  delay  =  80 ; 
294+   const  delay  =  unicode  ? 80  : 120 ; 
278295  return  { 
279296    start ( message  =  "" )  { 
280297      message  =  message . replace ( / \. ? \. ? \. $ / ,  "" ) ; 
281298      unblock  =  block ( ) ; 
282299      process . stdout . write ( 
283-         `${ color . gray ( bar ) }  \n${ color . magenta ( "○" ) }    ${ message }  \n` 
300+         `${ color . gray ( S_BAR ) }  \n${ color . magenta ( "○" ) }    ${ message }  \n` 
284301      ) ; 
285302      let  i  =  0 ; 
286303      let  dot  =  0 ; 
@@ -299,7 +316,7 @@ export const spinner = () => {
299316      process . stdout . write ( erase . down ( 2 ) ) ; 
300317      clearInterval ( loop ) ; 
301318      process . stdout . write ( 
302-         `${ color . gray ( bar ) }  \n${ color . green ( "○" ) }    ${ message }  \n` 
319+         `${ color . gray ( S_BAR ) }  \n${ color . green ( S_STEP_SUBMIT ) }    ${ message }  \n` 
303320      ) ; 
304321      unblock ( ) ; 
305322    } , 
0 commit comments