@gitlab/ui 63.2.0 → 63.3.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
+ # [63.3.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v63.2.1...v63.3.0) (2023-05-24)
2
+
3
+
4
+ ### Features
5
+
6
+ * **GlCollapsibleListbox:** Add select all button in header ([eb74768](https://gitlab.com/gitlab-org/gitlab-ui/commit/eb74768163939bdb1b08a168d58e800b440705c2))
7
+
8
+ ## [63.2.1](https://gitlab.com/gitlab-org/gitlab-ui/compare/v63.2.0...v63.2.1) (2023-05-23)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **GlAreaChart:** Replace the getDom to getZr for supporting edge ([2a5bef8](https://gitlab.com/gitlab-org/gitlab-ui/commit/2a5bef897dcd54422cb1163e497461558a8f06ac))
14
+
1
15
  # [63.2.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v63.1.3...v63.2.0) (2023-05-23)
2
16
 
3
17
 
@@ -267,6 +267,17 @@ var script = {
267
267
  required: false,
268
268
  default: ''
269
269
  },
270
+ /**
271
+ * The select all button's label, to be rendered in the header. If this is omitted, the button is not
272
+ * rendered.
273
+ * The select all button requires a header to be set, so this prop should be used in conjunction with
274
+ * headerText.
275
+ */
276
+ showSelectAllButtonLabel: {
277
+ type: String,
278
+ required: false,
279
+ default: ''
280
+ },
270
281
  /**
271
282
  * Render the toggle button as a block element
272
283
  */
@@ -366,6 +377,15 @@ var script = {
366
377
  }
367
378
  return Boolean(this.selected);
368
379
  },
380
+ showSelectAllButton() {
381
+ if (!this.showSelectAllButtonLabel) {
382
+ return false;
383
+ }
384
+ if (!this.multiple) {
385
+ return false;
386
+ }
387
+ return this.selected.length === 0;
388
+ },
369
389
  showIntersectionObserver() {
370
390
  return this.infiniteScroll && !this.infiniteScrollLoading && !this.loading && !this.searching;
371
391
  },
@@ -421,6 +441,14 @@ var script = {
421
441
  }
422
442
  }
423
443
  },
444
+ showSelectAllButtonLabel: {
445
+ immediate: true,
446
+ handler(showSelectAllButtonLabel) {
447
+ if (showSelectAllButtonLabel && !this.headerText) {
448
+ throw new Error('The select all button cannot be rendered without a header. Either provide a header via the headerText prop, or do not provide the showSelectAllButtonLabel prop.');
449
+ }
450
+ }
451
+ },
424
452
  infiniteScroll: {
425
453
  immediate: true,
426
454
  handler(newValue) {
@@ -576,6 +604,14 @@ var script = {
576
604
  this.$emit('reset');
577
605
  this.closeAndFocus();
578
606
  },
607
+ onSelectAllButtonClicked() {
608
+ /**
609
+ * Emitted when the select all button is clicked
610
+ *
611
+ * @event select-all
612
+ */
613
+ this.$emit('select-all');
614
+ },
579
615
  closeAndFocus() {
580
616
  this.$refs.baseDropdown.closeAndFocus();
581
617
  },
@@ -632,7 +668,7 @@ var script = {
632
668
  const __vue_script__ = script;
633
669
 
634
670
  /* template */
635
- 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.toggleId,"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,"popper-options":_vm.popperOptions,"fluid-width":_vm.fluidWidth},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-display-flex gl-align-items-center gl-p-4! gl-min-h-8",class:_vm.$options.HEADER_ITEMS_BORDER_CLASSES},[_c('div',{staticClass:"gl-flex-grow-1 gl-font-weight-bold gl-font-sm gl-pr-2",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-focus-inset-border-2-blue-400! gl-flex-shrink-0 gl-font-sm! gl-px-2! gl-py-2! gl-w-auto! gl-m-0!",attrs:{"category":"tertiary","data-testid":"listbox-reset-button"},on:{"click":_vm.onResetButtonClicked}},[_vm._v("\n "+_vm._s(_vm.resetButtonLabel)+"\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",attrs:{"aria-owns":_vm.listboxId,"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.toggleId,"role":"listbox","tabindex":"-1"},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-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-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-0!': _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")],2):(_vm.showNoResultsText)?_c('div',{staticClass:"gl-pl-7 gl-pr-5 gl-py-3 gl-font-base gl-text-gray-600",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)};
671
+ 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.toggleId,"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,"popper-options":_vm.popperOptions,"fluid-width":_vm.fluidWidth},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-display-flex gl-align-items-center gl-p-4! gl-min-h-8",class:_vm.$options.HEADER_ITEMS_BORDER_CLASSES},[_c('div',{staticClass:"gl-flex-grow-1 gl-font-weight-bold gl-font-sm gl-pr-2",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-focus-inset-border-2-blue-400! gl-flex-shrink-0 gl-font-sm! gl-px-2! gl-py-2! gl-w-auto! gl-m-0! gl-max-w-50p gl-text-overflow-ellipsis",attrs:{"category":"tertiary","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-focus-inset-border-2-blue-400! gl-flex-shrink-0 gl-font-sm! gl-px-2! gl-py-2! gl-w-auto! gl-m-0! gl-max-w-50p gl-text-overflow-ellipsis",attrs:{"category":"tertiary","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",attrs:{"aria-owns":_vm.listboxId,"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.toggleId,"role":"listbox","tabindex":"-1"},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-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-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-0!': _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")],2):(_vm.showNoResultsText)?_c('div',{staticClass:"gl-pl-7 gl-pr-5 gl-py-3 gl-font-base gl-text-gray-600",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)};
636
672
  var __vue_staticRenderFns__ = [];
637
673
 
638
674
  /* style */
@@ -218,8 +218,8 @@ var script = {
218
218
  }
219
219
  },
220
220
  beforeDestroy() {
221
- this.chart.getDom().removeEventListener('mousemove', this.debouncedShowHideTooltip);
222
- this.chart.getDom().removeEventListener('mouseout', this.debouncedShowHideTooltip);
221
+ this.chart.getZr().off('mousemove', this.debouncedShowHideTooltip);
222
+ this.chart.getZr().off('mouseout', this.debouncedShowHideTooltip);
223
223
  this.chart.off('mouseout', this.hideAnnotationsTooltip);
224
224
  this.chart.off('mouseover', this.onChartMouseOver);
225
225
  },
@@ -244,8 +244,8 @@ var script = {
244
244
  // when the mouse is hovered over the parent container
245
245
  // of echarts' svg element. This works only for data points
246
246
  // and not markPoints.
247
- chart.getDom().addEventListener('mousemove', this.debouncedShowHideTooltip);
248
- chart.getDom().addEventListener('mouseout', this.debouncedShowHideTooltip);
247
+ chart.getZr().on('mousemove', this.debouncedShowHideTooltip);
248
+ chart.getZr().on('mouseout', this.debouncedShowHideTooltip);
249
249
 
250
250
  // eCharts inbuild mouse events
251
251
  // https://echarts.apache.org/en/api.html#events.Mouse%20events
@@ -264,7 +264,10 @@ var script = {
264
264
  this.chart = chart;
265
265
  this.$emit('created', chart);
266
266
  },
267
- showHideTooltip(mouseEvent) {
267
+ showHideTooltip(_ref) {
268
+ let {
269
+ event: mouseEvent
270
+ } = _ref;
268
271
  this.showDataTooltip = this.chart.containPixel('grid', [mouseEvent.zrX, mouseEvent.zrY]);
269
272
  this.dataTooltipPosition = {
270
273
  left: `${mouseEvent.zrX + DATA_TOOLTIP_LEFT_OFFSET}px`,
@@ -20,4 +20,10 @@ const SERIES_NAME = {
20
20
  [SERIES_NAME_LONG_WITHOUT_SPACES]: 'Series_name_long._Lorem_ipsum_dolor_sit_amet,_consectetur_adipiscing_elit._Sed_tincidunt_interdum_sapien_ut_blandit._Nulla_fermentum_nisi_id_euismod_vulputate._END'
21
21
  };
22
22
 
23
- export { ARG_TYPE_SUBCATEGORY_ACCESSIBILITY, ARG_TYPE_SUBCATEGORY_INFINITE_SCROLL, ARG_TYPE_SUBCATEGORY_LOOK_AND_FEEL, ARG_TYPE_SUBCATEGORY_SEARCH, ARG_TYPE_SUBCATEGORY_STATE, SERIES_NAME, SERIES_NAME_LONG, SERIES_NAME_LONG_WITHOUT_SPACES, SERIES_NAME_SHORT };
23
+ /**
24
+ * Reused constants for ListBox
25
+ */
26
+
27
+ const LISTBOX_CONTAINER_HEIGHT = '370px';
28
+
29
+ export { ARG_TYPE_SUBCATEGORY_ACCESSIBILITY, ARG_TYPE_SUBCATEGORY_INFINITE_SCROLL, ARG_TYPE_SUBCATEGORY_LOOK_AND_FEEL, ARG_TYPE_SUBCATEGORY_SEARCH, ARG_TYPE_SUBCATEGORY_STATE, LISTBOX_CONTAINER_HEIGHT, SERIES_NAME, SERIES_NAME_LONG, SERIES_NAME_LONG_WITHOUT_SPACES, SERIES_NAME_SHORT };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "63.2.0",
3
+ "version": "63.3.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -27,6 +27,7 @@
27
27
  "copy-fonts": "make copy-fonts",
28
28
  "build-scss-variables": "make scss_to_js/scss_variables.js",
29
29
  "clean": "rm -r dist storybook scss_to_js/scss_variables.* src/scss/utilities.scss",
30
+ "cy:edge": "cypress run --browser edge",
30
31
  "cy:run": "cypress run --browser firefox",
31
32
  "start": "yarn storybook",
32
33
  "storybook": "yarn storybook-prep && storybook dev --ci --host localhost --port 9001 -c .storybook",
@@ -41,6 +41,7 @@ describe('GlCollapsibleListbox', () => {
41
41
  const findLoadingIcon = () => wrapper.find("[data-testid='listbox-search-loader']");
42
42
  const findSRNumberOfResultsText = () => wrapper.find("[data-testid='listbox-number-of-results']");
43
43
  const findResetButton = () => wrapper.find("[data-testid='listbox-reset-button']");
44
+ const findSelectAllButton = () => wrapper.find("[data-testid='listbox-select-all-button']");
44
45
  const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver);
45
46
 
46
47
  it('passes custom popper.js options to the base dropdown', () => {
@@ -514,6 +515,68 @@ describe('GlCollapsibleListbox', () => {
514
515
  });
515
516
  });
516
517
 
518
+ describe('with select all action', () => {
519
+ it('throws an error when enabling the select action without a header', () => {
520
+ expect(() => {
521
+ buildWrapper({ showSelectAllButtonLabel: 'Select All' });
522
+ }).toThrow(
523
+ 'The select all button cannot be rendered without a header. Either provide a header via the headerText prop, or do not provide the showSelectAllButtonLabel prop.'
524
+ );
525
+ expect(wrapper).toHaveLoggedVueErrors();
526
+ });
527
+
528
+ it.each`
529
+ multiple | expectedResult
530
+ ${false} | ${false}
531
+ ${true} | ${true}
532
+ `(
533
+ 'shows the select all button if the label is provided and the selection is empty and multiple option is $multiple',
534
+ ({ multiple, expectedResult }) => {
535
+ buildWrapper({
536
+ headerText: 'Select assignee',
537
+ resetButtonLabel: 'Unassign',
538
+ showSelectAllButtonLabel: 'Select All',
539
+ selected: [],
540
+ items: mockOptions,
541
+ multiple,
542
+ });
543
+
544
+ expect(findResetButton().exists()).toBe(!expectedResult);
545
+ expect(findSelectAllButton().exists()).toBe(expectedResult);
546
+ }
547
+ );
548
+
549
+ it('has the label text "Select All" if the label is provided and the selection is empty', () => {
550
+ buildWrapper({
551
+ headerText: 'Select assignee',
552
+ resetButtonLabel: 'Unassign',
553
+ showSelectAllButtonLabel: 'Select All',
554
+ selected: [],
555
+ items: mockOptions,
556
+ multiple: true,
557
+ });
558
+
559
+ expect(findSelectAllButton().text()).toBe('Select All');
560
+ });
561
+
562
+ it('on click, emits the select-all event and calls closeAndFocus()', () => {
563
+ buildWrapper({
564
+ headerText: 'Select assignee',
565
+ resetButtonLabel: 'Unassign',
566
+ showSelectAllButtonLabel: 'Select All',
567
+ selected: [],
568
+ items: mockOptions,
569
+ multiple: true,
570
+ });
571
+
572
+ expect(wrapper.emitted('select-all')).toBe(undefined);
573
+
574
+ findSelectAllButton().trigger('click');
575
+
576
+ expect(wrapper.emitted('select-all')).toHaveLength(1);
577
+ });
578
+ });
579
+
517
580
  describe('when `infiniteScroll` prop is `true`', () => {
518
581
  it('should throw an error when items are groups', () => {
519
582
  expect(() => {
@@ -20,6 +20,7 @@ import {
20
20
  ARG_TYPE_SUBCATEGORY_SEARCH,
21
21
  ARG_TYPE_SUBCATEGORY_ACCESSIBILITY,
22
22
  ARG_TYPE_SUBCATEGORY_INFINITE_SCROLL,
23
+ LISTBOX_CONTAINER_HEIGHT,
23
24
  } from '../../../../utils/stories_constants';
24
25
  import { POSITION } from '../../../utilities/truncate/constants';
25
26
  import readme from './listbox.md';
@@ -55,6 +56,7 @@ const generateProps = ({
55
56
  toggleAriaLabelledBy,
56
57
  listAriaLabelledBy,
57
58
  resetButtonLabel = defaultValue('resetButtonLabel'),
59
+ showSelectAllButtonLabel = defaultValue('showSelectAllButtonLabel'),
58
60
  startOpened = true,
59
61
  fluidWidth,
60
62
  } = {}) => ({
@@ -83,6 +85,7 @@ const generateProps = ({
83
85
  toggleAriaLabelledBy,
84
86
  listAriaLabelledBy,
85
87
  resetButtonLabel,
88
+ showSelectAllButtonLabel,
86
89
  startOpened,
87
90
  fluidWidth,
88
91
  });
@@ -114,6 +117,7 @@ const makeBindings = (overrides = {}) =>
114
117
  ':toggle-aria-labelled-by': 'toggleAriaLabelledBy',
115
118
  ':list-aria-labelled-by': 'listAriaLabelledBy',
116
119
  ':reset-button-label': 'resetButtonLabel',
120
+ ':show-select-all-button-label': 'showSelectAllButtonLabel',
117
121
  ':fluid-width': 'fluidWidth',
118
122
  ...overrides,
119
123
  })
@@ -160,7 +164,7 @@ export const Default = (args, { argTypes }) => ({
160
164
  }),
161
165
  });
162
166
  Default.args = generateProps({ toggleAriaLabelledBy: 'listbox-label' });
163
- Default.decorators = [makeContainer({ height: '370px' })];
167
+ Default.decorators = [makeContainer({ height: LISTBOX_CONTAINER_HEIGHT })];
164
168
 
165
169
  export const HeaderAndFooter = (args, { argTypes }) => ({
166
170
  props: Object.keys(argTypes),
@@ -181,7 +185,7 @@ export const HeaderAndFooter = (args, { argTypes }) => ({
181
185
  }
182
186
  },
183
187
  methods: {
184
- selectAll() {
188
+ selectAllItems() {
185
189
  const allValues = mockOptions.map(({ value }) => value);
186
190
  this.selected = [...allValues];
187
191
  },
@@ -193,7 +197,7 @@ export const HeaderAndFooter = (args, { argTypes }) => ({
193
197
  `
194
198
  <template #footer>
195
199
  <div class="gl-border-t-solid gl-border-t-1 gl-border-t-gray-100 gl-display-flex gl-flex-direction-column gl-p-2! gl-pt-0!">
196
- <gl-button @click="selectAll" category="tertiary" block class="gl-justify-content-start! gl-mt-2!"">
200
+ <gl-button @click="selectAllItems" category="tertiary" block class="gl-justify-content-start! gl-mt-2!"">
197
201
  Select all
198
202
  </gl-button>
199
203
  <gl-button category="tertiary" block class="gl-justify-content-start! gl-mt-2!">
@@ -216,7 +220,56 @@ HeaderAndFooter.args = generateProps({
216
220
  multiple: true,
217
221
  block: true,
218
222
  });
219
- HeaderAndFooter.decorators = [makeContainer({ height: '370px' })];
223
+ HeaderAndFooter.decorators = [makeContainer({ height: LISTBOX_CONTAINER_HEIGHT })];
224
+
225
+ export const HeaderActions = (args, { argTypes }) => ({
226
+ props: Object.keys(argTypes),
227
+ components: {
228
+ GlCollapsibleListbox,
229
+ GlSearchBoxByType,
230
+ GlButtonGroup,
231
+ GlButton,
232
+ },
233
+ data() {
234
+ return {
235
+ selected: [],
236
+ };
237
+ },
238
+ computed: {
239
+ allValues() {
240
+ return mockOptions.map(({ value }) => value);
241
+ },
242
+ },
243
+ mounted() {
244
+ if (this.startOpened) {
245
+ openListbox(this);
246
+ }
247
+ },
248
+ methods: {
249
+ selectAllItems() {
250
+ this.selected = [...this.allValues];
251
+ },
252
+ onReset() {
253
+ this.selected = [];
254
+ },
255
+ },
256
+ template: template('', {
257
+ bindingOverrides: {
258
+ '@reset': 'onReset',
259
+ '@select-all': 'selectAllItems',
260
+ },
261
+ }),
262
+ });
263
+
264
+ HeaderActions.args = generateProps({
265
+ toggleText: 'Header actions',
266
+ headerText: 'Assign to department',
267
+ resetButtonLabel: 'Unassign',
268
+ showSelectAllButtonLabel: 'Select All',
269
+ multiple: true,
270
+ block: true,
271
+ });
272
+ HeaderActions.decorators = [makeContainer({ height: LISTBOX_CONTAINER_HEIGHT })];
220
273
 
221
274
  export const CustomListItem = (args, { argTypes }) => ({
222
275
  props: Object.keys(argTypes),
@@ -624,7 +677,7 @@ Searchable.args = generateProps({
624
677
  searchable: true,
625
678
  searchPlaceholder: 'Find department',
626
679
  });
627
- Searchable.decorators = [makeContainer({ height: '370px' })];
680
+ Searchable.decorators = [makeContainer({ height: LISTBOX_CONTAINER_HEIGHT })];
628
681
 
629
682
  export const SearchableGroups = (args, { argTypes }) => ({
630
683
  props: Object.keys(argTypes),
@@ -712,7 +765,7 @@ SearchableGroups.args = generateProps({
712
765
  searchable: true,
713
766
  items: mockGroups,
714
767
  });
715
- SearchableGroups.decorators = [makeContainer({ height: '370px' })];
768
+ SearchableGroups.decorators = [makeContainer({ height: LISTBOX_CONTAINER_HEIGHT })];
716
769
 
717
770
  export const InfiniteScroll = (
718
771
  args,
@@ -764,7 +817,7 @@ InfiniteScroll.parameters = {
764
817
  storyshots: { disable: true },
765
818
  };
766
819
  InfiniteScroll.args = generateProps();
767
- InfiniteScroll.decorators = [makeContainer({ height: '370px' })];
820
+ InfiniteScroll.decorators = [makeContainer({ height: LISTBOX_CONTAINER_HEIGHT })];
768
821
 
769
822
  export const WithLongContent = (args, { argTypes: { items, ...argTypes } }) => ({
770
823
  props: Object.keys(argTypes),
@@ -280,6 +280,17 @@ export default {
280
280
  required: false,
281
281
  default: '',
282
282
  },
283
+ /**
284
+ * The select all button's label, to be rendered in the header. If this is omitted, the button is not
285
+ * rendered.
286
+ * The select all button requires a header to be set, so this prop should be used in conjunction with
287
+ * headerText.
288
+ */
289
+ showSelectAllButtonLabel: {
290
+ type: String,
291
+ required: false,
292
+ default: '',
293
+ },
283
294
  /**
284
295
  * Render the toggle button as a block element
285
296
  */
@@ -373,6 +384,17 @@ export default {
373
384
  }
374
385
  return Boolean(this.selected);
375
386
  },
387
+ showSelectAllButton() {
388
+ if (!this.showSelectAllButtonLabel) {
389
+ return false;
390
+ }
391
+
392
+ if (!this.multiple) {
393
+ return false;
394
+ }
395
+
396
+ return this.selected.length === 0;
397
+ },
376
398
  showIntersectionObserver() {
377
399
  return this.infiniteScroll && !this.infiniteScrollLoading && !this.loading && !this.searching;
378
400
  },
@@ -432,6 +454,16 @@ export default {
432
454
  }
433
455
  },
434
456
  },
457
+ showSelectAllButtonLabel: {
458
+ immediate: true,
459
+ handler(showSelectAllButtonLabel) {
460
+ if (showSelectAllButtonLabel && !this.headerText) {
461
+ throw new Error(
462
+ 'The select all button cannot be rendered without a header. Either provide a header via the headerText prop, or do not provide the showSelectAllButtonLabel prop.'
463
+ );
464
+ }
465
+ },
466
+ },
435
467
  infiniteScroll: {
436
468
  immediate: true,
437
469
  handler(newValue) {
@@ -591,6 +623,14 @@ export default {
591
623
  this.$emit('reset');
592
624
  this.closeAndFocus();
593
625
  },
626
+ onSelectAllButtonClicked() {
627
+ /**
628
+ * Emitted when the select all button is clicked
629
+ *
630
+ * @event select-all
631
+ */
632
+ this.$emit('select-all');
633
+ },
594
634
  closeAndFocus() {
595
635
  this.$refs.baseDropdown.closeAndFocus();
596
636
  },
@@ -690,12 +730,21 @@ export default {
690
730
  <gl-button
691
731
  v-if="showResetButton"
692
732
  category="tertiary"
693
- class="gl-focus-inset-border-2-blue-400! gl-flex-shrink-0 gl-font-sm! gl-px-2! gl-py-2! gl-w-auto! gl-m-0!"
733
+ class="gl-focus-inset-border-2-blue-400! gl-flex-shrink-0 gl-font-sm! gl-px-2! gl-py-2! gl-w-auto! gl-m-0! gl-max-w-50p gl-text-overflow-ellipsis"
694
734
  data-testid="listbox-reset-button"
695
735
  @click="onResetButtonClicked"
696
736
  >
697
737
  {{ resetButtonLabel }}
698
738
  </gl-button>
739
+ <gl-button
740
+ v-if="showSelectAllButton"
741
+ category="tertiary"
742
+ class="gl-focus-inset-border-2-blue-400! gl-flex-shrink-0 gl-font-sm! gl-px-2! gl-py-2! gl-w-auto! gl-m-0! gl-max-w-50p gl-text-overflow-ellipsis"
743
+ data-testid="listbox-select-all-button"
744
+ @click="onSelectAllButtonClicked"
745
+ >
746
+ {{ showSelectAllButtonLabel }}
747
+ </gl-button>
699
748
  </div>
700
749
 
701
750
  <div v-if="searchable" :class="$options.HEADER_ITEMS_BORDER_CLASSES">
@@ -269,8 +269,8 @@ export default {
269
269
  },
270
270
  },
271
271
  beforeDestroy() {
272
- this.chart.getDom().removeEventListener('mousemove', this.debouncedShowHideTooltip);
273
- this.chart.getDom().removeEventListener('mouseout', this.debouncedShowHideTooltip);
272
+ this.chart.getZr().off('mousemove', this.debouncedShowHideTooltip);
273
+ this.chart.getZr().off('mouseout', this.debouncedShowHideTooltip);
274
274
 
275
275
  this.chart.off('mouseout', this.hideAnnotationsTooltip);
276
276
  this.chart.off('mouseover', this.onChartMouseOver);
@@ -293,8 +293,8 @@ export default {
293
293
  // when the mouse is hovered over the parent container
294
294
  // of echarts' svg element. This works only for data points
295
295
  // and not markPoints.
296
- chart.getDom().addEventListener('mousemove', this.debouncedShowHideTooltip);
297
- chart.getDom().addEventListener('mouseout', this.debouncedShowHideTooltip);
296
+ chart.getZr().on('mousemove', this.debouncedShowHideTooltip);
297
+ chart.getZr().on('mouseout', this.debouncedShowHideTooltip);
298
298
 
299
299
  // eCharts inbuild mouse events
300
300
  // https://echarts.apache.org/en/api.html#events.Mouse%20events
@@ -314,7 +314,7 @@ export default {
314
314
  this.chart = chart;
315
315
  this.$emit('created', chart);
316
316
  },
317
- showHideTooltip(mouseEvent) {
317
+ showHideTooltip({ event: mouseEvent }) {
318
318
  this.showDataTooltip = this.chart.containPixel('grid', [mouseEvent.zrX, mouseEvent.zrY]);
319
319
 
320
320
  this.dataTooltipPosition = {
@@ -22,3 +22,9 @@ export const SERIES_NAME = {
22
22
  [SERIES_NAME_LONG_WITHOUT_SPACES]:
23
23
  'Series_name_long._Lorem_ipsum_dolor_sit_amet,_consectetur_adipiscing_elit._Sed_tincidunt_interdum_sapien_ut_blandit._Nulla_fermentum_nisi_id_euismod_vulputate._END',
24
24
  };
25
+
26
+ /**
27
+ * Reused constants for ListBox
28
+ */
29
+
30
+ export const LISTBOX_CONTAINER_HEIGHT = '370px';