@@ -66,27 +66,40 @@ type Props = {
6666 home ( noninteractive ?: boolean ) : void
6767}
6868
69+ type AssociateWithAsk < A extends Prompts . Prompt , T > = T & {
70+ /**
71+ * The question being asked by this form; so we can noticed when
72+ * the question changes for back-to-back forms.
73+ */
74+ ask : Ask < A >
75+ }
76+
6977type State = {
78+ /** User has opted for inline filters in the Select */
79+ hasInlineFilter ?: boolean
80+
7081 /** User has selected from the Select */
7182 userSelection ?: string
7283
73- /** User has opted for inline filters in the Select */
74- hasInlineFilter ?: boolean
84+ /** Current multiselect state (if any) */
85+ multiselectOptionsChecked ?: AssociateWithAsk <
86+ Prompts . MultiSelect ,
87+ {
88+ state : string [ ]
89+ }
90+ >
7591
7692 /** Current form state (if any) */
77- form ?: {
78- /**
79- * The question being asked by this form; so we can noticed when
80- * the question changes for back-to-back forms.
81- */
82- ask : Ask
83-
84- /**
85- * The current set of answers provided by the user, and
86- * initialized by the guidebook's `initial` value for each key.
87- */
88- state : Record < string , string >
89- }
93+ form ?: AssociateWithAsk <
94+ Prompts . Form ,
95+ {
96+ /**
97+ * The current set of answers provided by the user, and
98+ * initialized by the guidebook's `initial` value for each key.
99+ */
100+ state : Record < string , string >
101+ }
102+ >
90103}
91104
92105/**
@@ -101,32 +114,36 @@ export default class AskUI extends React.PureComponent<Props, State> {
101114 }
102115
103116 public static getDerivedStateFromProps ( props : Props , state : State ) {
104- console . error (
105- "!!!!!!!!" ,
106- state . form ,
107- state . form && state . form . ask === props . ask ,
108- state . form && state . form . ask ,
109- props . ask
110- )
111117 if ( state . userSelection && props . ask . prompt . choices . find ( ( _ ) => _ . name === state . userSelection ) ) {
112- console . error ( "!!!!!!!A" )
113118 return state
114119 } else if ( state . form && state . form . ask === props . ask ) {
115120 // there has been an update to the form, nothing to do here
116- console . error ( "!!!!!!!B" , state . form )
121+ return state
122+ } else if ( state . multiselectOptionsChecked && state . multiselectOptionsChecked . ask === props . ask ) {
123+ // there has been an update to the multiselect, nothing to do here
117124 return state
118125 } else {
119126 const suggested = props . ask . prompt . choices . find ( ( _ ) => ( _ as any ) [ "isSuggested" ] )
120- const state =
127+
128+ const form =
121129 ! props . ask || ! Prompts . isForm ( props . ask . prompt )
122130 ? undefined
123- : props . ask . prompt . choices . reduce ( ( M , _ ) => {
124- M [ _ . name ] = ( _ as any ) [ "initial" ]
125- return M
126- } , { } as Record < string , string > )
127- console . error ( "!!!!!!!C" , state )
131+ : {
132+ ask : props . ask ,
133+ state : props . ask . prompt . choices . reduce ( ( M , _ ) => {
134+ M [ _ . name ] = ( _ as any ) [ "initial" ]
135+ return M
136+ } , { } as Record < string , string > ) ,
137+ }
138+
139+ const multiselectOptionsChecked =
140+ ! props . ask || ! Prompts . isMultiSelect ( props . ask . prompt )
141+ ? undefined
142+ : { ask : props . ask , state : props . ask . prompt . initial }
143+
128144 return {
129- form : { ask : props . ask , state } ,
145+ form,
146+ multiselectOptionsChecked,
130147 userSelection : ! suggested ? undefined : suggested . name ,
131148 }
132149 }
@@ -206,9 +223,14 @@ export default class AskUI extends React.PureComponent<Props, State> {
206223
207224 /** User has clicked to submit a form */
208225 private readonly _onFormSubmit = ( evt : React . SyntheticEvent ) => {
209- if ( this . props . ask && this . state . form ) {
210- evt . preventDefault ( )
211- this . props . ask . onChoose ( Promise . resolve ( this . state . form . state ) )
226+ if ( this . props . ask ) {
227+ if ( this . state . form ) {
228+ evt . preventDefault ( )
229+ this . props . ask . onChoose ( Promise . resolve ( this . state . form . state ) )
230+ } else if ( this . state . multiselectOptionsChecked ) {
231+ evt . preventDefault ( )
232+ this . props . ask . onChoose ( Promise . resolve ( this . state . multiselectOptionsChecked . state ) )
233+ }
212234 }
213235 return false
214236 }
@@ -226,6 +248,30 @@ export default class AskUI extends React.PureComponent<Props, State> {
226248 }
227249 }
228250
251+ /** User has clicked on a MultiSelectOption checkbox */
252+ private readonly _onMultiSelect = (
253+ evt : React . MouseEvent | React . ChangeEvent ,
254+ selection : string | SelectOptionObject ,
255+ isPlaceholder ?: boolean
256+ ) => {
257+ const { ask } = this . props
258+ if ( ! isPlaceholder && selection && ask && this . isMultiSelect ( ask ) ) {
259+ const name = selection . toString ( )
260+ this . setState ( ( curState ) => {
261+ const selections = this . selections ( curState )
262+ return selections === undefined
263+ ? { multiselectOptionsChecked : { ask, state : [ name ] } }
264+ : {
265+ multiselectOptionsChecked : Object . assign ( { } , curState . multiselectOptionsChecked , {
266+ state : selections . includes ( name )
267+ ? selections . filter ( ( _ ) => _ !== name ) // toggle off
268+ : [ ...selections , name ] , // toggle on
269+ } ) ,
270+ }
271+ } )
272+ }
273+ }
274+
229275 private readonly _selectOptionForChoice = (
230276 _ : Ask < Prompts . Select > [ "prompt" ] [ "choices" ] [ number ] ,
231277 isFocused = false
@@ -276,8 +322,17 @@ export default class AskUI extends React.PureComponent<Props, State> {
276322 return ask . title . replace ( / \? $ / , "" )
277323 }
278324
325+ private selections ( state = this . state ) {
326+ if ( state . multiselectOptionsChecked ) {
327+ return state . multiselectOptionsChecked . state
328+ }
329+ }
330+
279331 /** Render a UI for making a selection */
280- private select ( ask : Ask < Prompts . Select > ) {
332+ private select (
333+ ask : Ask < Prompts . Select | Prompts . MultiSelect > ,
334+ wrap : ( node : React . ReactNode ) => React . ReactNode = ( _ ) => _
335+ ) {
281336 const suggested = ask . prompt . choices . find ( ( _ ) => _ . name === this . state ?. userSelection )
282337
283338 // present a filtered list of options; note that we filter based
@@ -314,26 +369,29 @@ export default class AskUI extends React.PureComponent<Props, State> {
314369 ]
315370 }
316371
372+ const isMulti = this . isMultiSelect ( ask )
317373 const onFilter = ( evt : React . ChangeEvent | null , filter : string ) => mkOptions ( filter )
318374
319375 const titleId = "kui--madwizard-ask-ui-title"
320376
321377 const props : SelectProps = {
378+ onFilter,
322379 isOpen : true ,
323380 isPlain : true ,
324381 isGrouped : true ,
325- hasInlineFilter : this . state . hasInlineFilter ,
382+ placeholderText : "" ,
383+ children : mkOptions ( ) ,
384+ onToggle : this . _doNothing ,
385+ "aria-labelledby" : titleId ,
326386 isInputValuePersisted : true ,
327387 isInputFilterPersisted : true ,
328- onFilter ,
329- "aria-labelledby" : titleId ,
388+ selections : this . selections ( ) ,
389+ toggleIndicator : < React . Fragment /> ,
330390 noResultsFoundText : "No matching choices" ,
331- placeholderText : "" ,
391+ variant : ! isMulti ? undefined : "checkbox" ,
392+ hasInlineFilter : this . state . hasInlineFilter ,
332393 inlineFilterPlaceholderText : "Filter choices" ,
333- onSelect : this . _onSelect ,
334- onToggle : this . _doNothing ,
335- toggleIndicator : < React . Fragment /> ,
336- children : mkOptions ( ) ,
394+ onSelect : ! isMulti ? this . _onSelect : this . _onMultiSelect ,
337395 }
338396
339397 // is every message the same as the title?
@@ -344,15 +402,19 @@ export default class AskUI extends React.PureComponent<Props, State> {
344402 return (
345403 < React . Fragment >
346404 < span id = { titleId } hidden />
347- { this . card ( ask , < Select { ...props } data-is-simplistic = { isSimplistic || undefined } /> ) }
405+ { this . card ( ask , wrap ( < Select { ...props } data-is-simplistic = { isSimplistic || undefined } /> ) ) }
348406 </ React . Fragment >
349407 )
350408 }
351409
352410 // eslint-disable-next-line @typescript-eslint/no-unused-vars
353411 private checkboxes ( ask : Ask < Prompts . MultiSelect > ) {
354- console . error ( "!!!!!!MMM" , ask . prompt )
355- return "multiselect"
412+ return this . select ( ask , ( select ) => (
413+ < Form onSubmit = { this . _onFormSubmit } className = "top-pad" >
414+ { select }
415+ { this . formButtons ( ) }
416+ </ Form >
417+ ) )
356418 }
357419
358420 /** User has edited the form */
@@ -370,6 +432,17 @@ export default class AskUI extends React.PureComponent<Props, State> {
370432 }
371433 }
372434
435+ /** Render button group for form */
436+ private formButtons ( ) {
437+ return (
438+ < ActionGroup >
439+ < Button variant = "primary" type = "submit" >
440+ Next
441+ </ Button >
442+ </ ActionGroup >
443+ )
444+ }
445+
373446 /** Render a form ui */
374447 private form ( ask : Ask < Prompts . Form > , form : Required < State > [ "form" ] ) {
375448 return this . card (
@@ -389,11 +462,7 @@ export default class AskUI extends React.PureComponent<Props, State> {
389462 ) ) }
390463 </ Grid >
391464
392- < ActionGroup >
393- < Button variant = "primary" type = "submit" >
394- Next
395- </ Button >
396- </ ActionGroup >
465+ { this . formButtons ( ) }
397466 </ Form >
398467 )
399468 }
0 commit comments