From 94d4efd5989f649c52e89a7b06a4b042045a01ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Rio?= Date: Wed, 23 Aug 2023 18:38:27 +0100 Subject: [PATCH 01/13] Block the ability to open dropdown by press enter key when it's disable. --- src/virtual-select.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/virtual-select.js b/src/virtual-select.js index b9d63dd..e5edc80 100755 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -467,7 +467,7 @@ export class VirtualSelect { if (this.isOpened()) { this.selectFocusedOption(); - } else { + } else if (this.$ele.disabled === false) { this.openDropbox(); } } @@ -2882,6 +2882,7 @@ export class VirtualSelect { this.$ele.removeAttribute('disabled'); this.$hiddenInput.removeAttribute('disabled'); DomUtils.setAria(this.$wrapper, 'disabled', false); + DomUtils.changeTabIndex(this.$wrapper, 0); } disable() { @@ -2890,6 +2891,8 @@ export class VirtualSelect { this.$ele.setAttribute('disabled', ''); this.$hiddenInput.setAttribute('disabled', ''); DomUtils.setAria(this.$wrapper, 'disabled', true); + DomUtils.changeTabIndex(this.$wrapper, -1); + this.$wrapper.blur(); } validate() { From 2fce3df8383a4be85d142f996e7cfacfcefbddc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Rio?= Date: Thu, 24 Aug 2023 11:49:59 +0100 Subject: [PATCH 02/13] manage focus accordingly in order to help screen reader focusing on the correct element. --- src/utils/dom-utils.js | 18 ++++++++++++++++++ src/virtual-select.js | 7 +++++++ 2 files changed, 25 insertions(+) diff --git a/src/utils/dom-utils.js b/src/utils/dom-utils.js index bc53b7e..5001a91 100644 --- a/src/utils/dom-utils.js +++ b/src/utils/dom-utils.js @@ -213,6 +213,24 @@ export class DomUtils { return $ele.forEach === undefined ? [$ele] : $ele; } + /** + * @static + * @param {string} [$selector=''] + * @param {*} [$parentEle=undefined] + * @return {*} + * @memberof DomUtils + */ + static getElementsBySelector($selector = '', $parentEle = undefined) { + let elements; + const parent = $parentEle !== undefined ? $parentEle : document; + + if ($selector !== '') { + elements = parent.querySelectorAll($selector); + } + + return elements !== undefined ? Array.from(elements) : []; + } + /** * @param {HTMLElement} $ele * @param {string} events diff --git a/src/virtual-select.js b/src/virtual-select.js index e5edc80..2f0d8b1 100755 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -261,6 +261,7 @@ export class VirtualSelect { const visibleOptions = this.getVisibleOptions(); let checkboxHtml = ''; let newOptionIconHtml = ''; + let focusedOption = []; const markSearchResults = !!(this.markSearchResults && this.searchValue); let searchRegex; const { labelRenderer, disableOptionGroupCheckbox, uniqueId, searchGroup } = this; @@ -679,6 +680,11 @@ export class VirtualSelect { this.setOptionAttr(); this.setOptionsPosition(); this.setOptionsTooltip(); + + const focusedOption = DomUtils.getElementsBySelector('.focused', this.$dropboxContainer)[0]; + if (focusedOption !== undefined) { + focusedOption.focus(); + } } afterSetOptionsContainerHeight(reset) { @@ -2973,6 +2979,7 @@ export class VirtualSelect { return; } + $ele.focus(); DomUtils.toggleClass($ele, 'focused', isFocused); if (isFocused) { From 3977c9019e79474e2c83405bfb01f50ff5d74764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Rio?= Date: Thu, 24 Aug 2023 12:10:42 +0100 Subject: [PATCH 03/13] Once focus is properly managed, live-region is not needed. --- src/utils/dom-utils.js | 2 +- src/virtual-select.js | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/utils/dom-utils.js b/src/utils/dom-utils.js index 5001a91..7e20288 100644 --- a/src/utils/dom-utils.js +++ b/src/utils/dom-utils.js @@ -217,7 +217,7 @@ export class DomUtils { * @static * @param {string} [$selector=''] * @param {*} [$parentEle=undefined] - * @return {*} + * @return {*} * @memberof DomUtils */ static getElementsBySelector($selector = '', $parentEle = undefined) { diff --git a/src/virtual-select.js b/src/virtual-select.js index 2f0d8b1..53b52d8 100755 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -177,17 +177,12 @@ export class VirtualSelect { -
-

-
- ${this.renderDropbox({ wrapperClasses })} `; this.$ele.innerHTML = html; this.$body = document.querySelector('body'); this.$wrapper = this.$ele.querySelector('.vscomp-wrapper'); - this.$ariaLiveElem = this.$ele.querySelector('.vscomp-live-region-title'); if (this.hasDropboxWrapper) { this.$allWrappers = [this.$wrapper, this.$dropboxWrapper]; @@ -261,7 +256,6 @@ export class VirtualSelect { const visibleOptions = this.getVisibleOptions(); let checkboxHtml = ''; let newOptionIconHtml = ''; - let focusedOption = []; const markSearchResults = !!(this.markSearchResults && this.searchValue); let searchRegex; const { labelRenderer, disableOptionGroupCheckbox, uniqueId, searchGroup } = this; @@ -2302,10 +2296,6 @@ export class VirtualSelect { this.toggleOptionFocusedState($focusedEle, false); } - if (this.$ariaLiveElem) { - this.$ariaLiveElem.textContent = $newFocusedEle.textContent; - } - this.toggleOptionFocusedState($newFocusedEle, true); this.toggleFocusedProp(DomUtils.getData($newFocusedEle, 'index'), true); this.moveFocusedOptionToView($newFocusedEle); From 38f5aa4874a13947069723c47244dc4f2b080fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Rio?= Date: Thu, 24 Aug 2023 15:03:14 +0100 Subject: [PATCH 04/13] Added the ability to clear using backspace or delete keys. --- src/virtual-select.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/virtual-select.js b/src/virtual-select.js index 53b52d8..cae559f 100755 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -10,6 +10,8 @@ const keyDownMethodMapping = { 13: 'onEnterPress', 38: 'onUpArrowPress', 40: 'onDownArrowPress', + 46: 'onBackspaceOrDeletePress', // Delete + 8: 'onBackspaceOrDeletePress', // Backspace }; const valueLessProps = ['autofocus', 'disabled', 'multiple', 'required']; @@ -175,7 +177,6 @@ export class VirtualSelect {
- ${this.renderDropbox({ wrapperClasses })} `; @@ -487,6 +488,13 @@ export class VirtualSelect { } } + onBackspaceOrDeletePress(e) { + e.preventDefault(); + if (this.selectedValues.length > 0) { + this.reset(); + } + } + onToggleButtonClick(e) { const $target = e.target; From 2ffe4346d07b405d165d8e29393e35fb408e1dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Rio?= Date: Thu, 24 Aug 2023 15:40:55 +0100 Subject: [PATCH 05/13] Add aria-label to the options in order to help contextualising groups. --- src/virtual-select.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/virtual-select.js b/src/virtual-select.js index cae559f..9fab766 100755 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -285,6 +285,7 @@ export class VirtualSelect { let rightSection = ''; let description = ''; let groupIndexText = ''; + let ariaLabel = ''; const isSelected = convertToBoolean(d.isSelected); let ariaDisabledText = ''; @@ -316,6 +317,10 @@ export class VirtualSelect { if (d.isGroupOption) { optionClasses += ' group-option'; groupIndexText = `data-group-index="${d.groupIndex}"`; + + const groupName = d.customData.group_name !== undefined ? `${d.customData.group_name}, ` : ''; + const optionDesc = d.customData.description !== undefined ? ` ${d.customData.description},` : ''; + ariaLabel = `aria-label="${groupName}${d.label},${optionDesc}"`; } if (hasLabelRenderer) { @@ -337,7 +342,7 @@ export class VirtualSelect { html += `
${leftSection} From c9bff36f5993d16f302ec1abec76da9668cdb753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Rio?= Date: Thu, 24 Aug 2023 21:07:42 +0100 Subject: [PATCH 06/13] Fixed issue when searching at input after the focus being set. --- src/sass/partials/virtual-select.scss | 1 + src/virtual-select.js | 34 ++++++++++++++++++++------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/sass/partials/virtual-select.scss b/src/sass/partials/virtual-select.scss index b5b8539..55805ac 100755 --- a/src/sass/partials/virtual-select.scss +++ b/src/sass/partials/virtual-select.scss @@ -272,6 +272,7 @@ } .vscomp-search-label, +.vscomp-options-bottom, .vscomp-live-region { border: 0; clip: rect(0 0 0 0); diff --git a/src/virtual-select.js b/src/virtual-select.js index 9fab766..41d06e7 100755 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -204,6 +204,7 @@ export class VirtualSelect { this.$search = this.$dropboxContainer.querySelector('.vscomp-search-wrapper'); this.$optionsContainer = this.$dropboxContainer.querySelector('.vscomp-options-container'); this.$optionsList = this.$dropboxContainer.querySelector('.vscomp-options-list'); + this.$optionsListBottom = this.$dropboxContainer.querySelector('.vscomp-bottom-item'); this.$options = this.$dropboxContainer.querySelector('.vscomp-options'); this.$noOptions = this.$dropboxContainer.querySelector('.vscomp-no-options'); this.$noSearchResults = this.$dropboxContainer.querySelector('.vscomp-no-search-results'); @@ -223,6 +224,7 @@ export class VirtualSelect {
+
@@ -286,6 +288,7 @@ export class VirtualSelect { let description = ''; let groupIndexText = ''; let ariaLabel = ''; + let tabIndexValue = '0'; const isSelected = convertToBoolean(d.isSelected); let ariaDisabledText = ''; @@ -303,6 +306,7 @@ export class VirtualSelect { } if (d.isGroupTitle) { + tabIndexValue = '-1'; optionClasses += ' group-title'; if (disableOptionGroupCheckbox) { @@ -342,7 +346,7 @@ export class VirtualSelect { html += `
${leftSection} @@ -399,6 +403,7 @@ export class VirtualSelect { this.addEvent(this.$searchInput, 'input', 'onSearch'); this.addEvent(this.$searchClear, 'click', 'onSearchClear'); this.addEvent(this.$toggleAllButton, 'click', 'onToggleAllOptions'); + this.addEvent(this.$optionsListBottom, 'focus', 'onFocusBottom'); } /** render methods - end */ @@ -449,7 +454,8 @@ export class VirtualSelect { const key = e.which || e.keyCode; const method = keyDownMethodMapping[key]; - if (document.activeElement === this.$searchInput && (key === 9 || (e.shiftKey && key === 9))) { + // if (document.activeElement === this.$searchInput && (key === 9 || (e.shiftKey && key === 9))) { + if (document.activeElement === this.$searchInput && (e.shiftKey && key === 9)) { this.closeDropbox(); return; } @@ -494,9 +500,11 @@ export class VirtualSelect { } onBackspaceOrDeletePress(e) { - e.preventDefault(); - if (this.selectedValues.length > 0) { - this.reset(); + if (e.target === this.$wrapper) { + e.preventDefault(); + if (this.selectedValues.length > 0) { + this.reset(); + } } } @@ -581,6 +589,10 @@ export class VirtualSelect { this.toggleAllOptions(); } + onFocusBottom() { + this.closeDropbox(); + } + onResize() { this.setOptionsContainerHeight(true); } @@ -688,9 +700,11 @@ export class VirtualSelect { this.setOptionsPosition(); this.setOptionsTooltip(); - const focusedOption = DomUtils.getElementsBySelector('.focused', this.$dropboxContainer)[0]; - if (focusedOption !== undefined) { - focusedOption.focus(); + if (document.activeElement !== this.$searchInput) { + const focusedOption = DomUtils.getElementsBySelector('.focused', this.$dropboxContainer)[0]; + if (focusedOption !== undefined) { + focusedOption.focus(); + } } } @@ -2982,7 +2996,9 @@ export class VirtualSelect { return; } - $ele.focus(); + if (document.activeElement !== this.$searchInput) { + $ele.focus(); + } DomUtils.toggleClass($ele, 'focused', isFocused); if (isFocused) { From 625233b3190eb8d85fa9829e91bc0204af77d468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Rio?= Date: Thu, 24 Aug 2023 21:10:55 +0100 Subject: [PATCH 07/13] Renamed method. --- src/virtual-select.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/virtual-select.js b/src/virtual-select.js index 41d06e7..041902a 100755 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -403,7 +403,7 @@ export class VirtualSelect { this.addEvent(this.$searchInput, 'input', 'onSearch'); this.addEvent(this.$searchClear, 'click', 'onSearchClear'); this.addEvent(this.$toggleAllButton, 'click', 'onToggleAllOptions'); - this.addEvent(this.$optionsListBottom, 'focus', 'onFocusBottom'); + this.addEvent(this.$optionsListBottom, 'focus', 'onOptionsListBottomFocus'); } /** render methods - end */ @@ -589,7 +589,7 @@ export class VirtualSelect { this.toggleAllOptions(); } - onFocusBottom() { + onOptionsListBottomFocus() { this.closeDropbox(); } From b0b501860c9972b0dc28316707bf1a411b83fbbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Rio?= Date: Thu, 24 Aug 2023 21:27:31 +0100 Subject: [PATCH 08/13] Fixed names references. --- src/sass/partials/virtual-select.scss | 2 +- src/virtual-select.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sass/partials/virtual-select.scss b/src/sass/partials/virtual-select.scss index 55805ac..f77033c 100755 --- a/src/sass/partials/virtual-select.scss +++ b/src/sass/partials/virtual-select.scss @@ -272,7 +272,7 @@ } .vscomp-search-label, -.vscomp-options-bottom, +.vscomp-options-list-bottom, .vscomp-live-region { border: 0; clip: rect(0 0 0 0); diff --git a/src/virtual-select.js b/src/virtual-select.js index 041902a..dc82cf6 100755 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -204,7 +204,7 @@ export class VirtualSelect { this.$search = this.$dropboxContainer.querySelector('.vscomp-search-wrapper'); this.$optionsContainer = this.$dropboxContainer.querySelector('.vscomp-options-container'); this.$optionsList = this.$dropboxContainer.querySelector('.vscomp-options-list'); - this.$optionsListBottom = this.$dropboxContainer.querySelector('.vscomp-bottom-item'); + this.$optionsListBottom = this.$dropboxContainer.querySelector('.vscomp-options-list-bottom'); this.$options = this.$dropboxContainer.querySelector('.vscomp-options'); this.$noOptions = this.$dropboxContainer.querySelector('.vscomp-no-options'); this.$noSearchResults = this.$dropboxContainer.querySelector('.vscomp-no-search-results'); @@ -224,7 +224,7 @@ export class VirtualSelect {
-
+
From 31cc3034db77956cac96e46fab8f8f7922def3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Rio?= Date: Fri, 25 Aug 2023 11:04:55 +0100 Subject: [PATCH 09/13] Disabled input search, and enable noOpts and noSearchOpts for a11y. --- src/sass/partials/virtual-select.scss | 4 ++-- src/virtual-select.js | 30 ++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/sass/partials/virtual-select.scss b/src/sass/partials/virtual-select.scss index f77033c..f5c9228 100755 --- a/src/sass/partials/virtual-select.scss +++ b/src/sass/partials/virtual-select.scss @@ -272,8 +272,8 @@ } .vscomp-search-label, -.vscomp-options-list-bottom, -.vscomp-live-region { +.vscomp-live-region, +.vscomp-dropbox-container-bottom { border: 0; clip: rect(0 0 0 0); height: 1px; diff --git a/src/virtual-select.js b/src/virtual-select.js index dc82cf6..3fcf0bc 100755 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -201,10 +201,10 @@ export class VirtualSelect { this.$hiddenInput = this.$ele.querySelector('.vscomp-hidden-input'); this.$dropbox = this.$dropboxContainer.querySelector('.vscomp-dropbox'); this.$dropboxCloseButton = this.$dropboxContainer.querySelector('.vscomp-dropbox-close-button'); + this.$dropboxContainerBottom = this.$dropboxContainer.querySelector('.vscomp-dropbox-container-bottom'); this.$search = this.$dropboxContainer.querySelector('.vscomp-search-wrapper'); this.$optionsContainer = this.$dropboxContainer.querySelector('.vscomp-options-container'); this.$optionsList = this.$dropboxContainer.querySelector('.vscomp-options-list'); - this.$optionsListBottom = this.$dropboxContainer.querySelector('.vscomp-options-list-bottom'); this.$options = this.$dropboxContainer.querySelector('.vscomp-options'); this.$noOptions = this.$dropboxContainer.querySelector('.vscomp-no-options'); this.$noSearchResults = this.$dropboxContainer.querySelector('.vscomp-no-search-results'); @@ -224,7 +224,6 @@ export class VirtualSelect {
-
@@ -234,6 +233,7 @@ export class VirtualSelect { + `; @@ -403,7 +403,7 @@ export class VirtualSelect { this.addEvent(this.$searchInput, 'input', 'onSearch'); this.addEvent(this.$searchClear, 'click', 'onSearchClear'); this.addEvent(this.$toggleAllButton, 'click', 'onToggleAllOptions'); - this.addEvent(this.$optionsListBottom, 'focus', 'onOptionsListBottomFocus'); + this.addEvent(this.$dropboxContainerBottom, 'focus', 'onDropboxContainerBottomFocus'); } /** render methods - end */ @@ -589,7 +589,7 @@ export class VirtualSelect { this.toggleAllOptions(); } - onOptionsListBottomFocus() { + onDropboxContainerBottomFocus() { this.closeDropbox(); } @@ -692,9 +692,23 @@ export class VirtualSelect { if (!this.allowNewOption || this.hasServerSearch || this.showOptionsOnlyOnSearch) { DomUtils.toggleClass(this.$allWrappers, 'has-no-search-results', hasNoSearchResults); + if (hasNoSearchResults) { + DomUtils.setAttr(this.$noSearchResults, 'tabindex', '0'); + DomUtils.setAttr(this.$noSearchResults, 'aria-hidden', 'false'); + } else { + DomUtils.setAttr(this.$noSearchResults, 'tabindex', '-1'); + DomUtils.setAttr(this.$noSearchResults, 'aria-hidden', 'true'); + } } DomUtils.toggleClass(this.$allWrappers, 'has-no-options', hasNoOptions); + if (hasNoOptions) { + DomUtils.setAttr(this.$noOptions, 'tabindex', '0'); + DomUtils.setAttr(this.$noOptions, 'aria-hidden', 'false'); + } else { + DomUtils.setAttr(this.$noOptions, 'tabindex', '-1'); + DomUtils.setAttr(this.$noOptions, 'aria-hidden', 'true'); + } this.setOptionAttr(); this.setOptionsPosition(); @@ -2292,9 +2306,15 @@ export class VirtualSelect { focusSearchInput() { const $ele = this.$searchInput; + const hasNoOptions = !this.options.length && !this.hasServerSearch; if ($ele) { - $ele.focus(); + if (hasNoOptions) { + DomUtils.setAttr($ele, 'disabled', ''); + this.$noOptions.focus(); + } else { + $ele.focus(); + } } } From f45d6bc0b3009a235ecb4117a10ac0b3fae72f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Rio?= Date: Sun, 27 Aug 2023 10:51:16 +0100 Subject: [PATCH 10/13] Improved Keyboard navigation and screen reader readability. --- src/sass/partials/virtual-select.scss | 1 + src/virtual-select.js | 57 +++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/sass/partials/virtual-select.scss b/src/sass/partials/virtual-select.scss index f5c9228..1fcbd56 100755 --- a/src/sass/partials/virtual-select.scss +++ b/src/sass/partials/virtual-select.scss @@ -273,6 +273,7 @@ .vscomp-search-label, .vscomp-live-region, +.vscomp-dropbox-container-top, .vscomp-dropbox-container-bottom { border: 0; clip: rect(0 0 0 0); diff --git a/src/virtual-select.js b/src/virtual-select.js index 3fcf0bc..ca73e98 100755 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -202,6 +202,7 @@ export class VirtualSelect { this.$dropbox = this.$dropboxContainer.querySelector('.vscomp-dropbox'); this.$dropboxCloseButton = this.$dropboxContainer.querySelector('.vscomp-dropbox-close-button'); this.$dropboxContainerBottom = this.$dropboxContainer.querySelector('.vscomp-dropbox-container-bottom'); + this.$dropboxContainerTop = this.$dropboxContainer.querySelector('.vscomp-dropbox-container-top'); this.$search = this.$dropboxContainer.querySelector('.vscomp-search-wrapper'); this.$optionsContainer = this.$dropboxContainer.querySelector('.vscomp-options-container'); this.$optionsList = this.$dropboxContainer.querySelector('.vscomp-options-list'); @@ -216,6 +217,7 @@ export class VirtualSelect { const $wrapper = this.dropboxWrapper !== 'self' ? document.querySelector(this.dropboxWrapper) : null; const html = `
+
@@ -288,7 +290,7 @@ export class VirtualSelect { let description = ''; let groupIndexText = ''; let ariaLabel = ''; - let tabIndexValue = '0'; + let tabIndexValue = '-1'; const isSelected = convertToBoolean(d.isSelected); let ariaDisabledText = ''; @@ -297,6 +299,7 @@ export class VirtualSelect { } if (d.isFocused) { + tabIndexValue = '0'; optionClasses += ' focused'; } @@ -306,7 +309,6 @@ export class VirtualSelect { } if (d.isGroupTitle) { - tabIndexValue = '-1'; optionClasses += ' group-title'; if (disableOptionGroupCheckbox) { @@ -403,7 +405,8 @@ export class VirtualSelect { this.addEvent(this.$searchInput, 'input', 'onSearch'); this.addEvent(this.$searchClear, 'click', 'onSearchClear'); this.addEvent(this.$toggleAllButton, 'click', 'onToggleAllOptions'); - this.addEvent(this.$dropboxContainerBottom, 'focus', 'onDropboxContainerBottomFocus'); + this.addEvent(this.$dropboxContainerBottom, 'focus', 'onDropboxContainerTopOrBottomFocus'); + this.addEvent(this.$dropboxContainerTop, 'focus', 'onDropboxContainerTopOrBottomFocus'); } /** render methods - end */ @@ -454,9 +457,14 @@ export class VirtualSelect { const key = e.which || e.keyCode; const method = keyDownMethodMapping[key]; - // if (document.activeElement === this.$searchInput && (key === 9 || (e.shiftKey && key === 9))) { if (document.activeElement === this.$searchInput && (e.shiftKey && key === 9)) { - this.closeDropbox(); + e.preventDefault(); + this.$dropboxContainerTop.focus(); + return; + } + if (document.activeElement === this.$searchInput && key === 9) { + e.preventDefault(); + this.focusFirstVisibleOption(); return; } // Handle the Escape key when showing the dropdown as a popup, closing it @@ -589,7 +597,7 @@ export class VirtualSelect { this.toggleAllOptions(); } - onDropboxContainerBottomFocus() { + onDropboxContainerTopOrBottomFocus() { this.closeDropbox(); } @@ -2220,7 +2228,7 @@ export class VirtualSelect { DomUtils.addClass(this.$body, 'vscomp-popup-active'); this.isPopupActive = true; } else { - this.focusSearchInput(); + this.focusElementOnOpen(); } DomUtils.dispatchEvent(this.$ele, 'afterOpen'); @@ -2306,6 +2314,14 @@ export class VirtualSelect { focusSearchInput() { const $ele = this.$searchInput; + + if ($ele) { + $ele.focus(); + } + } + + focusElementOnOpen() { + const $ele = this.$searchInput; const hasNoOptions = !this.options.length && !this.hasServerSearch; if ($ele) { @@ -2315,6 +2331,29 @@ export class VirtualSelect { } else { $ele.focus(); } + } else { + const $focusableEle = this.$dropbox.querySelector('[tabindex="0"]'); + const optIndex = DomUtils.getData($focusableEle, 'index'); + + if (optIndex !== undefined) { + this.focusOption({ direction: 'next' }); + } else if ($focusableEle) { + $focusableEle.focus(); + } else { + this.focusFirstVisibleOption(); + } + } + } + + focusFirstVisibleOption() { + const $focusableEle = this.$optionsContainer.querySelector(`[data-index='${this.getFirstVisibleOptionIndex()}']`); + if ($focusableEle !== undefined) { + DomUtils.setAttr($focusableEle, 'tabindex', '0'); + this.$optionsContainer.scrollTop = this.optionHeight * this.getFirstVisibleOptionIndex(); + this.focusOption({ + focusFirst: true, + }); + $focusableEle.focus(); } } @@ -3016,10 +3055,12 @@ export class VirtualSelect { return; } + DomUtils.toggleClass($ele, 'focused', isFocused); + DomUtils.setAttr($ele, 'tabindex', isFocused ? '0' : '-1'); + if (document.activeElement !== this.$searchInput) { $ele.focus(); } - DomUtils.toggleClass($ele, 'focused', isFocused); if (isFocused) { DomUtils.setAria(this.$wrapper, 'activedescendant', $ele.id); From 0b1ee376570ad6fa38bc9b767cadeef3cd671ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Rio?= Date: Mon, 28 Aug 2023 11:22:41 +0100 Subject: [PATCH 11/13] Fixed keyboard navigation when there are no results to show. --- src/virtual-select.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/virtual-select.js b/src/virtual-select.js index ca73e98..b3f50c4 100755 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -2346,14 +2346,19 @@ export class VirtualSelect { } focusFirstVisibleOption() { - const $focusableEle = this.$optionsContainer.querySelector(`[data-index='${this.getFirstVisibleOptionIndex()}']`); - if ($focusableEle !== undefined) { + let $focusableEle = this.$optionsContainer.querySelector(`[data-index='${this.getFirstVisibleOptionIndex()}']`); + if ($focusableEle) { DomUtils.setAttr($focusableEle, 'tabindex', '0'); this.$optionsContainer.scrollTop = this.optionHeight * this.getFirstVisibleOptionIndex(); this.focusOption({ focusFirst: true, }); $focusableEle.focus(); + } else { + $focusableEle = this.$dropbox.querySelector('[tabindex="0"]'); + if ($focusableEle) { + $focusableEle.focus(); + } } } From 55fe18027607b4d6d76850f6bc22c60920090c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Rio?= Date: Mon, 28 Aug 2023 16:33:58 +0100 Subject: [PATCH 12/13] Grant groupTitle is not focused using keyboard navigation. --- src/virtual-select.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/virtual-select.js b/src/virtual-select.js index b3f50c4..d9a7c05 100755 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -2347,7 +2347,12 @@ export class VirtualSelect { focusFirstVisibleOption() { let $focusableEle = this.$optionsContainer.querySelector(`[data-index='${this.getFirstVisibleOptionIndex()}']`); + if ($focusableEle) { + if (DomUtils.hasClass($focusableEle, '.group-title')) { + $focusableEle = this.getSibling($focusableEle, 'next'); + } + DomUtils.setAttr($focusableEle, 'tabindex', '0'); this.$optionsContainer.scrollTop = this.optionHeight * this.getFirstVisibleOptionIndex(); this.focusOption({ From 80c927ed21b5d1f63b96c730aafa39c8b8f955f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Rio?= Date: Mon, 28 Aug 2023 17:48:58 +0100 Subject: [PATCH 13/13] fixing an issue by getting item from a class selector. --- src/virtual-select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/virtual-select.js b/src/virtual-select.js index d9a7c05..5866abe 100755 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -2349,7 +2349,7 @@ export class VirtualSelect { let $focusableEle = this.$optionsContainer.querySelector(`[data-index='${this.getFirstVisibleOptionIndex()}']`); if ($focusableEle) { - if (DomUtils.hasClass($focusableEle, '.group-title')) { + if (DomUtils.hasClass($focusableEle, 'group-title')) { $focusableEle = this.getSibling($focusableEle, 'next'); }