@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
3
3
import classNames from 'classnames' ;
4
4
import scrollIntoView from 'scroll-into-view-if-needed' ;
5
5
import { isNil , focus , cancelEvent } from '../../helpers/statics' ;
6
+ import { optionMenuItemShape } from '../../helpers/customPropTypes' ;
6
7
7
8
import {
8
9
UP_KEY_CODE ,
@@ -15,21 +16,20 @@ import {
15
16
} from '../../constants' ;
16
17
17
18
import OptionMenuListItem from './OptionMenuListItem' ;
18
- import Icon from '../../library/icon' ;
19
19
20
20
const propTypes = {
21
21
id : PropTypes . string . isRequired ,
22
22
multiple : PropTypes . bool ,
23
23
autocomplete : PropTypes . bool ,
24
24
showCancel : PropTypes . bool ,
25
25
options : PropTypes . arrayOf (
26
- PropTypes . shape ( {
27
- value : PropTypes . oneOfType ( [ PropTypes . string , PropTypes . number ] )
28
- . isRequired ,
29
- label : PropTypes . node . isRequired ,
30
- icon : PropTypes . oneOf ( Icon . AVAILABLE_ICONS ) ,
31
- disabled : PropTypes . bool ,
32
- } ) ,
26
+ PropTypes . oneOfType ( [
27
+ PropTypes . shape ( optionMenuItemShape ) ,
28
+ PropTypes . shape ( {
29
+ ... optionMenuItemShape ,
30
+ value : PropTypes . arrayOf ( PropTypes . shape ( optionMenuItemShape ) ) ,
31
+ } ) ,
32
+ ] ) ,
33
33
) ,
34
34
selected : PropTypes . oneOfType ( [
35
35
PropTypes . string ,
@@ -72,13 +72,21 @@ const defaultProps = {
72
72
73
73
const getOptionId = ( id , value ) => `${ id } -${ value } ` ;
74
74
75
+ const getFocusableOptions = options =>
76
+ options . map ( opt => ( Array . isArray ( opt . value ) ? opt . value : opt ) ) . flat ( ) ;
77
+
75
78
const getFocusedId = ( focusedIndex , id , options ) =>
76
- isNil ( focusedIndex ) || focusedIndex >= options . length
79
+ typeof focusedIndex !== 'number' || focusedIndex >= options . length
77
80
? undefined
78
- : getOptionId ( id , options [ focusedIndex ] . value ) ;
81
+ : getOptionId (
82
+ id ,
83
+ getFocusableOptions ( options ) [ Math . max ( focusedIndex , 0 ) ] . value ,
84
+ ) ;
79
85
80
86
const getSelectionSet = selection =>
81
- new Set ( Array . isArray ( selection ) ? selection : [ selection ] ) ;
87
+ new Set (
88
+ ( Array . isArray ( selection ) ? selection : [ selection ] ) . filter ( el => ! ! el ) ,
89
+ ) ;
82
90
83
91
class OptionMenuList extends Component {
84
92
constructor ( props ) {
@@ -132,7 +140,11 @@ class OptionMenuList extends Component {
132
140
}
133
141
134
142
onMouseEnterItem ( focusedIndex ) {
135
- this . focusItem ( focusedIndex ) ;
143
+ if ( typeof focusedIndex === 'number' ) {
144
+ this . focusItem ( focusedIndex ) ;
145
+ } else {
146
+ this . setState ( { focusedIndex : null } ) ;
147
+ }
136
148
}
137
149
138
150
onCancel ( e ) {
@@ -159,12 +171,15 @@ class OptionMenuList extends Component {
159
171
if ( isNil ( focusedIndex ) ) {
160
172
this . focusFirst ( ) ;
161
173
} else {
162
- this . focusItem ( Math . min ( options . length - 1 , focusedIndex + 1 ) ) ;
174
+ this . focusItem (
175
+ Math . min ( getFocusableOptions ( options ) . length - 1 , focusedIndex + 1 ) ,
176
+ ) ;
163
177
}
164
178
}
165
179
166
180
onKeyDown ( e ) {
167
- const { onEscape, onClickItem } = this . props ;
181
+ const { onEscape, onClickItem, options } = this . props ;
182
+ const { focusedIndex } = this . state ;
168
183
169
184
switch ( e . keyCode ) {
170
185
case UP_KEY_CODE : {
@@ -189,8 +204,11 @@ class OptionMenuList extends Component {
189
204
}
190
205
case SPACE_KEY_CODE :
191
206
case ENTER_KEY_CODE : {
192
- this . selectFocusedItem ( ) ;
193
- onClickItem ( ) ;
207
+ const focused = getFocusableOptions ( options ) [ focusedIndex ] ;
208
+ if ( focused && ! focused . disabled ) {
209
+ this . selectFocusedItem ( ) ;
210
+ onClickItem ( ) ;
211
+ }
194
212
cancelEvent ( e ) ;
195
213
break ;
196
214
}
@@ -245,7 +263,7 @@ class OptionMenuList extends Component {
245
263
focusLast ( ) {
246
264
const { options } = this . props ;
247
265
248
- this . focusItem ( options . length - 1 ) ;
266
+ this . focusItem ( getFocusableOptions ( options ) . length - 1 ) ;
249
267
}
250
268
251
269
focusItem ( focusedIndex ) {
@@ -290,25 +308,14 @@ class OptionMenuList extends Component {
290
308
const { options } = this . props ;
291
309
292
310
if ( ! isNil ( focusedIndex ) ) {
293
- const { value } = options [ focusedIndex ] ;
311
+ const { value } = getFocusableOptions ( options ) [ focusedIndex ] ;
294
312
295
313
this . select ( value ) ;
296
314
}
297
315
}
298
316
299
317
/* eslint-disable jsx-a11y/click-events-have-key-events */
300
318
render ( ) {
301
- const {
302
- onClickItem,
303
- onMouseEnterItem,
304
- onCancel,
305
- onKeyDown,
306
- onKeyDownInAction,
307
- onFocus,
308
- onMenuBlur,
309
- onActionBlur,
310
- } = this ;
311
- const { focusedIndex } = this . state ;
312
319
const {
313
320
id,
314
321
options,
@@ -323,7 +330,6 @@ class OptionMenuList extends Component {
323
330
className,
324
331
style,
325
332
onBlur,
326
- focusedIndex : focussed ,
327
333
onFocusItem,
328
334
footer,
329
335
onClickItem : onClick ,
@@ -334,8 +340,95 @@ class OptionMenuList extends Component {
334
340
return null ;
335
341
}
336
342
343
+ const {
344
+ onClickItem,
345
+ onMouseEnterItem,
346
+ onCancel,
347
+ onKeyDown,
348
+ onKeyDownInAction,
349
+ onFocus,
350
+ onMenuBlur,
351
+ onActionBlur,
352
+ } = this ;
353
+
337
354
const selectionSet = getSelectionSet ( selected ) ;
338
- const focusedId = getFocusedId ( focusedIndex , id , options ) ;
355
+
356
+ delete rest . focusedIndex ;
357
+ const { focusedIndex } = this . state ;
358
+ const focusedId = getFocusedId (
359
+ focusedIndex ,
360
+ id ,
361
+ getFocusableOptions ( options ) ,
362
+ ) ;
363
+
364
+ const renderListItems = ( items , offset = 0 ) => {
365
+ const list = [ ] ;
366
+
367
+ items . forEach ( item => {
368
+ if ( Array . isArray ( item . value ) ) {
369
+ const groupId = `group-${ item . value
370
+ . map ( child => child . value )
371
+ . join ( '-' ) } `;
372
+ const labelId = `${ groupId } -label` ;
373
+
374
+ list . push (
375
+ < ul
376
+ role = "group"
377
+ aria-labelledby = { labelId }
378
+ className = "rc-menu-list-group"
379
+ id = { groupId }
380
+ key = { groupId }
381
+ >
382
+ { item . label && (
383
+ < OptionMenuListItem
384
+ type = "heading"
385
+ disabled = { item . disabled }
386
+ id = { labelId }
387
+ key = { labelId }
388
+ onMouseEnter = { ( ) => onMouseEnterItem ( null ) }
389
+ >
390
+ { item . label }
391
+ </ OptionMenuListItem >
392
+ ) }
393
+ { renderListItems (
394
+ item . value . map ( child =>
395
+ Object . assign ( child , {
396
+ disabled : item . disabled || child . disabled ,
397
+ } ) ,
398
+ ) ,
399
+ list . length + offset ,
400
+ ) }
401
+ </ ul > ,
402
+ ) ;
403
+ // eslint-disable-next-line no-param-reassign
404
+ offset += item . value . length - 1 ;
405
+ } else {
406
+ const index = list . length + offset ;
407
+ list . push (
408
+ < OptionMenuListItem
409
+ id = { getOptionId ( id , item . value ) }
410
+ key = { item . value }
411
+ focused = { index === focusedIndex }
412
+ selected = { selectionSet . has ( item . value ) }
413
+ icon = { item . icon }
414
+ svg = { item . svg }
415
+ disabled = { item . disabled }
416
+ onClick = { ( ) =>
417
+ item . disabled ? undefined : onClickItem ( item . value )
418
+ }
419
+ onMouseEnter = { ( ) => onMouseEnterItem ( index ) }
420
+ ref = { option => {
421
+ this . optionRefs [ index ] = option ;
422
+ } }
423
+ >
424
+ { item . label }
425
+ </ OptionMenuListItem > ,
426
+ ) ;
427
+ }
428
+ } ) ;
429
+
430
+ return list ;
431
+ } ;
339
432
340
433
const list = (
341
434
< ul
@@ -353,24 +446,7 @@ class OptionMenuList extends Component {
353
446
} }
354
447
{ ...rest }
355
448
>
356
- { options . map ( ( { value, label, icon, svg, disabled } , index ) => (
357
- < OptionMenuListItem
358
- id = { getOptionId ( id , value ) }
359
- key = { value }
360
- focused = { index === focusedIndex }
361
- selected = { selectionSet . has ( value ) }
362
- icon = { icon }
363
- svg = { svg }
364
- disabled = { disabled }
365
- onClick = { disabled ? undefined : ( ) => onClickItem ( value ) }
366
- onMouseEnter = { ( ) => onMouseEnterItem ( index ) }
367
- ref = { option => {
368
- this . optionRefs [ index ] = option ;
369
- } }
370
- >
371
- { label }
372
- </ OptionMenuListItem >
373
- ) ) }
449
+ { renderListItems ( options ) }
374
450
</ ul >
375
451
) ;
376
452
0 commit comments