@@ -10,7 +10,12 @@ import BaseComponent from './base-component.js'
1010import Data from './dom/data.js'
1111import EventHandler from './dom/event-handler.js'
1212import 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'
1419
1520/**
1621 * ------------------------------------------------------------------------
@@ -23,8 +28,11 @@ const DATA_KEY = 'coreui.multi-select'
2328const EVENT_KEY = `.${ DATA_KEY } `
2429const DATA_API_KEY = '.data-api'
2530
31+ const ESCAPE_KEY = 'Escape'
2632const 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
2836
2937const SELECTOR_CLEANER = '.form-multi-select-cleaner'
3038const SELECTOR_OPTGROUP = '.form-multi-select-optgroup'
@@ -34,6 +42,7 @@ const SELECTOR_OPTIONS_EMPTY = '.form-multi-select-options-empty'
3442const SELECTOR_SEARCH = '.form-multi-select-search'
3543const SELECTOR_SELECT = '.form-multi-select'
3644const SELECTOR_SELECTION = '.form-multi-select-selection'
45+ const SELECTOR_VISIBLE_ITEMS = '.form-multi-select-options .form-multi-select-option:not(.disabled):not(:disabled)'
3746
3847const EVENT_CHANGED = `changed${ EVENT_KEY } `
3948const EVENT_CLICK = `click${ EVENT_KEY } `
@@ -261,6 +270,12 @@ class MultiSelect extends BaseComponent {
261270 }
262271 } )
263272
273+ EventHandler . on ( this . _clone , EVENT_KEYDOWN , event => {
274+ if ( event . key === ESCAPE_KEY ) {
275+ this . hide ( )
276+ }
277+ } )
278+
264279 EventHandler . on ( this . _indicatorElement , EVENT_CLICK , event => {
265280 event . preventDefault ( )
266281 event . stopPropagation ( )
@@ -306,9 +321,11 @@ class MultiSelect extends BaseComponent {
306321
307322 if ( key === 13 ) {
308323 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 )
312329 }
313330 } )
314331 }
@@ -881,6 +898,18 @@ class MultiSelect extends BaseComponent {
881898 }
882899 }
883900
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+
884913 // Static
885914
886915 static multiSelectInterface ( element , config ) {
@@ -949,7 +978,6 @@ EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
949978 }
950979 }
951980} )
952-
953981EventHandler . on ( document , EVENT_CLICK_DATA_API , MultiSelect . clearMenus )
954982EventHandler . on ( document , EVENT_KEYUP_DATA_API , MultiSelect . clearMenus )
955983
0 commit comments