Skip to content

Commit 1b06a99

Browse files
committed
fix: clicking Tab key should focus on OK btn or Shift+Tab to inverse
1 parent 64fab62 commit 1b06a99

File tree

1 file changed

+59
-33
lines changed

1 file changed

+59
-33
lines changed

packages/multiple-select-vanilla/src/MultipleSelectInstance.ts

Lines changed: 59 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@ export class MultipleSelectInstance {
843843
}
844844
// move highlight back to top of the list
845845
this._currentHighlightIndex = -1;
846-
this.moveFocusDown();
846+
this.moveHighlightDown();
847847
this.filter();
848848
}) as EventListener);
849849
}
@@ -993,7 +993,7 @@ export class MultipleSelectInstance {
993993
this._bindEventService.bind(
994994
this.dropElm,
995995
'mouseover',
996-
((e: KeyboardEvent & { target: HTMLDivElement | HTMLLIElement }) => {
996+
((e: MouseEvent & { target: HTMLDivElement | HTMLLIElement }) => {
997997
const liElm = (e.target.closest('.ms-select-all') || e.target.closest('li')) as HTMLLIElement;
998998
if (this.dropElm.contains(liElm)) {
999999
const optionElms = this.dropElm?.querySelectorAll<HTMLLIElement>(OPTIONS_LIST_SELECTOR) || [];
@@ -1019,28 +1019,49 @@ export class MultipleSelectInstance {
10191019
switch (e.key) {
10201020
case 'ArrowUp':
10211021
e.preventDefault();
1022-
this.moveFocusUp();
1022+
this.moveHighlightUp();
10231023
break;
10241024
case 'ArrowDown':
10251025
e.preventDefault();
1026-
this.moveFocusDown();
1026+
this.moveHighlightDown();
10271027
break;
10281028
case 'Enter':
10291029
case ' ': {
1030-
const liElm = e.target.closest('.ms-select-all') || e.target.closest('li');
1031-
if ((e.key === ' ' && this.options.filter) || (this.options.filterAcceptOnEnter && !liElm)) {
1032-
return;
1033-
}
1034-
e.preventDefault();
1035-
this._currentSelectedElm?.querySelector('input')?.click();
1030+
// if we're focused on the OK button then don't execute following block
1031+
if (document.activeElement !== this.okButtonElm) {
1032+
const liElm = e.target.closest('.ms-select-all') || e.target.closest('li');
1033+
if ((e.key === ' ' && this.options.filter) || (this.options.filterAcceptOnEnter && !liElm)) {
1034+
return;
1035+
}
1036+
e.preventDefault();
1037+
this._currentSelectedElm?.querySelector('input')?.click();
10361038

1037-
// on single select, we should focus directly
1038-
if (this.options.single) {
1039-
this.choiceElm.focus();
1040-
this.lastFocusedItemKey = this.choiceElm?.dataset.key || '';
1039+
// on single select, we should focus directly
1040+
if (this.options.single) {
1041+
this.choiceElm.focus();
1042+
this.lastFocusedItemKey = this.choiceElm?.dataset.key || '';
1043+
}
10411044
}
10421045
break;
10431046
}
1047+
case 'Tab': {
1048+
// when clicking Tab, we'll focus on OK button when available
1049+
// or with Shift+Tab we'll either focus first option when coming
1050+
// from OK button or close drop if we're already in the lsit
1051+
e.preventDefault();
1052+
if (e.shiftKey) {
1053+
if (document.activeElement === this.okButtonElm) {
1054+
this.focusSelectAllOrList();
1055+
this.highlightCurrentOption();
1056+
} else {
1057+
this.close();
1058+
this.choiceElm.focus();
1059+
}
1060+
} else {
1061+
this.changeCurrentOptionHighlight();
1062+
this.okButtonElm?.focus();
1063+
}
1064+
}
10441065
}
10451066
}) as EventListener,
10461067
undefined,
@@ -1135,12 +1156,12 @@ export class MultipleSelectInstance {
11351156
if (this.options.maxHeightUnit === 'row') {
11361157
maxHeight = getElementSize(this.dropElm.querySelector('ul>li') as HTMLLIElement, 'outer', 'height') * this.options.maxHeight;
11371158
}
1138-
const ulElm = this.dropElm.querySelector('ul');
1139-
if (ulElm) {
1159+
this.ulElm ??= this.dropElm.querySelector('ul');
1160+
if (this.ulElm) {
11401161
if (minHeight) {
1141-
ulElm.style.minHeight = `${minHeight}px`;
1162+
this.ulElm.style.minHeight = `${minHeight}px`;
11421163
}
1143-
ulElm.style.maxHeight = `${maxHeight}px`;
1164+
this.ulElm.style.maxHeight = `${maxHeight}px`;
11441165
}
11451166
this.dropElm.querySelectorAll<HTMLDivElement>('.multiple').forEach(multElm => {
11461167
multElm.style.width = `${this.options.multipleWidth}px`;
@@ -1154,17 +1175,12 @@ export class MultipleSelectInstance {
11541175
this.filter(true);
11551176
} else {
11561177
// highlight SelectAll or 1st select option when opening dropdown
1157-
if (this.selectAllElm) {
1158-
this.selectAllElm.focus();
1159-
} else if (ulElm) {
1160-
ulElm.tabIndex = 0;
1161-
ulElm.focus();
1162-
}
1178+
this.focusSelectAllOrList();
11631179
}
11641180

11651181
if (this._currentHighlightIndex < 0) {
11661182
// on open drop initial, we'll focus on next available option
1167-
this.moveFocusDown();
1183+
this.moveHighlightDown();
11681184
} else {
11691185
// if it was already opened earlier, we'll keep same option index focused
11701186
this.highlightCurrentOption();
@@ -1195,6 +1211,15 @@ export class MultipleSelectInstance {
11951211
this.options.onOpen();
11961212
}
11971213

1214+
protected focusSelectAllOrList() {
1215+
if (this.selectAllElm) {
1216+
this.selectAllElm.focus();
1217+
} else if (this.ulElm) {
1218+
this.ulElm.tabIndex = 0;
1219+
this.ulElm.focus();
1220+
}
1221+
}
1222+
11981223
protected highlightCurrentOption() {
11991224
const optionElms = this.dropElm?.querySelectorAll<HTMLLIElement>(OPTIONS_LIST_SELECTOR) || [];
12001225

@@ -1212,8 +1237,9 @@ export class MultipleSelectInstance {
12121237
}
12131238
}
12141239

1215-
protected changeCurrentOptionHighlight(optionElm: HTMLLIElement | HTMLDivElement) {
1216-
optionElm.classList.add('highlighted');
1240+
/** Change highlighted option, or remove highlight when nothing is provided */
1241+
protected changeCurrentOptionHighlight(optionElm?: HTMLLIElement | HTMLDivElement) {
1242+
optionElm?.classList.add('highlighted');
12171243
const currentElms = this.dropElm?.querySelectorAll<HTMLLIElement>(OPTIONS_HIGHLIGHT_LIST_SELECTOR) || [];
12181244
currentElms.forEach(option => {
12191245
if (option !== optionElm) {
@@ -1222,18 +1248,18 @@ export class MultipleSelectInstance {
12221248
});
12231249
}
12241250

1225-
protected moveFocusDown() {
1251+
protected moveHighlightDown() {
12261252
const optionElms = this.dropElm?.querySelectorAll<HTMLLIElement>(OPTIONS_LIST_SELECTOR) || [];
12271253
if (this._currentHighlightIndex < optionElms.length - 1) {
12281254
this._currentHighlightIndex++;
12291255
if (optionElms[this._currentHighlightIndex]?.classList.contains('disabled')) {
1230-
this.moveFocusDown();
1256+
this.moveHighlightDown();
12311257
}
12321258
}
12331259
this.highlightCurrentOption();
12341260
}
12351261

1236-
protected moveFocusUp() {
1262+
protected moveHighlightUp() {
12371263
const optionElms = this.dropElm?.querySelectorAll<HTMLLIElement>(OPTIONS_LIST_SELECTOR) || [];
12381264
const idxToCompare = this.options.single ? 0 : 1;
12391265
if (this.virtualScroll && this._currentHighlightIndex <= idxToCompare && this.updateDataStart! > 0 && this.ulElm) {
@@ -1252,7 +1278,7 @@ export class MultipleSelectInstance {
12521278
if (this._currentHighlightIndex > 0) {
12531279
this._currentHighlightIndex--;
12541280
if (optionElms[this._currentHighlightIndex]?.classList.contains('disabled')) {
1255-
this.moveFocusUp();
1281+
this.moveHighlightUp();
12561282
}
12571283
}
12581284

@@ -1264,9 +1290,9 @@ export class MultipleSelectInstance {
12641290
const newIdx = Array.from(optionElms).findIndex(el => el.dataset.key === this.lastFocusedItemKey);
12651291
this._currentHighlightIndex = newIdx - 1;
12661292
if (direction === 'down') {
1267-
this.moveFocusDown();
1293+
this.moveHighlightDown();
12681294
} else if (direction === 'up') {
1269-
this.moveFocusUp();
1295+
this.moveHighlightUp();
12701296
this.isMoveUpRecalcRequired = false;
12711297
}
12721298
}

0 commit comments

Comments
 (0)