@@ -10,7 +10,12 @@ import BaseComponent from './base-component.js'
10
10
import Data from './dom/data.js'
11
11
import EventHandler from './dom/event-handler.js'
12
12
import SelectorEngine from './dom/selector-engine.js'
13
- import { defineJQueryPlugin , isRTL } from './util/index.js'
13
+ import {
14
+ defineJQueryPlugin ,
15
+ getNextActiveElement ,
16
+ isVisible ,
17
+ isRTL
18
+ } from './util/index.js'
14
19
15
20
/**
16
21
* ------------------------------------------------------------------------
@@ -23,8 +28,11 @@ const DATA_KEY = 'coreui.multi-select'
23
28
const EVENT_KEY = `.${ DATA_KEY } `
24
29
const DATA_API_KEY = '.data-api'
25
30
31
+ const ESCAPE_KEY = 'Escape'
26
32
const TAB_KEY = 'Tab'
27
- const RIGHT_MOUSE_BUTTON = 2
33
+ const ARROW_UP_KEY = 'ArrowUp'
34
+ const ARROW_DOWN_KEY = 'ArrowDown'
35
+ const RIGHT_MOUSE_BUTTON = 2 // MouseEvent.button value for the secondary button, usually the right button
28
36
29
37
const SELECTOR_CLEANER = '.form-multi-select-cleaner'
30
38
const SELECTOR_OPTGROUP = '.form-multi-select-optgroup'
@@ -34,6 +42,7 @@ const SELECTOR_OPTIONS_EMPTY = '.form-multi-select-options-empty'
34
42
const SELECTOR_SEARCH = '.form-multi-select-search'
35
43
const SELECTOR_SELECT = '.form-multi-select'
36
44
const SELECTOR_SELECTION = '.form-multi-select-selection'
45
+ const SELECTOR_VISIBLE_ITEMS = '.form-multi-select-options .form-multi-select-option:not(.disabled):not(:disabled)'
37
46
38
47
const EVENT_CHANGED = `changed${ EVENT_KEY } `
39
48
const EVENT_CLICK = `click${ EVENT_KEY } `
@@ -261,6 +270,12 @@ class MultiSelect extends BaseComponent {
261
270
}
262
271
} )
263
272
273
+ EventHandler . on ( this . _clone , EVENT_KEYDOWN , event => {
274
+ if ( event . key === ESCAPE_KEY ) {
275
+ this . hide ( )
276
+ }
277
+ } )
278
+
264
279
EventHandler . on ( this . _indicatorElement , EVENT_CLICK , event => {
265
280
event . preventDefault ( )
266
281
event . stopPropagation ( )
@@ -306,9 +321,11 @@ class MultiSelect extends BaseComponent {
306
321
307
322
if ( key === 13 ) {
308
323
this . _onOptionsClick ( event . target )
309
- if ( this . _config . search ) {
310
- SelectorEngine . findOne ( SELECTOR_SEARCH , this . _clone ) . focus ( )
311
- }
324
+ }
325
+
326
+ if ( [ ARROW_UP_KEY , ARROW_DOWN_KEY ] . includes ( event . key ) ) {
327
+ event . preventDefault ( )
328
+ this . _selectMenuItem ( event )
312
329
}
313
330
} )
314
331
}
@@ -881,6 +898,18 @@ class MultiSelect extends BaseComponent {
881
898
}
882
899
}
883
900
901
+ _selectMenuItem ( { key, target } ) {
902
+ const items = SelectorEngine . find ( SELECTOR_VISIBLE_ITEMS , this . _menu ) . filter ( element => isVisible ( element ) )
903
+
904
+ if ( ! items . length ) {
905
+ return
906
+ }
907
+
908
+ // if target isn't included in items (e.g. when expanding the dropdown)
909
+ // allow cycling to get the last item in case key equals ARROW_UP_KEY
910
+ getNextActiveElement ( items , target , key === ARROW_DOWN_KEY , ! items . includes ( target ) ) . focus ( )
911
+ }
912
+
884
913
// Static
885
914
886
915
static multiSelectInterface ( element , config ) {
@@ -949,7 +978,6 @@ EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
949
978
}
950
979
}
951
980
} )
952
-
953
981
EventHandler . on ( document , EVENT_CLICK_DATA_API , MultiSelect . clearMenus )
954
982
EventHandler . on ( document , EVENT_KEYUP_DATA_API , MultiSelect . clearMenus )
955
983
0 commit comments