From e064296e044e20d91616926c95c7dede1d3c80f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Louren=C3=A7o?= Date: Fri, 6 Feb 2026 14:45:27 +0000 Subject: [PATCH 1/3] Fixed event capture from bubbling phase to capturing --- src/utils/dom-utils.js | 5 +++-- src/virtual-select.js | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/utils/dom-utils.js b/src/utils/dom-utils.js index 729bdc9..36b8285 100644 --- a/src/utils/dom-utils.js +++ b/src/utils/dom-utils.js @@ -238,8 +238,9 @@ export class DomUtils { * @param {HTMLElement} $ele * @param {string} events * @param {Function} callback + * @param {boolean} capture */ - static addEvent($ele, events, callback) { + static addEvent($ele, events, callback, capture) { if (!$ele) { return; } @@ -250,7 +251,7 @@ export class DomUtils { const $eleArray = DomUtils.getElements($ele); $eleArray.forEach(($this) => { - $this.addEventListener(event, callback); + $this.addEventListener(event, callback, { capture }); }); }); } diff --git a/src/virtual-select.js b/src/virtual-select.js index 0dd95e3..2036906 100644 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -471,7 +471,7 @@ export class VirtualSelect { /** dom event methods - start */ addEvents() { - this.addEvent(document, 'click', 'onDocumentClick'); + this.addEvent(document, 'click', 'onDocumentClick', true); this.addEvent(this.$allWrappers, 'keydown', 'onKeyDown'); this.addEvent(this.$toggleButton, 'click keydown', 'onToggleButtonPress'); this.addEvent(this.$clearButton, 'click keydown', 'onClearButtonClick'); @@ -484,7 +484,7 @@ export class VirtualSelect { this.addMutationObserver(); } - addEvent($ele, events, method) { + addEvent($ele, events, method, capture = false) { if (!$ele) { return; } @@ -500,7 +500,7 @@ export class VirtualSelect { this.events[eventsKey] = callback; } - DomUtils.addEvent($ele, event, callback); + DomUtils.addEvent($ele, event, callback, capture); }); } From a1c0b0896d1314c2286d4aae42a1183f9b623029 Mon Sep 17 00:00:00 2001 From: David Lourenco Date: Fri, 31 Jul 2026 12:46:36 +0100 Subject: [PATCH 2/3] Fixed keyboard navigation to include group headers as selectable --- src/virtual-select.js | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/virtual-select.js b/src/virtual-select.js index c0680c7..2c4ace8 100644 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -360,6 +360,9 @@ export class VirtualSelect { if (disableOptionGroupCheckbox) { leftSection = ''; + } else if (this.multiple) { + const groupLabel = this.secureText(Utils.getString(d.label)); + ariaLabel = `aria-label="${groupLabel}, ${this.secureText(this.selectAllText)}"`; } } @@ -756,7 +759,7 @@ export class VirtualSelect { const $ele = e.target.closest('.vscomp-option'); if ($ele && this.isOpened()) { - if (DomUtils.hasClass($ele, 'disabled') || DomUtils.hasClass($ele, 'group-title')) { + if (this.shouldSkipOptionInNavigation($ele)) { this.removeOptionFocus(); } else { this.focusOption({ $option: $ele }); @@ -2570,6 +2573,22 @@ export class VirtualSelect { return `${this.optionsCount * this.optionHeight}px`; } + isSelectableGroupTitle($ele) { + return $ele && DomUtils.hasClass($ele, 'group-title') && this.multiple && !this.disableOptionGroupCheckbox; + } + + shouldSkipOptionInNavigation($ele) { + if (!$ele) { + return true; + } + + if (DomUtils.hasClass($ele, 'disabled')) { + return true; + } + + return DomUtils.hasClass($ele, 'group-title') && !this.isSelectableGroupTitle($ele); + } + /** getting next/prev valid option element */ getSibling($ele, direction) { const propName = direction === 'next' ? 'nextElementSibling' : 'previousElementSibling'; @@ -2579,7 +2598,7 @@ export class VirtualSelect { if ($sibling) { $sibling = $sibling[propName]; } - } while (DomUtils.hasClass($sibling, 'disabled') || DomUtils.hasClass($sibling, 'group-title')); + } while (this.shouldSkipOptionInNavigation($sibling)); return $sibling; } @@ -2862,7 +2881,7 @@ export class VirtualSelect { let $focusableEle = this.$optionsContainer.querySelector(`[data-index='${this.getFirstVisibleOptionIndex()}']`); if ($focusableEle) { - if (DomUtils.hasClass($focusableEle, 'group-title')) { + if (this.shouldSkipOptionInNavigation($focusableEle)) { $focusableEle = this.getSibling($focusableEle, 'next'); } @@ -2893,7 +2912,7 @@ export class VirtualSelect { `.vscomp-option[data-visible-index="${firstVisibleOptionIndex}"]`, ); - if (DomUtils.hasClass($newFocusedEle, 'disabled') || DomUtils.hasClass($newFocusedEle, 'group-title')) { + if (this.shouldSkipOptionInNavigation($newFocusedEle)) { $newFocusedEle = this.getSibling($newFocusedEle, 'next'); } } else { @@ -3031,7 +3050,18 @@ export class VirtualSelect { } selectFocusedOption() { - this.selectOption(this.$dropboxContainer.querySelector('.vscomp-option.focused')); + const $focusedEle = this.$dropboxContainer.querySelector('.vscomp-option.focused'); + + if (!$focusedEle) { + return; + } + + if (this.isSelectableGroupTitle($focusedEle)) { + this.onGroupTitleClick($focusedEle); + return; + } + + this.selectOption($focusedEle); } selectRangeOptions(lastSelectedOptionIndex, selectedIndex) { From 575af6e1791ca946546e1757721e20fd1dd86d74 Mon Sep 17 00:00:00 2001 From: David Lourenco Date: Fri, 31 Jul 2026 14:43:22 +0100 Subject: [PATCH 3/3] Fixed error that blocked the page --- src/virtual-select.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/virtual-select.js b/src/virtual-select.js index 2c4ace8..8775dae 100644 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -2579,7 +2579,7 @@ export class VirtualSelect { shouldSkipOptionInNavigation($ele) { if (!$ele) { - return true; + return false; } if (DomUtils.hasClass($ele, 'disabled')) { @@ -2885,12 +2885,14 @@ export class VirtualSelect { $focusableEle = this.getSibling($focusableEle, 'next'); } - DomUtils.setAttr($focusableEle, 'tabindex', '0'); - this.$optionsContainer.scrollTop = this.optionHeight * this.getFirstVisibleOptionIndex(); - this.focusOption({ - focusFirst: true, - }); - $focusableEle.focus(); + 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) {