@gitlab/ui 66.31.1 → 66.32.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,10 @@
1
+ # [66.32.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v66.31.1...v66.32.0) (2023-10-16)
2
+
3
+
4
+ ### Features
5
+
6
+ * **GlDisclosureDropdown:** add a beforeClose event ([cb4fdb1](https://gitlab.com/gitlab-org/gitlab-ui/commit/cb4fdb10f3669e80c688ea8e442f6dd9cb7ce973))
7
+
1
8
  ## [66.31.1](https://gitlab.com/gitlab-org/gitlab-ui/compare/v66.31.0...v66.31.1) (2023-10-13)
2
9
 
3
10
 
@@ -1,7 +1,7 @@
1
1
  import uniqueId from 'lodash/uniqueId';
2
2
  import { offset, autoPlacement, size, autoUpdate, computePosition } from '@floating-ui/dom';
3
3
  import { buttonCategoryOptions, dropdownVariantOptions, buttonSizeOptions, dropdownPlacements, dropdownAllowedAutoPlacements } from '../../../../utils/constants';
4
- import { POSITION_ABSOLUTE, POSITION_FIXED, GL_DROPDOWN_CONTENTS_CLASS, GL_DROPDOWN_SHOWN, GL_DROPDOWN_HIDDEN, ENTER, SPACE, ARROW_DOWN, GL_DROPDOWN_FOCUS_CONTENT } from '../constants';
4
+ import { POSITION_ABSOLUTE, POSITION_FIXED, GL_DROPDOWN_CONTENTS_CLASS, GL_DROPDOWN_BEFORE_CLOSE, GL_DROPDOWN_SHOWN, GL_DROPDOWN_HIDDEN, ENTER, SPACE, ARROW_DOWN, GL_DROPDOWN_FOCUS_CONTENT } from '../constants';
5
5
  import { isElementFocusable, isElementTabbable, logWarning } from '../../../../utils/utils';
6
6
  import GlButton from '../../button/button';
7
7
  import GlIcon from '../../icon/icon';
@@ -187,7 +187,7 @@ var script = {
187
187
  ...this.ariaAttributes,
188
188
  listeners: {
189
189
  keydown: event => this.onKeydown(event),
190
- click: () => this.toggle()
190
+ click: event => this.toggle(event)
191
191
  }
192
192
  };
193
193
  }
@@ -196,7 +196,7 @@ var script = {
196
196
  class: 'gl-new-dropdown-custom-toggle',
197
197
  listeners: {
198
198
  keydown: event => this.onKeydown(event),
199
- click: () => this.toggle()
199
+ click: event => this.toggle(event)
200
200
  }
201
201
  };
202
202
  },
@@ -314,7 +314,17 @@ var script = {
314
314
  (_this$observer = this.observer) === null || _this$observer === void 0 ? void 0 : _this$observer.disconnect();
315
315
  (_this$stopAutoUpdate = this.stopAutoUpdate) === null || _this$stopAutoUpdate === void 0 ? void 0 : _this$stopAutoUpdate.call(this);
316
316
  },
317
- async toggle() {
317
+ async toggle(event) {
318
+ if (event && this.visible) {
319
+ let prevented = false;
320
+ this.$emit(GL_DROPDOWN_BEFORE_CLOSE, {
321
+ originalEvent: event,
322
+ preventDefault() {
323
+ prevented = true;
324
+ }
325
+ });
326
+ if (prevented) return false;
327
+ }
318
328
  this.visible = !this.visible;
319
329
  if (this.visible) {
320
330
  // The dropdown needs to be actually visible before we compute its position with Floating UI.
@@ -331,6 +341,9 @@ var script = {
331
341
  this.stopFloating();
332
342
  this.$emit(GL_DROPDOWN_HIDDEN);
333
343
  }
344
+
345
+ // this is to check whether `toggle` was prevented or not
346
+ return true;
334
347
  },
335
348
  open() {
336
349
  if (this.visible) {
@@ -338,18 +351,20 @@ var script = {
338
351
  }
339
352
  this.toggle();
340
353
  },
341
- close() {
354
+ close(event) {
342
355
  if (!this.visible) {
343
356
  return;
344
357
  }
345
- this.toggle();
358
+ this.toggle(event);
346
359
  },
347
- closeAndFocus() {
360
+ async closeAndFocus(event) {
348
361
  if (!this.visible) {
349
362
  return;
350
363
  }
351
- this.toggle();
352
- this.focusToggle();
364
+ const hasToggled = await this.toggle(event);
365
+ if (hasToggled) {
366
+ this.focusToggle();
367
+ }
353
368
  },
354
369
  focusToggle() {
355
370
  this.toggleElement.focus();
@@ -370,7 +385,7 @@ var script = {
370
385
  toggleOnEnter = false;
371
386
  }
372
387
  if (code === ENTER && toggleOnEnter || code === SPACE && toggleOnSpace) {
373
- this.toggle();
388
+ this.toggle(event);
374
389
  }
375
390
  if (code === ARROW_DOWN) {
376
391
  this.$emit(GL_DROPDOWN_FOCUS_CONTENT, event);
@@ -1,6 +1,7 @@
1
1
  // base dropdown events
2
2
  const GL_DROPDOWN_SHOWN = 'shown';
3
3
  const GL_DROPDOWN_HIDDEN = 'hidden';
4
+ const GL_DROPDOWN_BEFORE_CLOSE = 'beforeClose';
4
5
  const GL_DROPDOWN_FOCUS_CONTENT = 'focusContent';
5
6
 
6
7
  // KEY Codes
@@ -17,4 +18,4 @@ const POSITION_ABSOLUTE = 'absolute';
17
18
  const POSITION_FIXED = 'fixed';
18
19
  const GL_DROPDOWN_CONTENTS_CLASS = 'gl-new-dropdown-contents';
19
20
 
20
- export { ARROW_DOWN, ARROW_UP, END, ENTER, GL_DROPDOWN_CONTENTS_CLASS, GL_DROPDOWN_FOCUS_CONTENT, GL_DROPDOWN_HIDDEN, GL_DROPDOWN_SHOWN, HOME, POSITION_ABSOLUTE, POSITION_FIXED, SPACE };
21
+ export { ARROW_DOWN, ARROW_UP, END, ENTER, GL_DROPDOWN_BEFORE_CLOSE, GL_DROPDOWN_CONTENTS_CLASS, GL_DROPDOWN_FOCUS_CONTENT, GL_DROPDOWN_HIDDEN, GL_DROPDOWN_SHOWN, HOME, POSITION_ABSOLUTE, POSITION_FIXED, SPACE };
@@ -1,7 +1,7 @@
1
1
  import clamp from 'lodash/clamp';
2
2
  import uniqueId from 'lodash/uniqueId';
3
3
  import { stopEvent, filterVisible } from '../../../../utils/utils';
4
- import { GL_DROPDOWN_SHOWN, GL_DROPDOWN_HIDDEN, GL_DROPDOWN_FOCUS_CONTENT, POSITION_ABSOLUTE, POSITION_FIXED, HOME, END, ARROW_UP, ARROW_DOWN, ENTER, SPACE, GL_DROPDOWN_CONTENTS_CLASS } from '../constants';
4
+ import { GL_DROPDOWN_SHOWN, GL_DROPDOWN_HIDDEN, GL_DROPDOWN_BEFORE_CLOSE, GL_DROPDOWN_FOCUS_CONTENT, POSITION_ABSOLUTE, POSITION_FIXED, HOME, END, ARROW_UP, ARROW_DOWN, ENTER, SPACE, GL_DROPDOWN_CONTENTS_CLASS } from '../constants';
5
5
  import { buttonCategoryOptions, dropdownVariantOptions, buttonSizeOptions, dropdownPlacements } from '../../../../utils/constants';
6
6
  import GlBaseDropdown from '../base_dropdown/base_dropdown';
7
7
  import GlDisclosureDropdownItem, { ITEM_CLASS } from './disclosure_dropdown_item';
@@ -16,6 +16,7 @@ var script = {
16
16
  events: {
17
17
  GL_DROPDOWN_SHOWN,
18
18
  GL_DROPDOWN_HIDDEN,
19
+ GL_DROPDOWN_BEFORE_CLOSE,
19
20
  GL_DROPDOWN_FOCUS_CONTENT
20
21
  },
21
22
  components: {
@@ -232,6 +233,14 @@ var script = {
232
233
  */
233
234
  this.$emit(GL_DROPDOWN_SHOWN);
234
235
  },
236
+ onBeforeClose(event) {
237
+ /**
238
+ * Emitted when dropdown is about to be closed
239
+ *
240
+ * @event beforeClose
241
+ */
242
+ this.$emit(GL_DROPDOWN_BEFORE_CLOSE, event);
243
+ },
235
244
  onHide() {
236
245
  /**
237
246
  * Emitted when dropdown is hidden
@@ -311,7 +320,7 @@ var script = {
311
320
  const __vue_script__ = script;
312
321
 
313
322
  /* template */
314
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-base-dropdown',{ref:"baseDropdown",staticClass:"gl-disclosure-dropdown",attrs:{"aria-labelledby":_vm.toggleAriaLabelledBy,"toggle-id":_vm.toggleId,"toggle-text":_vm.toggleText,"toggle-class":_vm.toggleClass,"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,"block":_vm.block,"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,_vm.$options.events.GL_DROPDOWN_FOCUS_CONTENT,_vm.onKeydown]),scopedSlots:_vm._u([(_vm.hasCustomToggle)?{key:"toggle",fn:function(){return [_vm._t("toggle")]},proxy:true}:null],null,true)},[_vm._v(" "),_vm._t("header"),_vm._v(" "),_c(_vm.disclosureTag,{ref:"content",tag:"component",class:_vm.$options.GL_DROPDOWN_CONTENTS_CLASS,attrs:{"id":_vm.disclosureId,"aria-labelledby":_vm.listAriaLabelledBy || _vm.toggleId,"data-testid":"disclosure-content","tabindex":"-1"},on:{"keydown":_vm.onKeydown,"click":_vm.handleAutoClose}},[_vm._t("default",function(){return [_vm._l((_vm.items),function(item,index){return [(_vm.isItem(item))?[_c('gl-disclosure-dropdown-item',{key:_vm.uniqueItemId(),attrs:{"item":item},on:{"action":_vm.handleAction},scopedSlots:_vm._u([{key:"list-item",fn:function(){return [_vm._t("list-item",null,{"item":item})]},proxy:true}],null,true)})]:[_c('gl-disclosure-dropdown-group',{key:item.name,attrs:{"bordered":index !== 0,"group":item},on:{"action":_vm.handleAction},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.$scopedSlots['list-item'])?_vm._l((item.items),function(groupItem){return _c('gl-disclosure-dropdown-item',{key:_vm.uniqueItemId(),attrs:{"item":groupItem},on:{"action":_vm.handleAction},scopedSlots:_vm._u([{key:"list-item",fn:function(){return [_vm._t("list-item",null,{"item":groupItem})]},proxy:true}],null,true)})}):_vm._e()],2)]]})]})],2),_vm._v(" "),_vm._t("footer")],2)};
323
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-base-dropdown',{ref:"baseDropdown",staticClass:"gl-disclosure-dropdown",attrs:{"aria-labelledby":_vm.toggleAriaLabelledBy,"toggle-id":_vm.toggleId,"toggle-text":_vm.toggleText,"toggle-class":_vm.toggleClass,"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,"block":_vm.block,"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,_vm.$options.events.GL_DROPDOWN_BEFORE_CLOSE,_vm.onBeforeClose,_vm.$options.events.GL_DROPDOWN_FOCUS_CONTENT,_vm.onKeydown]),scopedSlots:_vm._u([(_vm.hasCustomToggle)?{key:"toggle",fn:function(){return [_vm._t("toggle")]},proxy:true}:null],null,true)},[_vm._v(" "),_vm._t("header"),_vm._v(" "),_c(_vm.disclosureTag,{ref:"content",tag:"component",class:_vm.$options.GL_DROPDOWN_CONTENTS_CLASS,attrs:{"id":_vm.disclosureId,"aria-labelledby":_vm.listAriaLabelledBy || _vm.toggleId,"data-testid":"disclosure-content","tabindex":"-1"},on:{"keydown":_vm.onKeydown,"click":_vm.handleAutoClose}},[_vm._t("default",function(){return [_vm._l((_vm.items),function(item,index){return [(_vm.isItem(item))?[_c('gl-disclosure-dropdown-item',{key:_vm.uniqueItemId(),attrs:{"item":item},on:{"action":_vm.handleAction},scopedSlots:_vm._u([{key:"list-item",fn:function(){return [_vm._t("list-item",null,{"item":item})]},proxy:true}],null,true)})]:[_c('gl-disclosure-dropdown-group',{key:item.name,attrs:{"bordered":index !== 0,"group":item},on:{"action":_vm.handleAction},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.$scopedSlots['list-item'])?_vm._l((item.items),function(groupItem){return _c('gl-disclosure-dropdown-item',{key:_vm.uniqueItemId(),attrs:{"item":groupItem},on:{"action":_vm.handleAction},scopedSlots:_vm._u([{key:"list-item",fn:function(){return [_vm._t("list-item",null,{"item":groupItem})]},proxy:true}],null,true)})}):_vm._e()],2)]]})]})],2),_vm._v(" "),_vm._t("footer")],2)};
315
324
  var __vue_staticRenderFns__ = [];
316
325
 
317
326
  /* style */
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Fri, 13 Oct 2023 20:03:52 GMT
3
+ * Generated on Mon, 16 Oct 2023 11:22:12 GMT
4
4
  */
5
5
 
6
6
  :root {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Fri, 13 Oct 2023 20:03:52 GMT
3
+ * Generated on Mon, 16 Oct 2023 11:22:12 GMT
4
4
  */
5
5
 
6
6
  :root {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Fri, 13 Oct 2023 20:03:52 GMT
3
+ * Generated on Mon, 16 Oct 2023 11:22:12 GMT
4
4
  */
5
5
 
6
6
  export const BLACK = "#fff";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Fri, 13 Oct 2023 20:03:52 GMT
3
+ * Generated on Mon, 16 Oct 2023 11:22:12 GMT
4
4
  */
5
5
 
6
6
  export const BLACK = "#000";
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Fri, 13 Oct 2023 20:03:52 GMT
3
+ // Generated on Mon, 16 Oct 2023 11:22:12 GMT
4
4
 
5
5
  $red-950: #fff4f3;
6
6
  $red-900: #fcf1ef;
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Fri, 13 Oct 2023 20:03:52 GMT
3
+ // Generated on Mon, 16 Oct 2023 11:22:12 GMT
4
4
 
5
5
  $gl-line-height-52: 3.25rem;
6
6
  $gl-line-height-44: 2.75rem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "66.31.1",
3
+ "version": "66.32.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -88,8 +88,8 @@
88
88
  },
89
89
  "devDependencies": {
90
90
  "@arkweid/lefthook": "0.7.7",
91
- "@babel/core": "^7.23.0",
92
- "@babel/preset-env": "^7.22.20",
91
+ "@babel/core": "^7.23.2",
92
+ "@babel/preset-env": "^7.23.2",
93
93
  "@babel/preset-react": "^7.22.15",
94
94
  "@gitlab/eslint-plugin": "19.0.0",
95
95
  "@gitlab/fonts": "^1.2.0",
@@ -6,6 +6,7 @@ import {
6
6
  GL_DROPDOWN_FOCUS_CONTENT,
7
7
  GL_DROPDOWN_HIDDEN,
8
8
  GL_DROPDOWN_SHOWN,
9
+ GL_DROPDOWN_BEFORE_CLOSE,
9
10
  GL_DROPDOWN_CONTENTS_CLASS,
10
11
  } from '../constants';
11
12
  import { waitForAnimationFrame } from '../../../../utils/test_utils';
@@ -28,7 +29,7 @@ const DEFAULT_BTN_TOGGLE_CLASSES = [
28
29
  describe('base dropdown', () => {
29
30
  let wrapper;
30
31
 
31
- const buildWrapper = (propsData, slots = {}) => {
32
+ const buildWrapper = (propsData, slots = {}, listeners = {}) => {
32
33
  wrapper = mount(GlBaseDropdown, {
33
34
  propsData: {
34
35
  toggleId: 'dropdown-toggle-btn-1',
@@ -39,6 +40,7 @@ describe('base dropdown', () => {
39
40
  ...slots,
40
41
  },
41
42
  attachTo: document.body,
43
+ listeners,
42
44
  });
43
45
  };
44
46
 
@@ -332,6 +334,60 @@ describe('base dropdown', () => {
332
334
  });
333
335
  });
334
336
 
337
+ describe('beforeClose event', () => {
338
+ let event;
339
+
340
+ beforeEach(() => {
341
+ event = undefined;
342
+ buildWrapper(undefined, undefined, {
343
+ [GL_DROPDOWN_BEFORE_CLOSE]({ originalEvent, preventDefault }) {
344
+ event = originalEvent;
345
+ preventDefault();
346
+ },
347
+ });
348
+ });
349
+
350
+ it('should prevent closing', async () => {
351
+ const toggle = findDefaultDropdownToggle();
352
+ const menu = findDropdownMenu();
353
+
354
+ await toggle.trigger('click');
355
+
356
+ menu.element.focus();
357
+ await menu.trigger('keydown.esc');
358
+ expect(menu.classes('gl-display-block!')).toBe(true);
359
+ expect(toggle.attributes('aria-expanded')).toBeDefined();
360
+ expect(wrapper.emitted(GL_DROPDOWN_HIDDEN)).toBeUndefined();
361
+ expect(toggle.element).not.toHaveFocus();
362
+ });
363
+
364
+ it('should contain original keyboard event', async () => {
365
+ const toggle = findDefaultDropdownToggle();
366
+ const menu = findDropdownMenu();
367
+ await toggle.trigger('click');
368
+ await menu.trigger('keydown.esc');
369
+ expect(event.type).toBe('keydown');
370
+ });
371
+
372
+ it('should contain original toggle click event', async () => {
373
+ const toggle = findDefaultDropdownToggle();
374
+ await toggle.trigger('click');
375
+ await toggle.trigger('click');
376
+ expect(event.type).toBe('click');
377
+ });
378
+
379
+ it('should contain original outside click event', async () => {
380
+ const outsideElement = document.createElement('div');
381
+ document.body.appendChild(outsideElement);
382
+
383
+ const toggle = findDefaultDropdownToggle();
384
+ await toggle.trigger('click');
385
+ const click = new MouseEvent('click', { bubbles: true });
386
+ outsideElement.dispatchEvent(click);
387
+ expect(event).toBe(click);
388
+ });
389
+ });
390
+
335
391
  describe('Custom toggle', () => {
336
392
  const customToggleTestId = 'custom-toggle';
337
393
  const toggleContent = `<button data-testid="${customToggleTestId}">Custom toggle</button>`;
@@ -11,6 +11,7 @@ import {
11
11
  import {
12
12
  GL_DROPDOWN_SHOWN,
13
13
  GL_DROPDOWN_HIDDEN,
14
+ GL_DROPDOWN_BEFORE_CLOSE,
14
15
  GL_DROPDOWN_FOCUS_CONTENT,
15
16
  ENTER,
16
17
  SPACE,
@@ -202,7 +203,7 @@ export default {
202
203
  ...this.ariaAttributes,
203
204
  listeners: {
204
205
  keydown: (event) => this.onKeydown(event),
205
- click: () => this.toggle(),
206
+ click: (event) => this.toggle(event),
206
207
  },
207
208
  };
208
209
  }
@@ -212,7 +213,7 @@ export default {
212
213
  class: 'gl-new-dropdown-custom-toggle',
213
214
  listeners: {
214
215
  keydown: (event) => this.onKeydown(event),
215
- click: () => this.toggle(),
216
+ click: (event) => this.toggle(event),
216
217
  },
217
218
  };
218
219
  },
@@ -333,7 +334,17 @@ export default {
333
334
  this.observer?.disconnect();
334
335
  this.stopAutoUpdate?.();
335
336
  },
336
- async toggle() {
337
+ async toggle(event) {
338
+ if (event && this.visible) {
339
+ let prevented = false;
340
+ this.$emit(GL_DROPDOWN_BEFORE_CLOSE, {
341
+ originalEvent: event,
342
+ preventDefault() {
343
+ prevented = true;
344
+ },
345
+ });
346
+ if (prevented) return false;
347
+ }
337
348
  this.visible = !this.visible;
338
349
 
339
350
  if (this.visible) {
@@ -352,6 +363,9 @@ export default {
352
363
  this.stopFloating();
353
364
  this.$emit(GL_DROPDOWN_HIDDEN);
354
365
  }
366
+
367
+ // this is to check whether `toggle` was prevented or not
368
+ return true;
355
369
  },
356
370
  open() {
357
371
  if (this.visible) {
@@ -359,18 +373,20 @@ export default {
359
373
  }
360
374
  this.toggle();
361
375
  },
362
- close() {
376
+ close(event) {
363
377
  if (!this.visible) {
364
378
  return;
365
379
  }
366
- this.toggle();
380
+ this.toggle(event);
367
381
  },
368
- closeAndFocus() {
382
+ async closeAndFocus(event) {
369
383
  if (!this.visible) {
370
384
  return;
371
385
  }
372
- this.toggle();
373
- this.focusToggle();
386
+ const hasToggled = await this.toggle(event);
387
+ if (hasToggled) {
388
+ this.focusToggle();
389
+ }
374
390
  },
375
391
  focusToggle() {
376
392
  this.toggleElement.focus();
@@ -392,7 +408,7 @@ export default {
392
408
  }
393
409
 
394
410
  if ((code === ENTER && toggleOnEnter) || (code === SPACE && toggleOnSpace)) {
395
- this.toggle();
411
+ this.toggle(event);
396
412
  }
397
413
 
398
414
  if (code === ARROW_DOWN) {
@@ -1,6 +1,7 @@
1
1
  // base dropdown events
2
2
  export const GL_DROPDOWN_SHOWN = 'shown';
3
3
  export const GL_DROPDOWN_HIDDEN = 'hidden';
4
+ export const GL_DROPDOWN_BEFORE_CLOSE = 'beforeClose';
4
5
  export const GL_DROPDOWN_FOCUS_CONTENT = 'focusContent';
5
6
 
6
7
  // KEY Codes
@@ -38,6 +38,27 @@ The disclosure dropdown is closed by any of the following:
38
38
  - clicking anywhere outside the component
39
39
  - clicking the action or link inside the dropdown
40
40
 
41
+ Before closing, `GlDisclosureDropdown` emits a `beforeClose` event with these properties:
42
+
43
+ 1. `originalEvent` – the event that triggered closing of the dropdown
44
+ 2. `preventDefault` – a method which will prevent closing of the dropdown
45
+
46
+ An example of using this event to prevent the dropdown from closing:
47
+
48
+ ```html
49
+ <gl-disclosure-dropdown @beforeClose="$event.preventDefault()" />
50
+ ```
51
+
52
+ Note that this method will also prevent the dropdown from closing even if the trigger button is clicked.
53
+
54
+ You can use the `preventDefault` to filter out events that are causing undesired dropdown closing:
55
+
56
+ ```html
57
+ <gl-disclosure-dropdown
58
+ @beforeClose="(e) => { ignoreElement.contains(e.originalEvent.target) && e.preventDefault() }"
59
+ />
60
+ ```
61
+
41
62
  After closing, `GlDisclosureDropdown` emits a `hidden` event.
42
63
 
43
64
  ### Setting disclosure dropdown items
@@ -5,6 +5,7 @@ import GlBaseDropdown from '../base_dropdown/base_dropdown.vue';
5
5
  import {
6
6
  GL_DROPDOWN_SHOWN,
7
7
  GL_DROPDOWN_HIDDEN,
8
+ GL_DROPDOWN_BEFORE_CLOSE,
8
9
  GL_DROPDOWN_FOCUS_CONTENT,
9
10
  ARROW_DOWN,
10
11
  ARROW_UP,
@@ -100,6 +101,20 @@ describe('GlDisclosureDropdown', () => {
100
101
  });
101
102
  });
102
103
 
104
+ describe('onClose', () => {
105
+ let data;
106
+
107
+ beforeEach(() => {
108
+ buildWrapper();
109
+ data = { event: {}, preventDefault() {} };
110
+ findBaseDropdown().vm.$emit(GL_DROPDOWN_BEFORE_CLOSE, data);
111
+ });
112
+
113
+ it('should re-emit the event', () => {
114
+ expect(wrapper.emitted(GL_DROPDOWN_BEFORE_CLOSE)).toStrictEqual([[data]]);
115
+ });
116
+ });
117
+
103
118
  describe('onHide', () => {
104
119
  beforeEach(() => {
105
120
  buildWrapper();
@@ -6,6 +6,7 @@ import { stopEvent, filterVisible } from '../../../../utils/utils';
6
6
  import {
7
7
  GL_DROPDOWN_SHOWN,
8
8
  GL_DROPDOWN_HIDDEN,
9
+ GL_DROPDOWN_BEFORE_CLOSE,
9
10
  GL_DROPDOWN_FOCUS_CONTENT,
10
11
  ENTER,
11
12
  SPACE,
@@ -35,6 +36,7 @@ export default {
35
36
  events: {
36
37
  GL_DROPDOWN_SHOWN,
37
38
  GL_DROPDOWN_HIDDEN,
39
+ GL_DROPDOWN_BEFORE_CLOSE,
38
40
  GL_DROPDOWN_FOCUS_CONTENT,
39
41
  },
40
42
  components: {
@@ -250,6 +252,14 @@ export default {
250
252
  */
251
253
  this.$emit(GL_DROPDOWN_SHOWN);
252
254
  },
255
+ onBeforeClose(event) {
256
+ /**
257
+ * Emitted when dropdown is about to be closed
258
+ *
259
+ * @event beforeClose
260
+ */
261
+ this.$emit(GL_DROPDOWN_BEFORE_CLOSE, event);
262
+ },
253
263
  onHide() {
254
264
  /**
255
265
  * Emitted when dropdown is hidden
@@ -349,6 +359,7 @@ export default {
349
359
  class="gl-disclosure-dropdown"
350
360
  @[$options.events.GL_DROPDOWN_SHOWN]="onShow"
351
361
  @[$options.events.GL_DROPDOWN_HIDDEN]="onHide"
362
+ @[$options.events.GL_DROPDOWN_BEFORE_CLOSE]="onBeforeClose"
352
363
  @[$options.events.GL_DROPDOWN_FOCUS_CONTENT]="onKeydown"
353
364
  >
354
365
  <template v-if="hasCustomToggle" #toggle>