@gitlab/ui 111.9.0 → 111.10.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 CHANGED
@@ -1,3 +1,17 @@
1
+ # [111.10.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v111.9.1...v111.10.0) (2025-03-28)
2
+
3
+
4
+ ### Features
5
+
6
+ * **GlCollapsibleListbox:** Allow listbox options to have a null value ([6e1c5f6](https://gitlab.com/gitlab-org/gitlab-ui/commit/6e1c5f6af82d74c856ab4f1a5985d65316cbc39b))
7
+
8
+ ## [111.9.1](https://gitlab.com/gitlab-org/gitlab-ui/compare/v111.9.0...v111.9.1) (2025-03-28)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **GlCollapsibleListbox:** Enter press should toggle selection ([0a89dc6](https://gitlab.com/gitlab-org/gitlab-ui/commit/0a89dc60c427c5671bbb9a809c1b89088aff1735))
14
+
1
15
  # [111.9.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v111.8.1...v111.9.0) (2025-03-27)
2
16
 
3
17
 
@@ -1,11 +1,11 @@
1
- import { BLink } from '../../../vendor/bootstrap-vue/src/components/link/link';
2
- import { breadCrumbSizeOptions } from '../../../utils/constants';
1
+ import GlLink from '../link/link';
2
+ import { breadCrumbSizeOptions, linkVariantUnstyled } from '../../../utils/constants';
3
3
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
4
4
 
5
5
  var script = {
6
6
  name: 'GlBreadcrumbItem',
7
7
  components: {
8
- BLink
8
+ GlLink
9
9
  },
10
10
  inheritAttrs: false,
11
11
  props: {
@@ -38,14 +38,15 @@ var script = {
38
38
  default: breadCrumbSizeOptions.sm,
39
39
  validator: value => Object.keys(breadCrumbSizeOptions).includes(value)
40
40
  }
41
- }
41
+ },
42
+ linkVariantUnstyled
42
43
  };
43
44
 
44
45
  /* script */
45
46
  const __vue_script__ = script;
46
47
 
47
48
  /* template */
48
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('li',{class:("gl-breadcrumb-item gl-breadcrumb-item-" + _vm.size)},[_c('b-link',{attrs:{"href":_vm.href,"to":_vm.to,"aria-current":_vm.ariaCurrent}},[_vm._t("default",function(){return [_vm._v(_vm._s(_vm.text))]})],2)],1)};
49
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('li',{class:("gl-breadcrumb-item gl-breadcrumb-item-" + _vm.size)},[_c('gl-link',{attrs:{"href":_vm.href,"to":_vm.to,"aria-current":_vm.ariaCurrent,"variant":_vm.$options.linkVariantUnstyled}},[_vm._t("default",function(){return [_vm._v(_vm._s(_vm.text))]})],2)],1)};
49
50
  var __vue_staticRenderFns__ = [];
50
51
 
51
52
  /* style */
@@ -18,6 +18,7 @@ import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
18
18
 
19
19
  //
20
20
  const ITEM_SELECTOR = '[role="option"]';
21
+ const ITEM_NULL_KEY = Symbol('null-key');
21
22
  const HEADER_ITEMS_BORDER_CLASSES = ['gl-border-b-1', 'gl-border-b-solid', 'gl-border-b-dropdown-divider'];
22
23
  const GROUP_TOP_BORDER_CLASSES = ['gl-border-t-1', 'gl-border-t-solid', 'gl-border-t-dropdown-divider', 'gl-pt-1', 'gl-mt-2'];
23
24
  const SEARCH_INPUT_SELECTOR = '.gl-listbox-search-input';
@@ -621,7 +622,9 @@ var script = {
621
622
  }
622
623
  } else if (code === ENTER && isSearchInput) {
623
624
  if (this.searchHasOptions && elements.length > 0) {
624
- this.onSelect(this.flattenedOptions[0], true);
625
+ // Toggle selection state of the first item
626
+ const firstItem = this.flattenedOptions[0];
627
+ this.onSelect(firstItem, !this.isSelected(firstItem));
625
628
  }
626
629
  stop = true;
627
630
  } else {
@@ -723,6 +726,12 @@ var script = {
723
726
  */
724
727
  this.$emit('bottom-reached');
725
728
  },
729
+ listboxItemKey(item) {
730
+ if (item.value === null) {
731
+ return ITEM_NULL_KEY;
732
+ }
733
+ return item.value;
734
+ },
726
735
  listboxItemMoreItemsAriaAttributes(index) {
727
736
  if (this.totalItems === null) {
728
737
  return {};
@@ -767,7 +776,7 @@ var script = {
767
776
  const __vue_script__ = script;
768
777
 
769
778
  /* template */
770
- 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],null,true)},[_vm._v(" "),(_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:{"data-testid":"listbox-search-input","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-labelledby":_vm.listAriaLabelledBy || _vm.headerId || _vm.toggleIdComputed,"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:item.value,attrs:{"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:option.value,attrs:{"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.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")],2)};
779
+ 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],null,true)},[_vm._v(" "),(_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:{"data-testid":"listbox-search-input","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-labelledby":_vm.listAriaLabelledBy || _vm.headerId || _vm.toggleIdComputed,"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:{"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:{"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.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")],2)};
771
780
  var __vue_staticRenderFns__ = [];
772
781
 
773
782
  /* style */
@@ -800,4 +809,4 @@ var __vue_staticRenderFns__ = [];
800
809
  );
801
810
 
802
811
  export default __vue_component__;
803
- export { ITEM_SELECTOR, SEARCH_INPUT_SELECTOR };
812
+ export { ITEM_NULL_KEY, ITEM_SELECTOR, SEARCH_INPUT_SELECTOR };
@@ -34,6 +34,9 @@ const mockOptions = [{
34
34
  }, {
35
35
  value: 'sup',
36
36
  text: 'Support'
37
+ }, {
38
+ value: null,
39
+ text: 'None'
37
40
  }];
38
41
  const mockOptionsValues = mockOptions.map(_ref => {
39
42
  let {
@@ -1,7 +1,7 @@
1
1
  import isNumber from 'lodash/isNumber';
2
2
  import isString from 'lodash/isString';
3
3
 
4
- const isOption = item => Boolean(item) && (isString(item.value) || isNumber(item.value));
4
+ const isOption = item => Boolean(item) && (isString(item.value) || isNumber(item.value) || item.value === null);
5
5
 
6
6
  // eslint-disable-next-line unicorn/no-array-callback-reference
7
7
  const isGroup = function () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "111.9.0",
3
+ "version": "111.10.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -1,11 +1,11 @@
1
1
  <script>
2
- import { BLink } from '../../../vendor/bootstrap-vue/src/components/link/link';
3
- import { breadCrumbSizeOptions } from '../../../utils/constants';
2
+ import GlLink from '../link/link.vue';
3
+ import { breadCrumbSizeOptions, linkVariantUnstyled } from '../../../utils/constants';
4
4
 
5
5
  export default {
6
6
  name: 'GlBreadcrumbItem',
7
7
  components: {
8
- BLink,
8
+ GlLink,
9
9
  },
10
10
  inheritAttrs: false,
11
11
  props: {
@@ -39,13 +39,19 @@ export default {
39
39
  validator: (value) => Object.keys(breadCrumbSizeOptions).includes(value),
40
40
  },
41
41
  },
42
+ linkVariantUnstyled,
42
43
  };
43
44
  </script>
44
45
 
45
46
  <template>
46
47
  <li :class="`gl-breadcrumb-item gl-breadcrumb-item-${size}`">
47
- <b-link :href="href" :to="to" :aria-current="ariaCurrent">
48
+ <gl-link
49
+ :href="href"
50
+ :to="to"
51
+ :aria-current="ariaCurrent"
52
+ :variant="$options.linkVariantUnstyled"
53
+ >
48
54
  <slot>{{ text }}</slot>
49
- </b-link>
55
+ </gl-link>
50
56
  </li>
51
57
  </template>
@@ -100,7 +100,7 @@ objects:
100
100
 
101
101
  ```typescript
102
102
  type Option = {
103
- value: string
103
+ value: string | number | null
104
104
  text?: string
105
105
  }
106
106
 
@@ -34,6 +34,7 @@ import GlListboxGroup from './listbox_group.vue';
34
34
  import { isOption, itemsValidator, flattenedOptions } from './utils';
35
35
 
36
36
  export const ITEM_SELECTOR = '[role="option"]';
37
+ export const ITEM_NULL_KEY = Symbol('null-key');
37
38
  const HEADER_ITEMS_BORDER_CLASSES = [
38
39
  'gl-border-b-1',
39
40
  'gl-border-b-solid',
@@ -653,7 +654,9 @@ export default {
653
654
  }
654
655
  } else if (code === ENTER && isSearchInput) {
655
656
  if (this.searchHasOptions && elements.length > 0) {
656
- this.onSelect(this.flattenedOptions[0], true);
657
+ // Toggle selection state of the first item
658
+ const firstItem = this.flattenedOptions[0];
659
+ this.onSelect(firstItem, !this.isSelected(firstItem));
657
660
  }
658
661
  stop = true;
659
662
  } else {
@@ -757,6 +760,12 @@ export default {
757
760
  */
758
761
  this.$emit('bottom-reached');
759
762
  },
763
+ listboxItemKey(item) {
764
+ if (item.value === null) {
765
+ return ITEM_NULL_KEY;
766
+ }
767
+ return item.value;
768
+ },
760
769
  listboxItemMoreItemsAriaAttributes(index) {
761
770
  if (this.totalItems === null) {
762
771
  return {};
@@ -905,7 +914,7 @@ export default {
905
914
  <template v-for="(item, index) in items">
906
915
  <template v-if="isOption(item)">
907
916
  <gl-listbox-item
908
- :key="item.value"
917
+ :key="listboxItemKey(item)"
909
918
  :data-testid="`listbox-item-${item.value}`"
910
919
  :is-highlighted="isHighlighted(item)"
911
920
  :is-selected="isSelected(item)"
@@ -935,7 +944,7 @@ export default {
935
944
 
936
945
  <gl-listbox-item
937
946
  v-for="option in item.options"
938
- :key="option.value"
947
+ :key="listboxItemKey(option)"
939
948
  :data-testid="`listbox-item-${option.value}`"
940
949
  :is-highlighted="isHighlighted(option)"
941
950
  :is-selected="isSelected(option)"
@@ -47,6 +47,10 @@ export const mockOptions = [
47
47
  value: 'sup',
48
48
  text: 'Support',
49
49
  },
50
+ {
51
+ value: null,
52
+ text: 'None',
53
+ },
50
54
  ];
51
55
 
52
56
  export const mockOptionsValues = mockOptions.map(({ value }) => value);
@@ -1,7 +1,8 @@
1
1
  import isNumber from 'lodash/isNumber';
2
2
  import isString from 'lodash/isString';
3
3
 
4
- const isOption = (item) => Boolean(item) && (isString(item.value) || isNumber(item.value));
4
+ const isOption = (item) =>
5
+ Boolean(item) && (isString(item.value) || isNumber(item.value) || item.value === null);
5
6
 
6
7
  // eslint-disable-next-line unicorn/no-array-callback-reference
7
8
  const isGroup = ({ options } = {}) => Array.isArray(options) && options.every(isOption);