@gitlab/ui 113.4.0 → 113.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/components/base/new_dropdowns/base_dropdown/base_dropdown.js +1 -1
- package/dist/components/base/new_dropdowns/listbox/listbox.js +145 -44
- package/dist/components/base/new_dropdowns/listbox/listbox_search_input.js +2 -1
- package/package.json +1 -1
- package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.vue +1 -1
- package/src/components/base/new_dropdowns/listbox/listbox.vue +321 -186
- package/src/components/base/new_dropdowns/listbox/listbox_search_input.vue +2 -0
- package/translations.js +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [113.5.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v113.4.0...v113.5.0) (2025-05-12)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **GlCollapsibleListbox:** improve a11y support ([13453e7](https://gitlab.com/gitlab-org/gitlab-ui/commit/13453e73c28ddc3acf22951738acfeb9e2ade4c0))
|
|
7
|
+
|
|
1
8
|
# [113.4.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v113.3.2...v113.4.0) (2025-05-12)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -496,7 +496,7 @@ var script = {
|
|
|
496
496
|
const __vue_script__ = script;
|
|
497
497
|
|
|
498
498
|
/* template */
|
|
499
|
-
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{directives:[{name:"outside",rawName:"v-outside.click.focusin",value:(_vm.close),expression:"close",modifiers:{"click":true,"focusin":true}}],class:[_vm.$options.BASE_DROPDOWN_CLASS, { '!gl-block': _vm.block }]},[_c(_vm.toggleComponent,_vm._g(_vm._b({ref:"toggle",tag:"component",attrs:{"id":_vm.toggleId,"data-testid":"base-dropdown-toggle"},on:{"keydown":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"esc",27,$event.key,["Esc","Escape"])){ return null; }$event.stopPropagation();$event.preventDefault();return _vm.close.apply(null, arguments)}}},'component',_vm.toggleAttributes,false),_vm.toggleListeners),[_vm._t("toggle",function(){return [_c('span',{staticClass:"gl-new-dropdown-button-text",class:{ 'gl-sr-only': _vm.textSrOnly }},[_vm._v("\n "+_vm._s(_vm.toggleText)+"\n ")]),_vm._v(" "),(!_vm.noCaret)?_c('gl-icon',{staticClass:"gl-button-icon gl-new-dropdown-chevron",attrs:{"name":"chevron-down"}}):_vm._e()]})],2),_vm._v(" "),_c('div',{ref:"content",staticClass:"gl-new-dropdown-panel",class:_vm.panelClasses,attrs:{"id":_vm.baseDropdownId,"data-testid":"base-dropdown-menu"},on:{"keydown":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"esc",27,$event.key,["Esc","Escape"])){ return null; }$event.stopPropagation();$event.preventDefault();return _vm.closeAndFocus.apply(null, arguments)}}},[_c('div',{ref:"dropdownArrow",staticClass:"gl-new-dropdown-arrow"}),_vm._v(" "),_c('div',{staticClass:"gl-new-dropdown-inner"},[_vm._t("default")],2)])],1)};
|
|
499
|
+
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{directives:[{name:"outside",rawName:"v-outside.click.focusin",value:(_vm.close),expression:"close",modifiers:{"click":true,"focusin":true}}],class:[_vm.$options.BASE_DROPDOWN_CLASS, { '!gl-block': _vm.block }]},[_c(_vm.toggleComponent,_vm._g(_vm._b({ref:"toggle",tag:"component",attrs:{"id":_vm.toggleId,"data-testid":"base-dropdown-toggle"},on:{"keydown":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"esc",27,$event.key,["Esc","Escape"])){ return null; }$event.stopPropagation();$event.preventDefault();return _vm.close.apply(null, arguments)}}},'component',_vm.toggleAttributes,false),_vm.toggleListeners),[_vm._t("toggle",function(){return [_c('span',{staticClass:"gl-new-dropdown-button-text",class:{ 'gl-sr-only': _vm.textSrOnly }},[_vm._v("\n "+_vm._s(_vm.toggleText)+"\n ")]),_vm._v(" "),(!_vm.noCaret)?_c('gl-icon',{staticClass:"gl-button-icon gl-new-dropdown-chevron",attrs:{"name":"chevron-down"}}):_vm._e()]})],2),_vm._v(" "),_c('div',{ref:"content",staticClass:"gl-new-dropdown-panel",class:_vm.panelClasses,attrs:{"id":_vm.baseDropdownId,"data-testid":"base-dropdown-menu"},on:{"keydown":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"esc",27,$event.key,["Esc","Escape"])){ return null; }$event.stopPropagation();$event.preventDefault();return _vm.closeAndFocus.apply(null, arguments)}}},[_c('div',{ref:"dropdownArrow",staticClass:"gl-new-dropdown-arrow"}),_vm._v(" "),_c('div',{staticClass:"gl-new-dropdown-inner"},[_vm._t("default",null,{"visible":_vm.visible})],2)])],1)};
|
|
500
500
|
var __vue_staticRenderFns__ = [];
|
|
501
501
|
|
|
502
502
|
/* style */
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import clamp from 'lodash/clamp';
|
|
2
2
|
import uniqueId from 'lodash/uniqueId';
|
|
3
3
|
import { stopEvent } from '../../../../utils/utils';
|
|
4
|
-
import { GL_DROPDOWN_SHOWN, GL_DROPDOWN_HIDDEN, POSITION_ABSOLUTE, POSITION_FIXED, GL_DROPDOWN_CONTENTS_CLASS,
|
|
4
|
+
import { GL_DROPDOWN_SHOWN, GL_DROPDOWN_HIDDEN, POSITION_ABSOLUTE, POSITION_FIXED, GL_DROPDOWN_CONTENTS_CLASS, ARROW_UP, ENTER, ARROW_DOWN, END, HOME } from '../constants';
|
|
5
5
|
import { buttonCategoryOptions, dropdownVariantOptions, buttonSizeOptions, dropdownPlacements } from '../../../../utils/constants';
|
|
6
6
|
import GlButton from '../../button/button';
|
|
7
7
|
import GlLoadingIcon from '../../loading_icon/loading_icon';
|
|
8
8
|
import GlIntersectionObserver from '../../../utilities/intersection_observer/intersection_observer';
|
|
9
9
|
import GlSearchBoxByType from '../../search_box_by_type/search_box_by_type';
|
|
10
10
|
import GlBaseDropdown from '../base_dropdown/base_dropdown';
|
|
11
|
-
import { translatePlural } from '../../../../utils/i18n';
|
|
11
|
+
import { translatePlural, translate } from '../../../../utils/i18n';
|
|
12
12
|
import GlListboxItem from './listbox_item';
|
|
13
13
|
import GlListboxSearchInput from './listbox_search_input';
|
|
14
14
|
import GlListboxGroup from './listbox_group';
|
|
@@ -342,13 +342,22 @@ var script = {
|
|
|
342
342
|
return {
|
|
343
343
|
selectedValues: [],
|
|
344
344
|
listboxId: uniqueId('listbox-'),
|
|
345
|
+
searchInputId: uniqueId('listbox-search-input-'),
|
|
345
346
|
nextFocusedItemIndex: null,
|
|
346
347
|
searchStr: '',
|
|
347
348
|
topBoundaryVisible: true,
|
|
348
|
-
bottomBoundaryVisible: true
|
|
349
|
+
bottomBoundaryVisible: true,
|
|
350
|
+
activeItemId: null,
|
|
351
|
+
itemIds: new Map()
|
|
349
352
|
};
|
|
350
353
|
},
|
|
351
354
|
computed: {
|
|
355
|
+
ariaLabelledByID() {
|
|
356
|
+
if (this.searchable) {
|
|
357
|
+
return this.searchInputId;
|
|
358
|
+
}
|
|
359
|
+
return this.listAriaLabelledBy || this.headerId || this.toggleIdComputed;
|
|
360
|
+
},
|
|
352
361
|
toggleIdComputed() {
|
|
353
362
|
return this.toggleId || uniqueId('dropdown-toggle-btn-');
|
|
354
363
|
},
|
|
@@ -451,6 +460,9 @@ var script = {
|
|
|
451
460
|
showIntersectionObserver() {
|
|
452
461
|
return this.infiniteScroll && !this.infiniteScrollLoading && !this.loading && !this.searching;
|
|
453
462
|
},
|
|
463
|
+
isBusy() {
|
|
464
|
+
return this.infiniteScrollLoading || this.loading || this.searching;
|
|
465
|
+
},
|
|
454
466
|
hasCustomToggle() {
|
|
455
467
|
return Boolean(this.$scopedSlots.toggle);
|
|
456
468
|
},
|
|
@@ -469,6 +481,18 @@ var script = {
|
|
|
469
481
|
},
|
|
470
482
|
hasFooter() {
|
|
471
483
|
return Boolean(this.$scopedSlots.footer);
|
|
484
|
+
},
|
|
485
|
+
loadingAnnouncementText() {
|
|
486
|
+
if (this.infiniteScrollLoading) {
|
|
487
|
+
return translate('GlCollapsibleListbox.loadingAnnouncementText.loadingMoreItems', 'Loading more items');
|
|
488
|
+
}
|
|
489
|
+
if (this.searching) {
|
|
490
|
+
return translate('GlCollapsibleListbox.loadingAnnouncementText.searching', 'Searching');
|
|
491
|
+
}
|
|
492
|
+
if (this.loading) {
|
|
493
|
+
return translate('GlCollapsibleListbox.loadingAnnouncementText.loadingItems', 'Loading items');
|
|
494
|
+
}
|
|
495
|
+
return '';
|
|
472
496
|
}
|
|
473
497
|
},
|
|
474
498
|
watch: {
|
|
@@ -561,6 +585,9 @@ var script = {
|
|
|
561
585
|
*/
|
|
562
586
|
if (this.searchHasOptions) {
|
|
563
587
|
this.nextFocusedItemIndex = 0;
|
|
588
|
+
// Set activeItemId for the first item
|
|
589
|
+
const firstItem = this.flattenedOptions[0];
|
|
590
|
+
this.activeItemId = this.generateItemId(firstItem);
|
|
564
591
|
}
|
|
565
592
|
} else {
|
|
566
593
|
var _this$selectedIndices;
|
|
@@ -582,6 +609,21 @@ var script = {
|
|
|
582
609
|
this.$emit(GL_DROPDOWN_HIDDEN);
|
|
583
610
|
this.nextFocusedItemIndex = null;
|
|
584
611
|
},
|
|
612
|
+
getNextIndex(currentIndex, keyCode, totalLength) {
|
|
613
|
+
// For UP: move up or wrap to end
|
|
614
|
+
if (keyCode === ARROW_UP) {
|
|
615
|
+
return currentIndex > 0 ? currentIndex - 1 : totalLength - 1;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// For DOWN: move down or wrap to start
|
|
619
|
+
return currentIndex < totalLength - 1 ? currentIndex + 1 : 0;
|
|
620
|
+
},
|
|
621
|
+
handleListNavigation(keyCode, elements) {
|
|
622
|
+
var _this$nextFocusedItem;
|
|
623
|
+
const currentIndex = (_this$nextFocusedItem = this.nextFocusedItemIndex) !== null && _this$nextFocusedItem !== void 0 ? _this$nextFocusedItem : -1;
|
|
624
|
+
const nextIndex = this.getNextIndex(currentIndex, keyCode, elements.length);
|
|
625
|
+
this.focusItem(nextIndex, elements, this.searchable);
|
|
626
|
+
},
|
|
585
627
|
onKeydown(event) {
|
|
586
628
|
const {
|
|
587
629
|
code,
|
|
@@ -589,46 +631,54 @@ var script = {
|
|
|
589
631
|
} = event;
|
|
590
632
|
const elements = this.getFocusableListItemElements();
|
|
591
633
|
if (elements.length < 1) return;
|
|
592
|
-
let stop = true;
|
|
593
634
|
const isSearchInput = target.matches(SEARCH_INPUT_SELECTOR);
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
if (isSearchInput) {
|
|
601
|
-
return;
|
|
602
|
-
}
|
|
603
|
-
this.focusItem(elements.length - 1, elements);
|
|
604
|
-
} else if (code === ARROW_UP) {
|
|
605
|
-
if (isSearchInput) {
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
if (this.searchable && elements.indexOf(target) === 0) {
|
|
609
|
-
this.focusSearchInput();
|
|
610
|
-
if (!this.searchHasOptions) {
|
|
611
|
-
this.nextFocusedItemIndex = null;
|
|
635
|
+
let stop = true;
|
|
636
|
+
switch (code) {
|
|
637
|
+
case HOME:
|
|
638
|
+
// Jump to first item if searchable or not in search input
|
|
639
|
+
if (this.searchable || !isSearchInput) {
|
|
640
|
+
this.focusItem(0, elements, this.searchable);
|
|
612
641
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
642
|
+
break;
|
|
643
|
+
case END:
|
|
644
|
+
// Jump to last item if searchable or not in search input
|
|
645
|
+
if (this.searchable || !isSearchInput) {
|
|
646
|
+
this.focusItem(elements.length - 1, elements, this.searchable);
|
|
647
|
+
}
|
|
648
|
+
break;
|
|
649
|
+
case ARROW_UP:
|
|
650
|
+
// Let default behavior work for non-searchable input
|
|
651
|
+
if (isSearchInput && !this.searchable) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
this.handleListNavigation(ARROW_UP, elements);
|
|
655
|
+
break;
|
|
656
|
+
case ARROW_DOWN:
|
|
657
|
+
// Focus first item from search input, otherwise navigate down
|
|
658
|
+
if (isSearchInput && !this.searchable) {
|
|
659
|
+
this.focusItem(0, elements);
|
|
660
|
+
} else {
|
|
661
|
+
this.handleListNavigation(ARROW_DOWN, elements);
|
|
662
|
+
}
|
|
663
|
+
break;
|
|
664
|
+
case ENTER:
|
|
665
|
+
if (isSearchInput) {
|
|
666
|
+
// Toggle selection of highlighted item if one exists
|
|
667
|
+
if (elements.length > 0 && this.nextFocusedItemIndex !== null) {
|
|
668
|
+
const highlightedItem = this.flattenedOptions[this.nextFocusedItemIndex];
|
|
669
|
+
this.onSelect(highlightedItem, !this.isSelected(highlightedItem));
|
|
670
|
+
}
|
|
671
|
+
} else {
|
|
672
|
+
stop = false;
|
|
673
|
+
}
|
|
674
|
+
break;
|
|
675
|
+
default:
|
|
676
|
+
// Allow default behavior for unhandled keys
|
|
677
|
+
stop = false;
|
|
678
|
+
break;
|
|
631
679
|
}
|
|
680
|
+
|
|
681
|
+
// Prevent default behavior for handled keys
|
|
632
682
|
if (stop) {
|
|
633
683
|
stopEvent(event);
|
|
634
684
|
}
|
|
@@ -647,9 +697,25 @@ var script = {
|
|
|
647
697
|
this.focusItem(nextIndex, elements);
|
|
648
698
|
},
|
|
649
699
|
focusItem(index, elements) {
|
|
650
|
-
|
|
700
|
+
let keepSearchFocused = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
651
701
|
this.nextFocusedItemIndex = index;
|
|
652
|
-
|
|
702
|
+
|
|
703
|
+
// Always update the activeItemId when focus changes
|
|
704
|
+
const item = this.flattenedOptions[index];
|
|
705
|
+
if (item) {
|
|
706
|
+
this.activeItemId = this.generateItemId(item);
|
|
707
|
+
} else {
|
|
708
|
+
this.activeItemId = null;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// If we're not keeping the search focused, focus the item
|
|
712
|
+
if (!keepSearchFocused) {
|
|
713
|
+
var _elements$index;
|
|
714
|
+
(_elements$index = elements[index]) === null || _elements$index === void 0 ? void 0 : _elements$index.focus();
|
|
715
|
+
}
|
|
716
|
+
this.$nextTick(() => {
|
|
717
|
+
this.scrollActiveItemIntoView();
|
|
718
|
+
});
|
|
653
719
|
},
|
|
654
720
|
focusSearchInput() {
|
|
655
721
|
this.$refs.searchBox.focusInput();
|
|
@@ -767,7 +833,40 @@ var script = {
|
|
|
767
833
|
}
|
|
768
834
|
this.scrollObserver = observer;
|
|
769
835
|
},
|
|
770
|
-
isOption
|
|
836
|
+
isOption,
|
|
837
|
+
generateItemId(item) {
|
|
838
|
+
const key = item.value === null ? ITEM_NULL_KEY : item.value;
|
|
839
|
+
if (!this.itemIds.has(key)) {
|
|
840
|
+
this.itemIds.set(key, uniqueId('listbox-item-'));
|
|
841
|
+
}
|
|
842
|
+
return this.itemIds.get(key);
|
|
843
|
+
},
|
|
844
|
+
scrollActiveItemIntoView() {
|
|
845
|
+
const listContainer = this.$refs.list;
|
|
846
|
+
if (!this.activeItemId || !this.searchable || !listContainer) return;
|
|
847
|
+
const activeElement = document.getElementById(this.activeItemId);
|
|
848
|
+
if (!activeElement) return;
|
|
849
|
+
const containerRect = listContainer.getBoundingClientRect();
|
|
850
|
+
const itemRect = activeElement.getBoundingClientRect();
|
|
851
|
+
const itemTop = activeElement.offsetTop;
|
|
852
|
+
const padding = 30;
|
|
853
|
+
|
|
854
|
+
// If item is above the visible area
|
|
855
|
+
if (itemRect.top < containerRect.top) {
|
|
856
|
+
listContainer.scrollTo({
|
|
857
|
+
top: itemTop - padding,
|
|
858
|
+
behavior: 'smooth'
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// If item is below the visible area
|
|
863
|
+
else if (itemRect.bottom > containerRect.bottom) {
|
|
864
|
+
listContainer.scrollTo({
|
|
865
|
+
top: itemTop - containerRect.height + activeElement.offsetHeight + padding,
|
|
866
|
+
behavior: 'smooth'
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
}
|
|
771
870
|
}
|
|
772
871
|
};
|
|
773
872
|
|
|
@@ -775,7 +874,9 @@ var script = {
|
|
|
775
874
|
const __vue_script__ = script;
|
|
776
875
|
|
|
777
876
|
/* template */
|
|
778
|
-
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-base-dropdown',{ref:"baseDropdown",attrs:{"aria-haspopup":"listbox","aria-labelledby":_vm.toggleAriaLabelledBy,"block":_vm.block,"toggle-id":_vm.toggleIdComputed,"toggle-text":_vm.listboxToggleText,"toggle-class":_vm.toggleButtonClasses,"text-sr-only":_vm.textSrOnly,"category":_vm.category,"variant":_vm.variant,"size":_vm.size,"icon":_vm.icon,"disabled":_vm.disabled,"loading":_vm.loading,"no-caret":_vm.noCaret,"placement":_vm.placement,"offset":_vm.dropdownOffset,"fluid-width":_vm.fluidWidth,"positioning-strategy":_vm.positioningStrategy},on:_vm._d({},[_vm.$options.events.GL_DROPDOWN_SHOWN,_vm.onShow,_vm.$options.events.GL_DROPDOWN_HIDDEN,_vm.onHide]),scopedSlots:_vm._u([(_vm.hasCustomToggle)?{key:"toggle",fn:function(){return [_vm._t("toggle")]},proxy:true}:null
|
|
877
|
+
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-base-dropdown',{ref:"baseDropdown",attrs:{"aria-haspopup":"listbox","aria-labelledby":_vm.toggleAriaLabelledBy,"block":_vm.block,"toggle-id":_vm.toggleIdComputed,"toggle-text":_vm.listboxToggleText,"toggle-class":_vm.toggleButtonClasses,"text-sr-only":_vm.textSrOnly,"category":_vm.category,"variant":_vm.variant,"size":_vm.size,"icon":_vm.icon,"disabled":_vm.disabled,"loading":_vm.loading,"no-caret":_vm.noCaret,"placement":_vm.placement,"offset":_vm.dropdownOffset,"fluid-width":_vm.fluidWidth,"positioning-strategy":_vm.positioningStrategy},on:_vm._d({},[_vm.$options.events.GL_DROPDOWN_SHOWN,_vm.onShow,_vm.$options.events.GL_DROPDOWN_HIDDEN,_vm.onHide]),scopedSlots:_vm._u([(_vm.hasCustomToggle)?{key:"toggle",fn:function(){return [_vm._t("toggle")]},proxy:true}:null,{key:"default",fn:function(ref){
|
|
878
|
+
var visible = ref.visible;
|
|
879
|
+
return [(_vm.headerText)?_c('div',{staticClass:"gl-flex gl-min-h-8 gl-items-center !gl-p-4",class:_vm.$options.HEADER_ITEMS_BORDER_CLASSES},[_c('div',{staticClass:"gl-grow gl-pr-2 gl-text-sm gl-font-bold gl-text-strong",attrs:{"id":_vm.headerId,"data-testid":"listbox-header-text"}},[_vm._v("\n "+_vm._s(_vm.headerText)+"\n ")]),_vm._v(" "),(_vm.showResetButton)?_c('gl-button',{staticClass:"!gl-m-0 !gl-w-auto gl-max-w-1/2 gl-flex-shrink-0 gl-text-ellipsis !gl-px-2 !gl-text-sm focus:!gl-focus-inset",attrs:{"category":"tertiary","size":"small","data-testid":"listbox-reset-button"},on:{"click":_vm.onResetButtonClicked}},[_vm._v("\n "+_vm._s(_vm.resetButtonLabel)+"\n ")]):_vm._e(),_vm._v(" "),(_vm.showSelectAllButton)?_c('gl-button',{staticClass:"!gl-m-0 !gl-w-auto gl-max-w-1/2 gl-flex-shrink-0 gl-text-ellipsis !gl-px-2 !gl-text-sm focus:!gl-focus-inset",attrs:{"category":"tertiary","size":"small","data-testid":"listbox-select-all-button"},on:{"click":_vm.onSelectAllButtonClicked}},[_vm._v("\n "+_vm._s(_vm.showSelectAllButtonLabel)+"\n ")]):_vm._e()],1):_vm._e(),_vm._v(" "),(_vm.searchable)?_c('div',{class:_vm.$options.HEADER_ITEMS_BORDER_CLASSES},[_c('gl-listbox-search-input',{ref:"searchBox",class:{ 'gl-listbox-topmost': !_vm.headerText },attrs:{"id":_vm.searchInputId,"data-testid":"listbox-search-input","role":"combobox","aria-expanded":String(visible),"aria-controls":_vm.listboxId,"aria-activedescendant":_vm.activeItemId,"aria-haspopup":"listbox","placeholder":_vm.searchPlaceholder},on:{"input":_vm.search,"keydown":[function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"enter",13,$event.key,"Enter")){ return null; }$event.preventDefault();},_vm.onKeydown]},model:{value:(_vm.searchStr),callback:function ($$v) {_vm.searchStr=$$v;},expression:"searchStr"}}),_vm._v(" "),(_vm.searching)?_c('gl-loading-icon',{staticClass:"gl-my-3",attrs:{"data-testid":"listbox-search-loader","size":"md"}}):_vm._e()],1):_vm._e(),_vm._v(" "),(_vm.showList)?_c(_vm.listboxTag,{ref:"list",tag:"component",staticClass:"gl-new-dropdown-contents gl-new-dropdown-contents-with-scrim-overlay",class:_vm.listboxClasses,attrs:{"id":_vm.listboxId,"aria-busy":_vm.isBusy,"aria-labelledby":_vm.ariaLabelledByID,"aria-multiselectable":_vm.multiple ? 'true' : undefined,"role":"listbox","tabindex":"0"},on:{"keydown":_vm.onKeydown}},[_c(_vm.itemTag,{tag:"component",staticClass:"top-scrim-wrapper",attrs:{"aria-hidden":"true","data-testid":"top-scrim"}},[_c('div',{staticClass:"top-scrim",class:{ 'top-scrim-light': !_vm.hasHeader, 'top-scrim-dark': _vm.hasHeader }})]),_vm._v(" "),_c(_vm.itemTag,{ref:"top-boundary",tag:"component",attrs:{"aria-hidden":"true"}}),_vm._v(" "),_vm._l((_vm.items),function(item,index){return [(_vm.isOption(item))?[_c('gl-listbox-item',_vm._b({key:_vm.listboxItemKey(item),attrs:{"id":_vm.generateItemId(item),"data-testid":("listbox-item-" + (item.value)),"is-highlighted":_vm.isHighlighted(item),"is-selected":_vm.isSelected(item),"is-focused":_vm.isFocused(item),"is-check-centered":_vm.isCheckCentered},on:{"select":function($event){return _vm.onSelect(item, $event)}}},'gl-listbox-item',_vm.listboxItemMoreItemsAriaAttributes(index),false),[_vm._t("list-item",function(){return [_vm._v("\n "+_vm._s(item.text)+"\n ")]},{"item":item})],2)]:[_c('gl-listbox-group',{key:item.text,class:_vm.groupClasses(index),attrs:{"name":item.text,"text-sr-only":item.textSrOnly},scopedSlots:_vm._u([(_vm.$scopedSlots['group-label'])?{key:"group-label",fn:function(){return [_vm._t("group-label",null,{"group":item})]},proxy:true}:null],null,true)},[_vm._v(" "),_vm._l((item.options),function(option){return _c('gl-listbox-item',{key:_vm.listboxItemKey(option),attrs:{"id":_vm.generateItemId(option),"data-testid":("listbox-item-" + (option.value)),"is-highlighted":_vm.isHighlighted(option),"is-selected":_vm.isSelected(option),"is-focused":_vm.isFocused(option),"is-check-centered":_vm.isCheckCentered},on:{"select":function($event){return _vm.onSelect(option, $event)}}},[_vm._t("list-item",function(){return [_vm._v("\n "+_vm._s(option.text)+"\n ")]},{"item":option})],2)})],2)]]}),_vm._v(" "),(_vm.infiniteScrollLoading)?_c(_vm.itemTag,{tag:"component"},[_c('gl-loading-icon',{staticClass:"gl-my-3",attrs:{"data-testid":"listbox-infinite-scroll-loader","size":"md"}})],1):_vm._e(),_vm._v(" "),(_vm.showIntersectionObserver)?_c('gl-intersection-observer',{on:{"appear":_vm.onIntersectionObserverAppear}}):_vm._e(),_vm._v(" "),_c(_vm.itemTag,{ref:"bottom-boundary",tag:"component",attrs:{"aria-hidden":"true"}}),_vm._v(" "),_c(_vm.itemTag,{tag:"component",staticClass:"bottom-scrim-wrapper",attrs:{"aria-hidden":"true","data-testid":"bottom-scrim"}},[_c('div',{staticClass:"bottom-scrim",class:{ '!gl-rounded-none': _vm.hasFooter }})])],2):_vm._e(),_vm._v(" "),(_vm.announceSRSearchResults)?_c('span',{staticClass:"gl-sr-only",attrs:{"data-testid":"listbox-number-of-results","aria-live":"assertive"}},[_vm._t("search-summary-sr-only",function(){return [_vm._v("\n "+_vm._s(_vm.srOnlyResultsLabel(_vm.flattenedOptions.length))+"\n ")]})],2):_vm._e(),_vm._v(" "),(_vm.isBusy)?_c('span',{staticClass:"gl-sr-only",attrs:{"aria-live":"polite","data-testid":"listbox-loading-announcement"}},[_vm._v("\n "+_vm._s(_vm.loadingAnnouncementText)+"\n ")]):(_vm.showNoResultsText)?_c('div',{staticClass:"gl-py-3 gl-pl-7 gl-pr-5 gl-text-base gl-text-subtle",attrs:{"aria-live":"assertive","data-testid":"listbox-no-results-text"}},[_vm._v("\n "+_vm._s(_vm.noResultsText)+"\n ")]):_vm._e(),_vm._v(" "),_vm._t("footer")]}}],null,true)})};
|
|
779
880
|
var __vue_staticRenderFns__ = [];
|
|
780
881
|
|
|
781
882
|
/* style */
|
|
@@ -8,6 +8,7 @@ var script = {
|
|
|
8
8
|
GlClearIconButton,
|
|
9
9
|
GlIcon
|
|
10
10
|
},
|
|
11
|
+
inheritAttrs: false,
|
|
11
12
|
model: {
|
|
12
13
|
prop: 'value',
|
|
13
14
|
event: 'input'
|
|
@@ -58,7 +59,7 @@ var script = {
|
|
|
58
59
|
const __vue_script__ = script;
|
|
59
60
|
|
|
60
61
|
/* template */
|
|
61
|
-
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-listbox-search"},[_c('gl-icon',{staticClass:"gl-listbox-search-icon",attrs:{"name":"search-sm","size":12}}),_vm._v(" "),_c('input',_vm._g({ref:"input",staticClass:"gl-listbox-search-input",attrs:{"type":"search","aria-label":_vm.placeholder,"placeholder":_vm.placeholder},domProps:{"value":_vm.value}},_vm.inputListeners)),_vm._v(" "),(_vm.hasValue)?_c('gl-clear-icon-button',{staticClass:"gl-listbox-search-clear-button",on:{"click":function($event){$event.stopPropagation();return _vm.clearInput.apply(null, arguments)}}}):_vm._e()],1)};
|
|
62
|
+
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-listbox-search"},[_c('gl-icon',{staticClass:"gl-listbox-search-icon",attrs:{"name":"search-sm","size":12}}),_vm._v(" "),_c('input',_vm._g(_vm._b({ref:"input",staticClass:"gl-listbox-search-input",attrs:{"type":"search","aria-label":_vm.placeholder,"placeholder":_vm.placeholder},domProps:{"value":_vm.value}},'input',_vm.$attrs,false),_vm.inputListeners)),_vm._v(" "),(_vm.hasValue)?_c('gl-clear-icon-button',{staticClass:"gl-listbox-search-clear-button",on:{"click":function($event){$event.stopPropagation();return _vm.clearInput.apply(null, arguments)}}}):_vm._e()],1)};
|
|
62
63
|
var __vue_staticRenderFns__ = [];
|
|
63
64
|
|
|
64
65
|
/* style */
|
package/package.json
CHANGED
|
@@ -26,7 +26,7 @@ import GlLoadingIcon from '../../loading_icon/loading_icon.vue';
|
|
|
26
26
|
import GlIntersectionObserver from '../../../utilities/intersection_observer/intersection_observer.vue';
|
|
27
27
|
import GlSearchBoxByType from '../../search_box_by_type/search_box_by_type.vue';
|
|
28
28
|
import GlBaseDropdown from '../base_dropdown/base_dropdown.vue';
|
|
29
|
-
import { translatePlural } from '../../../../utils/i18n';
|
|
29
|
+
import { translate, translatePlural } from '../../../../utils/i18n';
|
|
30
30
|
import GlListboxItem from './listbox_item.vue';
|
|
31
31
|
import GlListboxSearchInput from './listbox_search_input.vue';
|
|
32
32
|
import GlListboxGroup from './listbox_group.vue';
|
|
@@ -373,13 +373,22 @@ export default {
|
|
|
373
373
|
return {
|
|
374
374
|
selectedValues: [],
|
|
375
375
|
listboxId: uniqueId('listbox-'),
|
|
376
|
+
searchInputId: uniqueId('listbox-search-input-'),
|
|
376
377
|
nextFocusedItemIndex: null,
|
|
377
378
|
searchStr: '',
|
|
378
379
|
topBoundaryVisible: true,
|
|
379
380
|
bottomBoundaryVisible: true,
|
|
381
|
+
activeItemId: null,
|
|
382
|
+
itemIds: new Map(),
|
|
380
383
|
};
|
|
381
384
|
},
|
|
382
385
|
computed: {
|
|
386
|
+
ariaLabelledByID() {
|
|
387
|
+
if (this.searchable) {
|
|
388
|
+
return this.searchInputId;
|
|
389
|
+
}
|
|
390
|
+
return this.listAriaLabelledBy || this.headerId || this.toggleIdComputed;
|
|
391
|
+
},
|
|
383
392
|
toggleIdComputed() {
|
|
384
393
|
return this.toggleId || uniqueId('dropdown-toggle-btn-');
|
|
385
394
|
},
|
|
@@ -477,6 +486,9 @@ export default {
|
|
|
477
486
|
showIntersectionObserver() {
|
|
478
487
|
return this.infiniteScroll && !this.infiniteScrollLoading && !this.loading && !this.searching;
|
|
479
488
|
},
|
|
489
|
+
isBusy() {
|
|
490
|
+
return this.infiniteScrollLoading || this.loading || this.searching;
|
|
491
|
+
},
|
|
480
492
|
hasCustomToggle() {
|
|
481
493
|
return Boolean(this.$scopedSlots.toggle);
|
|
482
494
|
},
|
|
@@ -497,6 +509,24 @@ export default {
|
|
|
497
509
|
hasFooter() {
|
|
498
510
|
return Boolean(this.$scopedSlots.footer);
|
|
499
511
|
},
|
|
512
|
+
loadingAnnouncementText() {
|
|
513
|
+
if (this.infiniteScrollLoading) {
|
|
514
|
+
return translate(
|
|
515
|
+
'GlCollapsibleListbox.loadingAnnouncementText.loadingMoreItems',
|
|
516
|
+
'Loading more items'
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
if (this.searching) {
|
|
520
|
+
return translate('GlCollapsibleListbox.loadingAnnouncementText.searching', 'Searching');
|
|
521
|
+
}
|
|
522
|
+
if (this.loading) {
|
|
523
|
+
return translate(
|
|
524
|
+
'GlCollapsibleListbox.loadingAnnouncementText.loadingItems',
|
|
525
|
+
'Loading items'
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
return '';
|
|
529
|
+
},
|
|
500
530
|
},
|
|
501
531
|
watch: {
|
|
502
532
|
selected: {
|
|
@@ -595,6 +625,9 @@ export default {
|
|
|
595
625
|
*/
|
|
596
626
|
if (this.searchHasOptions) {
|
|
597
627
|
this.nextFocusedItemIndex = 0;
|
|
628
|
+
// Set activeItemId for the first item
|
|
629
|
+
const firstItem = this.flattenedOptions[0];
|
|
630
|
+
this.activeItemId = this.generateItemId(firstItem);
|
|
598
631
|
}
|
|
599
632
|
} else {
|
|
600
633
|
this.focusItem(this.selectedIndices[0] ?? 0, this.getFocusableListItemElements());
|
|
@@ -615,54 +648,80 @@ export default {
|
|
|
615
648
|
this.$emit(GL_DROPDOWN_HIDDEN);
|
|
616
649
|
this.nextFocusedItemIndex = null;
|
|
617
650
|
},
|
|
651
|
+
getNextIndex(currentIndex, keyCode, totalLength) {
|
|
652
|
+
// For UP: move up or wrap to end
|
|
653
|
+
if (keyCode === ARROW_UP) {
|
|
654
|
+
return currentIndex > 0 ? currentIndex - 1 : totalLength - 1;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// For DOWN: move down or wrap to start
|
|
658
|
+
return currentIndex < totalLength - 1 ? currentIndex + 1 : 0;
|
|
659
|
+
},
|
|
660
|
+
handleListNavigation(keyCode, elements) {
|
|
661
|
+
const currentIndex = this.nextFocusedItemIndex ?? -1;
|
|
662
|
+
const nextIndex = this.getNextIndex(currentIndex, keyCode, elements.length);
|
|
663
|
+
this.focusItem(nextIndex, elements, this.searchable);
|
|
664
|
+
},
|
|
618
665
|
onKeydown(event) {
|
|
619
666
|
const { code, target } = event;
|
|
620
667
|
const elements = this.getFocusableListItemElements();
|
|
621
668
|
|
|
622
669
|
if (elements.length < 1) return;
|
|
623
670
|
|
|
624
|
-
let stop = true;
|
|
625
671
|
const isSearchInput = target.matches(SEARCH_INPUT_SELECTOR);
|
|
672
|
+
let stop = true;
|
|
626
673
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
} else if (code === END) {
|
|
633
|
-
if (isSearchInput) {
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
this.focusItem(elements.length - 1, elements);
|
|
637
|
-
} else if (code === ARROW_UP) {
|
|
638
|
-
if (isSearchInput) {
|
|
639
|
-
return;
|
|
640
|
-
}
|
|
641
|
-
if (this.searchable && elements.indexOf(target) === 0) {
|
|
642
|
-
this.focusSearchInput();
|
|
643
|
-
if (!this.searchHasOptions) {
|
|
644
|
-
this.nextFocusedItemIndex = null;
|
|
674
|
+
switch (code) {
|
|
675
|
+
case HOME:
|
|
676
|
+
// Jump to first item if searchable or not in search input
|
|
677
|
+
if (this.searchable || !isSearchInput) {
|
|
678
|
+
this.focusItem(0, elements, this.searchable);
|
|
645
679
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
680
|
+
break;
|
|
681
|
+
|
|
682
|
+
case END:
|
|
683
|
+
// Jump to last item if searchable or not in search input
|
|
684
|
+
if (this.searchable || !isSearchInput) {
|
|
685
|
+
this.focusItem(elements.length - 1, elements, this.searchable);
|
|
686
|
+
}
|
|
687
|
+
break;
|
|
688
|
+
|
|
689
|
+
case ARROW_UP:
|
|
690
|
+
// Let default behavior work for non-searchable input
|
|
691
|
+
if (isSearchInput && !this.searchable) {
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
this.handleListNavigation(ARROW_UP, elements);
|
|
695
|
+
break;
|
|
696
|
+
|
|
697
|
+
case ARROW_DOWN:
|
|
698
|
+
// Focus first item from search input, otherwise navigate down
|
|
699
|
+
if (isSearchInput && !this.searchable) {
|
|
700
|
+
this.focusItem(0, elements);
|
|
701
|
+
} else {
|
|
702
|
+
this.handleListNavigation(ARROW_DOWN, elements);
|
|
703
|
+
}
|
|
704
|
+
break;
|
|
705
|
+
|
|
706
|
+
case ENTER:
|
|
707
|
+
if (isSearchInput) {
|
|
708
|
+
// Toggle selection of highlighted item if one exists
|
|
709
|
+
if (elements.length > 0 && this.nextFocusedItemIndex !== null) {
|
|
710
|
+
const highlightedItem = this.flattenedOptions[this.nextFocusedItemIndex];
|
|
711
|
+
this.onSelect(highlightedItem, !this.isSelected(highlightedItem));
|
|
712
|
+
}
|
|
713
|
+
} else {
|
|
714
|
+
stop = false;
|
|
715
|
+
}
|
|
716
|
+
break;
|
|
717
|
+
|
|
718
|
+
default:
|
|
719
|
+
// Allow default behavior for unhandled keys
|
|
720
|
+
stop = false;
|
|
721
|
+
break;
|
|
664
722
|
}
|
|
665
723
|
|
|
724
|
+
// Prevent default behavior for handled keys
|
|
666
725
|
if (stop) {
|
|
667
726
|
stopEvent(event);
|
|
668
727
|
}
|
|
@@ -678,10 +737,25 @@ export default {
|
|
|
678
737
|
|
|
679
738
|
this.focusItem(nextIndex, elements);
|
|
680
739
|
},
|
|
681
|
-
focusItem(index, elements) {
|
|
740
|
+
focusItem(index, elements, keepSearchFocused = false) {
|
|
682
741
|
this.nextFocusedItemIndex = index;
|
|
683
742
|
|
|
684
|
-
|
|
743
|
+
// Always update the activeItemId when focus changes
|
|
744
|
+
const item = this.flattenedOptions[index];
|
|
745
|
+
if (item) {
|
|
746
|
+
this.activeItemId = this.generateItemId(item);
|
|
747
|
+
} else {
|
|
748
|
+
this.activeItemId = null;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// If we're not keeping the search focused, focus the item
|
|
752
|
+
if (!keepSearchFocused) {
|
|
753
|
+
elements[index]?.focus();
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
this.$nextTick(() => {
|
|
757
|
+
this.scrollActiveItemIntoView();
|
|
758
|
+
});
|
|
685
759
|
},
|
|
686
760
|
focusSearchInput() {
|
|
687
761
|
this.$refs.searchBox.focusInput();
|
|
@@ -806,6 +880,41 @@ export default {
|
|
|
806
880
|
this.scrollObserver = observer;
|
|
807
881
|
},
|
|
808
882
|
isOption,
|
|
883
|
+
generateItemId(item) {
|
|
884
|
+
const key = item.value === null ? ITEM_NULL_KEY : item.value;
|
|
885
|
+
if (!this.itemIds.has(key)) {
|
|
886
|
+
this.itemIds.set(key, uniqueId('listbox-item-'));
|
|
887
|
+
}
|
|
888
|
+
return this.itemIds.get(key);
|
|
889
|
+
},
|
|
890
|
+
scrollActiveItemIntoView() {
|
|
891
|
+
const listContainer = this.$refs.list;
|
|
892
|
+
if (!this.activeItemId || !this.searchable || !listContainer) return;
|
|
893
|
+
|
|
894
|
+
const activeElement = document.getElementById(this.activeItemId);
|
|
895
|
+
if (!activeElement) return;
|
|
896
|
+
|
|
897
|
+
const containerRect = listContainer.getBoundingClientRect();
|
|
898
|
+
const itemRect = activeElement.getBoundingClientRect();
|
|
899
|
+
const itemTop = activeElement.offsetTop;
|
|
900
|
+
const padding = 30;
|
|
901
|
+
|
|
902
|
+
// If item is above the visible area
|
|
903
|
+
if (itemRect.top < containerRect.top) {
|
|
904
|
+
listContainer.scrollTo({
|
|
905
|
+
top: itemTop - padding,
|
|
906
|
+
behavior: 'smooth',
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// If item is below the visible area
|
|
911
|
+
else if (itemRect.bottom > containerRect.bottom) {
|
|
912
|
+
listContainer.scrollTo({
|
|
913
|
+
top: itemTop - containerRect.height + activeElement.offsetHeight + padding,
|
|
914
|
+
behavior: 'smooth',
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
},
|
|
809
918
|
},
|
|
810
919
|
};
|
|
811
920
|
</script>
|
|
@@ -839,165 +948,191 @@ export default {
|
|
|
839
948
|
<slot name="toggle"></slot>
|
|
840
949
|
</template>
|
|
841
950
|
|
|
842
|
-
<
|
|
843
|
-
v-if="headerText"
|
|
844
|
-
class="gl-flex gl-min-h-8 gl-items-center !gl-p-4"
|
|
845
|
-
:class="$options.HEADER_ITEMS_BORDER_CLASSES"
|
|
846
|
-
>
|
|
951
|
+
<template #default="{ visible }">
|
|
847
952
|
<div
|
|
848
|
-
|
|
849
|
-
class="gl-
|
|
850
|
-
|
|
851
|
-
>
|
|
852
|
-
{{ headerText }}
|
|
853
|
-
</div>
|
|
854
|
-
<gl-button
|
|
855
|
-
v-if="showResetButton"
|
|
856
|
-
category="tertiary"
|
|
857
|
-
class="!gl-m-0 !gl-w-auto gl-max-w-1/2 gl-flex-shrink-0 gl-text-ellipsis !gl-px-2 !gl-text-sm focus:!gl-focus-inset"
|
|
858
|
-
size="small"
|
|
859
|
-
data-testid="listbox-reset-button"
|
|
860
|
-
@click="onResetButtonClicked"
|
|
953
|
+
v-if="headerText"
|
|
954
|
+
class="gl-flex gl-min-h-8 gl-items-center !gl-p-4"
|
|
955
|
+
:class="$options.HEADER_ITEMS_BORDER_CLASSES"
|
|
861
956
|
>
|
|
862
|
-
{{ resetButtonLabel }}
|
|
863
|
-
</gl-button>
|
|
864
|
-
<gl-button
|
|
865
|
-
v-if="showSelectAllButton"
|
|
866
|
-
category="tertiary"
|
|
867
|
-
class="!gl-m-0 !gl-w-auto gl-max-w-1/2 gl-flex-shrink-0 gl-text-ellipsis !gl-px-2 !gl-text-sm focus:!gl-focus-inset"
|
|
868
|
-
size="small"
|
|
869
|
-
data-testid="listbox-select-all-button"
|
|
870
|
-
@click="onSelectAllButtonClicked"
|
|
871
|
-
>
|
|
872
|
-
{{ showSelectAllButtonLabel }}
|
|
873
|
-
</gl-button>
|
|
874
|
-
</div>
|
|
875
|
-
|
|
876
|
-
<div v-if="searchable" :class="$options.HEADER_ITEMS_BORDER_CLASSES">
|
|
877
|
-
<gl-listbox-search-input
|
|
878
|
-
ref="searchBox"
|
|
879
|
-
v-model="searchStr"
|
|
880
|
-
data-testid="listbox-search-input"
|
|
881
|
-
:placeholder="searchPlaceholder"
|
|
882
|
-
:class="{ 'gl-listbox-topmost': !headerText }"
|
|
883
|
-
@input="search"
|
|
884
|
-
@keydown.enter.prevent
|
|
885
|
-
@keydown="onKeydown"
|
|
886
|
-
/>
|
|
887
|
-
<gl-loading-icon
|
|
888
|
-
v-if="searching"
|
|
889
|
-
data-testid="listbox-search-loader"
|
|
890
|
-
size="md"
|
|
891
|
-
class="gl-my-3"
|
|
892
|
-
/>
|
|
893
|
-
</div>
|
|
894
|
-
|
|
895
|
-
<component
|
|
896
|
-
:is="listboxTag"
|
|
897
|
-
v-if="showList"
|
|
898
|
-
:id="listboxId"
|
|
899
|
-
ref="list"
|
|
900
|
-
:aria-labelledby="listAriaLabelledBy || headerId || toggleIdComputed"
|
|
901
|
-
role="listbox"
|
|
902
|
-
class="gl-new-dropdown-contents gl-new-dropdown-contents-with-scrim-overlay"
|
|
903
|
-
:class="listboxClasses"
|
|
904
|
-
tabindex="0"
|
|
905
|
-
@keydown="onKeydown"
|
|
906
|
-
>
|
|
907
|
-
<component :is="itemTag" class="top-scrim-wrapper" aria-hidden="true" data-testid="top-scrim">
|
|
908
957
|
<div
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
<
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
958
|
+
:id="headerId"
|
|
959
|
+
class="gl-grow gl-pr-2 gl-text-sm gl-font-bold gl-text-strong"
|
|
960
|
+
data-testid="listbox-header-text"
|
|
961
|
+
>
|
|
962
|
+
{{ headerText }}
|
|
963
|
+
</div>
|
|
964
|
+
<gl-button
|
|
965
|
+
v-if="showResetButton"
|
|
966
|
+
category="tertiary"
|
|
967
|
+
class="!gl-m-0 !gl-w-auto gl-max-w-1/2 gl-flex-shrink-0 gl-text-ellipsis !gl-px-2 !gl-text-sm focus:!gl-focus-inset"
|
|
968
|
+
size="small"
|
|
969
|
+
data-testid="listbox-reset-button"
|
|
970
|
+
@click="onResetButtonClicked"
|
|
971
|
+
>
|
|
972
|
+
{{ resetButtonLabel }}
|
|
973
|
+
</gl-button>
|
|
974
|
+
<gl-button
|
|
975
|
+
v-if="showSelectAllButton"
|
|
976
|
+
category="tertiary"
|
|
977
|
+
class="!gl-m-0 !gl-w-auto gl-max-w-1/2 gl-flex-shrink-0 gl-text-ellipsis !gl-px-2 !gl-text-sm focus:!gl-focus-inset"
|
|
978
|
+
size="small"
|
|
979
|
+
data-testid="listbox-select-all-button"
|
|
980
|
+
@click="onSelectAllButtonClicked"
|
|
981
|
+
>
|
|
982
|
+
{{ showSelectAllButtonLabel }}
|
|
983
|
+
</gl-button>
|
|
984
|
+
</div>
|
|
932
985
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
986
|
+
<div v-if="searchable" :class="$options.HEADER_ITEMS_BORDER_CLASSES">
|
|
987
|
+
<gl-listbox-search-input
|
|
988
|
+
:id="searchInputId"
|
|
989
|
+
ref="searchBox"
|
|
990
|
+
v-model="searchStr"
|
|
991
|
+
data-testid="listbox-search-input"
|
|
992
|
+
role="combobox"
|
|
993
|
+
:aria-expanded="String(visible)"
|
|
994
|
+
:aria-controls="listboxId"
|
|
995
|
+
:aria-activedescendant="activeItemId"
|
|
996
|
+
aria-haspopup="listbox"
|
|
997
|
+
:placeholder="searchPlaceholder"
|
|
998
|
+
:class="{ 'gl-listbox-topmost': !headerText }"
|
|
999
|
+
@input="search"
|
|
1000
|
+
@keydown.enter.prevent
|
|
1001
|
+
@keydown="onKeydown"
|
|
1002
|
+
/>
|
|
1003
|
+
<gl-loading-icon
|
|
1004
|
+
v-if="searching"
|
|
1005
|
+
data-testid="listbox-search-loader"
|
|
1006
|
+
size="md"
|
|
1007
|
+
class="gl-my-3"
|
|
1008
|
+
/>
|
|
1009
|
+
</div>
|
|
944
1010
|
|
|
1011
|
+
<component
|
|
1012
|
+
:is="listboxTag"
|
|
1013
|
+
v-if="showList"
|
|
1014
|
+
:id="listboxId"
|
|
1015
|
+
ref="list"
|
|
1016
|
+
:aria-busy="isBusy"
|
|
1017
|
+
:aria-labelledby="ariaLabelledByID"
|
|
1018
|
+
:aria-multiselectable="multiple ? 'true' : undefined"
|
|
1019
|
+
role="listbox"
|
|
1020
|
+
class="gl-new-dropdown-contents gl-new-dropdown-contents-with-scrim-overlay"
|
|
1021
|
+
:class="listboxClasses"
|
|
1022
|
+
tabindex="0"
|
|
1023
|
+
@keydown="onKeydown"
|
|
1024
|
+
>
|
|
1025
|
+
<component
|
|
1026
|
+
:is="itemTag"
|
|
1027
|
+
class="top-scrim-wrapper"
|
|
1028
|
+
aria-hidden="true"
|
|
1029
|
+
data-testid="top-scrim"
|
|
1030
|
+
>
|
|
1031
|
+
<div
|
|
1032
|
+
class="top-scrim"
|
|
1033
|
+
:class="{ 'top-scrim-light': !hasHeader, 'top-scrim-dark': hasHeader }"
|
|
1034
|
+
></div>
|
|
1035
|
+
</component>
|
|
1036
|
+
<component :is="itemTag" ref="top-boundary" aria-hidden="true" />
|
|
1037
|
+
<template v-for="(item, index) in items">
|
|
1038
|
+
<template v-if="isOption(item)">
|
|
945
1039
|
<gl-listbox-item
|
|
946
|
-
|
|
947
|
-
:key="listboxItemKey(
|
|
948
|
-
:data-testid="`listbox-item-${
|
|
949
|
-
:is-highlighted="isHighlighted(
|
|
950
|
-
:is-selected="isSelected(
|
|
951
|
-
:is-focused="isFocused(
|
|
1040
|
+
:id="generateItemId(item)"
|
|
1041
|
+
:key="listboxItemKey(item)"
|
|
1042
|
+
:data-testid="`listbox-item-${item.value}`"
|
|
1043
|
+
:is-highlighted="isHighlighted(item)"
|
|
1044
|
+
:is-selected="isSelected(item)"
|
|
1045
|
+
:is-focused="isFocused(item)"
|
|
952
1046
|
:is-check-centered="isCheckCentered"
|
|
953
|
-
|
|
1047
|
+
v-bind="listboxItemMoreItemsAriaAttributes(index)"
|
|
1048
|
+
@select="onSelect(item, $event)"
|
|
954
1049
|
>
|
|
955
1050
|
<!-- @slot Custom template of the listbox item -->
|
|
956
|
-
<slot name="list-item" :item="
|
|
957
|
-
{{
|
|
1051
|
+
<slot name="list-item" :item="item">
|
|
1052
|
+
{{ item.text }}
|
|
958
1053
|
</slot>
|
|
959
1054
|
</gl-listbox-item>
|
|
960
|
-
</
|
|
1055
|
+
</template>
|
|
1056
|
+
|
|
1057
|
+
<template v-else>
|
|
1058
|
+
<gl-listbox-group
|
|
1059
|
+
:key="item.text"
|
|
1060
|
+
:name="item.text"
|
|
1061
|
+
:text-sr-only="item.textSrOnly"
|
|
1062
|
+
:class="groupClasses(index)"
|
|
1063
|
+
>
|
|
1064
|
+
<template v-if="$scopedSlots['group-label']" #group-label>
|
|
1065
|
+
<!-- @slot Custom template for group names -->
|
|
1066
|
+
<slot name="group-label" :group="item"></slot>
|
|
1067
|
+
</template>
|
|
1068
|
+
|
|
1069
|
+
<gl-listbox-item
|
|
1070
|
+
v-for="option in item.options"
|
|
1071
|
+
:id="generateItemId(option)"
|
|
1072
|
+
:key="listboxItemKey(option)"
|
|
1073
|
+
:data-testid="`listbox-item-${option.value}`"
|
|
1074
|
+
:is-highlighted="isHighlighted(option)"
|
|
1075
|
+
:is-selected="isSelected(option)"
|
|
1076
|
+
:is-focused="isFocused(option)"
|
|
1077
|
+
:is-check-centered="isCheckCentered"
|
|
1078
|
+
@select="onSelect(option, $event)"
|
|
1079
|
+
>
|
|
1080
|
+
<!-- @slot Custom template of the listbox item -->
|
|
1081
|
+
<slot name="list-item" :item="option">
|
|
1082
|
+
{{ option.text }}
|
|
1083
|
+
</slot>
|
|
1084
|
+
</gl-listbox-item>
|
|
1085
|
+
</gl-listbox-group>
|
|
1086
|
+
</template>
|
|
961
1087
|
</template>
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1088
|
+
<component :is="itemTag" v-if="infiniteScrollLoading">
|
|
1089
|
+
<gl-loading-icon data-testid="listbox-infinite-scroll-loader" size="md" class="gl-my-3" />
|
|
1090
|
+
</component>
|
|
1091
|
+
<gl-intersection-observer
|
|
1092
|
+
v-if="showIntersectionObserver"
|
|
1093
|
+
@appear="onIntersectionObserverAppear"
|
|
1094
|
+
/>
|
|
1095
|
+
<component :is="itemTag" ref="bottom-boundary" aria-hidden="true" />
|
|
1096
|
+
<component
|
|
1097
|
+
:is="itemTag"
|
|
1098
|
+
class="bottom-scrim-wrapper"
|
|
1099
|
+
aria-hidden="true"
|
|
1100
|
+
data-testid="bottom-scrim"
|
|
1101
|
+
>
|
|
1102
|
+
<div class="bottom-scrim" :class="{ '!gl-rounded-none': hasFooter }"></div>
|
|
1103
|
+
</component>
|
|
965
1104
|
</component>
|
|
966
|
-
<
|
|
967
|
-
v-if="
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
<component
|
|
972
|
-
:is="itemTag"
|
|
973
|
-
class="bottom-scrim-wrapper"
|
|
974
|
-
aria-hidden="true"
|
|
975
|
-
data-testid="bottom-scrim"
|
|
1105
|
+
<span
|
|
1106
|
+
v-if="announceSRSearchResults"
|
|
1107
|
+
data-testid="listbox-number-of-results"
|
|
1108
|
+
class="gl-sr-only"
|
|
1109
|
+
aria-live="assertive"
|
|
976
1110
|
>
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
{{
|
|
989
|
-
</
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1111
|
+
<!-- @slot Text read by screen reader announcing a number of search results -->
|
|
1112
|
+
<slot name="search-summary-sr-only">
|
|
1113
|
+
{{ srOnlyResultsLabel(flattenedOptions.length) }}
|
|
1114
|
+
</slot>
|
|
1115
|
+
</span>
|
|
1116
|
+
<span
|
|
1117
|
+
v-if="isBusy"
|
|
1118
|
+
class="gl-sr-only"
|
|
1119
|
+
aria-live="polite"
|
|
1120
|
+
data-testid="listbox-loading-announcement"
|
|
1121
|
+
>
|
|
1122
|
+
{{ loadingAnnouncementText }}
|
|
1123
|
+
</span>
|
|
1124
|
+
|
|
1125
|
+
<div
|
|
1126
|
+
v-else-if="showNoResultsText"
|
|
1127
|
+
aria-live="assertive"
|
|
1128
|
+
class="gl-py-3 gl-pl-7 gl-pr-5 gl-text-base gl-text-subtle"
|
|
1129
|
+
data-testid="listbox-no-results-text"
|
|
1130
|
+
>
|
|
1131
|
+
{{ noResultsText }}
|
|
1132
|
+
</div>
|
|
1133
|
+
|
|
1134
|
+
<!-- @slot Content to display in dropdown footer -->
|
|
1135
|
+
<slot name="footer"></slot>
|
|
1136
|
+
</template>
|
|
1002
1137
|
</gl-base-dropdown>
|
|
1003
1138
|
</template>
|
|
@@ -8,6 +8,7 @@ export default {
|
|
|
8
8
|
GlClearIconButton,
|
|
9
9
|
GlIcon,
|
|
10
10
|
},
|
|
11
|
+
inheritAttrs: false,
|
|
11
12
|
model: {
|
|
12
13
|
prop: 'value',
|
|
13
14
|
event: 'input',
|
|
@@ -65,6 +66,7 @@ export default {
|
|
|
65
66
|
class="gl-listbox-search-input"
|
|
66
67
|
:aria-label="placeholder"
|
|
67
68
|
:placeholder="placeholder"
|
|
69
|
+
v-bind="$attrs"
|
|
68
70
|
v-on="inputListeners"
|
|
69
71
|
/>
|
|
70
72
|
<gl-clear-icon-button
|
package/translations.js
CHANGED
|
@@ -10,6 +10,9 @@ export default {
|
|
|
10
10
|
'GlChartLegend.current': 'Current',
|
|
11
11
|
'GlChartLegend.max': 'Max',
|
|
12
12
|
'GlChartLegend.min': 'Min',
|
|
13
|
+
'GlCollapsibleListbox.loadingAnnouncementText.loadingItems': 'Loading items',
|
|
14
|
+
'GlCollapsibleListbox.loadingAnnouncementText.loadingMoreItems': 'Loading more items',
|
|
15
|
+
'GlCollapsibleListbox.loadingAnnouncementText.searching': 'Searching',
|
|
13
16
|
'GlCollapsibleListbox.srOnlyResultsLabel': null,
|
|
14
17
|
'GlDatepicker.monthLabel': 'Month',
|
|
15
18
|
'GlDatepicker.yearLabel': 'Year',
|