@gitlab/ui 62.10.0 → 62.11.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
+ # [62.11.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v62.10.0...v62.11.0) (2023-05-12)
2
+
3
+
4
+ ### Features
5
+
6
+ * **GlDisclosureDropdown:** Close the disclosure dropdown on item click ([f4c2208](https://gitlab.com/gitlab-org/gitlab-ui/commit/f4c2208af8f11b5aa96faa6bdb88898f67b4edcc))
7
+
1
8
  # [62.10.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v62.9.3...v62.10.0) (2023-05-11)
2
9
 
3
10
 
@@ -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, HOME, END, ARROW_UP, ARROW_DOWN } from '../constants';
4
+ import { GL_DROPDOWN_SHOWN, GL_DROPDOWN_HIDDEN, GL_DROPDOWN_FOCUS_CONTENT, HOME, END, ARROW_UP, ARROW_DOWN, ENTER, SPACE } 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';
@@ -10,6 +10,7 @@ import { itemsValidator, hasOnlyListItems, isItem } from './utils';
10
10
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
11
11
 
12
12
  //
13
+ const ITEM_SELECTOR = `.${ITEM_CLASS}`;
13
14
  var script = {
14
15
  name: 'GlDisclosureDropdown',
15
16
  events: {
@@ -169,6 +170,14 @@ var script = {
169
170
  type: Boolean,
170
171
  required: false,
171
172
  default: false
173
+ },
174
+ /**
175
+ * Close the dropdown on item click (action)
176
+ */
177
+ autoClose: {
178
+ type: Boolean,
179
+ required: false,
180
+ default: true
172
181
  }
173
182
  },
174
183
  data() {
@@ -228,6 +237,8 @@ var script = {
228
237
  this.focusNextItem(event, elements, -1);
229
238
  } else if (code === ARROW_DOWN) {
230
239
  this.focusNextItem(event, elements, 1);
240
+ } else if (code === ENTER || code === SPACE) {
241
+ this.handleAutoClose(event);
231
242
  } else {
232
243
  stop = false;
233
244
  }
@@ -237,7 +248,7 @@ var script = {
237
248
  },
238
249
  getFocusableListItemElements() {
239
250
  var _this$$refs$content;
240
- const items = (_this$$refs$content = this.$refs.content) === null || _this$$refs$content === void 0 ? void 0 : _this$$refs$content.querySelectorAll(`.${ITEM_CLASS}`);
251
+ const items = (_this$$refs$content = this.$refs.content) === null || _this$$refs$content === void 0 ? void 0 : _this$$refs$content.querySelectorAll(ITEM_SELECTOR);
241
252
  return filterVisible(Array.from(items || []));
242
253
  },
243
254
  focusNextItem(event, elements, offset) {
@@ -264,6 +275,11 @@ var script = {
264
275
  */
265
276
  this.$emit('action', action);
266
277
  },
278
+ handleAutoClose(e) {
279
+ if (this.autoClose && e.target.closest(ITEM_SELECTOR)) {
280
+ this.closeAndFocus();
281
+ }
282
+ },
267
283
  uniqueItemId() {
268
284
  return uniqueId(`disclosure-item-`);
269
285
  },
@@ -275,7 +291,7 @@ var script = {
275
291
  const __vue_script__ = script;
276
292
 
277
293
  /* template */
278
- 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,"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,_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",staticClass:"gl-new-dropdown-contents",attrs:{"id":_vm.disclosureId,"aria-labelledby":_vm.listAriaLabelledBy || _vm.toggleId,"data-testid":"disclosure-content","tabindex":"-1"},on:{"keydown":_vm.onKeydown}},[_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)};
294
+ 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,"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,_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",staticClass:"gl-new-dropdown-contents",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)};
279
295
  var __vue_staticRenderFns__ = [];
280
296
 
281
297
  /* style */
@@ -308,3 +324,4 @@ var __vue_staticRenderFns__ = [];
308
324
  );
309
325
 
310
326
  export default __vue_component__;
327
+ export { ITEM_SELECTOR };
@@ -79,11 +79,11 @@ var script = {
79
79
  code
80
80
  } = event;
81
81
  if (code === ENTER || code === SPACE) {
82
- stopEvent(event);
83
82
  if (this.isCustomContent) {
84
83
  this.action();
85
84
  } else {
86
85
  var _this$$refs$item;
86
+ stopEvent(event);
87
87
  /** Instead of simply navigating or calling the action, we want
88
88
  * the `a/button` to be the target of the event as it might have additional attributes.
89
89
  * E.g. `a` might have `target` attribute.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "62.10.0",
3
+ "version": "62.11.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -89,7 +89,7 @@
89
89
  "@gitlab/eslint-plugin": "19.0.0",
90
90
  "@gitlab/fonts": "^1.2.0",
91
91
  "@gitlab/stylelint-config": "4.1.0",
92
- "@gitlab/svgs": "3.44.0",
92
+ "@gitlab/svgs": "3.45.0",
93
93
  "@rollup/plugin-commonjs": "^11.1.0",
94
94
  "@rollup/plugin-node-resolve": "^7.1.3",
95
95
  "@rollup/plugin-replace": "^2.3.2",
@@ -36,6 +36,7 @@ The disclosure dropdown is closed by any of the following:
36
36
 
37
37
  - pressing <kbd>Esc</kbd>
38
38
  - clicking anywhere outside the component
39
+ - clicking the action or link inside the dropdown
39
40
 
40
41
  After closing, `GlDisclosureDropdown` emits a `hidden` event.
41
42
 
@@ -323,4 +323,20 @@ describe('GlDisclosureDropdown', () => {
323
323
  expect(findBaseDropdown().props('fluidWidth')).toBe(true);
324
324
  });
325
325
  });
326
+
327
+ describe('auto closing', () => {
328
+ it('closes the dropdown when `autoClose` is set on item click', () => {
329
+ buildWrapper({ items: mockItems });
330
+ const closeSpy = jest.spyOn(wrapper.vm.$refs.baseDropdown, 'closeAndFocus');
331
+ findListItem(0).trigger('click');
332
+ expect(closeSpy).toHaveBeenCalled();
333
+ });
334
+
335
+ it('does not close the dropdown on item click when `autoClose` is set to `false`', () => {
336
+ buildWrapper({ items: mockItems, autoClose: false });
337
+ const closeSpy = jest.spyOn(wrapper.vm.$refs.baseDropdown, 'closeAndFocus');
338
+ findListItem(0).trigger('click');
339
+ expect(closeSpy).not.toHaveBeenCalled();
340
+ });
341
+ });
326
342
  });
@@ -40,6 +40,7 @@ const makeBindings = (overrides = {}) =>
40
40
  ':toggle-aria-labelled-by': 'toggleAriaLabelledBy',
41
41
  ':list-aria-labelled-by': 'listAriaLabelledBy',
42
42
  ':fluid-width': 'fluidWidth',
43
+ ':auto-close': 'autoClose',
43
44
  ...overrides,
44
45
  })
45
46
  .map(([key, value]) => `${key}="${value}"`)
@@ -216,7 +217,7 @@ export const CustomGroupsItemsAndToggle = makeGroupedExample({
216
217
  </button>
217
218
  </template>
218
219
  <gl-disclosure-dropdown-group>
219
- <gl-disclosure-dropdown-item>
220
+ <gl-disclosure-dropdown-item @action="closeDropdown">
220
221
  <template #list-item>
221
222
  <span class="gl-display-flex gl-flex-direction-column">
222
223
  <span class="gl-font-weight-bold gl-white-space-nowrap">Orange Fox</span>
@@ -225,7 +226,7 @@ export const CustomGroupsItemsAndToggle = makeGroupedExample({
225
226
  </template>
226
227
  </gl-disclosure-dropdown-item>
227
228
  </gl-disclosure-dropdown-group>
228
- <gl-disclosure-dropdown-group bordered :group="$options.groups[0]">
229
+ <gl-disclosure-dropdown-group bordered :group="$options.groups[0]" @action="closeDropdown">
229
230
  <template #list-item="{ item }">
230
231
  <span class="gl-display-flex gl-align-items-center gl-justify-content-space-between">
231
232
  {{item.text}}
@@ -249,7 +250,7 @@ export const CustomGroupsItemsAndToggle = makeGroupedExample({
249
250
  <template #list-item>Provide feedback</template>
250
251
  </gl-disclosure-dropdown-item>
251
252
  </gl-disclosure-dropdown-group>
252
- <gl-disclosure-dropdown-group bordered :group="$options.groups[1]"/>
253
+ <gl-disclosure-dropdown-group bordered :group="$options.groups[1]" @action="closeDropdown"/>
253
254
  `,
254
255
  {
255
256
  after: `
@@ -266,11 +267,19 @@ export const CustomGroupsItemsAndToggle = makeGroupedExample({
266
267
  };
267
268
  },
268
269
  methods: {
270
+ closeDropdown() {
271
+ this.$refs.disclosure.closeAndFocus();
272
+ },
269
273
  toggleModalVisibility(value) {
270
274
  this.feedBackModalVisible = value;
275
+ this.closeDropdown();
271
276
  },
272
277
  toggleNewNavigation() {
273
278
  this.newNavigation = !this.newNavigation;
279
+ // eslint-disable-next-line no-restricted-globals
280
+ setTimeout(() => {
281
+ this.closeDropdown();
282
+ }, 500);
274
283
  },
275
284
  },
276
285
  groups: mockProfileGroups,
@@ -279,6 +288,7 @@ CustomGroupsItemsAndToggle.args = {
279
288
  icon: 'plus-square',
280
289
  toggleText: 'User profile menu',
281
290
  textSrOnly: true,
291
+ autoClose: false,
282
292
  };
283
293
  CustomGroupsItemsAndToggle.decorators = [makeContainer({ height: '400px' })];
284
294
 
@@ -7,6 +7,8 @@ import {
7
7
  GL_DROPDOWN_SHOWN,
8
8
  GL_DROPDOWN_HIDDEN,
9
9
  GL_DROPDOWN_FOCUS_CONTENT,
10
+ ENTER,
11
+ SPACE,
10
12
  HOME,
11
13
  END,
12
14
  ARROW_DOWN,
@@ -23,6 +25,8 @@ import GlDisclosureDropdownItem, { ITEM_CLASS } from './disclosure_dropdown_item
23
25
  import GlDisclosureDropdownGroup from './disclosure_dropdown_group.vue';
24
26
  import { itemsValidator, isItem, hasOnlyListItems } from './utils';
25
27
 
28
+ export const ITEM_SELECTOR = `.${ITEM_CLASS}`;
29
+
26
30
  export default {
27
31
  name: 'GlDisclosureDropdown',
28
32
  events: {
@@ -183,6 +187,14 @@ export default {
183
187
  required: false,
184
188
  default: false,
185
189
  },
190
+ /**
191
+ * Close the dropdown on item click (action)
192
+ */
193
+ autoClose: {
194
+ type: Boolean,
195
+ required: false,
196
+ default: true,
197
+ },
186
198
  },
187
199
  data() {
188
200
  return {
@@ -241,6 +253,8 @@ export default {
241
253
  this.focusNextItem(event, elements, -1);
242
254
  } else if (code === ARROW_DOWN) {
243
255
  this.focusNextItem(event, elements, 1);
256
+ } else if (code === ENTER || code === SPACE) {
257
+ this.handleAutoClose(event);
244
258
  } else {
245
259
  stop = false;
246
260
  }
@@ -250,7 +264,7 @@ export default {
250
264
  }
251
265
  },
252
266
  getFocusableListItemElements() {
253
- const items = this.$refs.content?.querySelectorAll(`.${ITEM_CLASS}`);
267
+ const items = this.$refs.content?.querySelectorAll(ITEM_SELECTOR);
254
268
  return filterVisible(Array.from(items || []));
255
269
  },
256
270
  focusNextItem(event, elements, offset) {
@@ -276,6 +290,11 @@ export default {
276
290
  */
277
291
  this.$emit('action', action);
278
292
  },
293
+ handleAutoClose(e) {
294
+ if (this.autoClose && e.target.closest(ITEM_SELECTOR)) {
295
+ this.closeAndFocus();
296
+ }
297
+ },
279
298
  uniqueItemId() {
280
299
  return uniqueId(`disclosure-item-`);
281
300
  },
@@ -324,6 +343,7 @@ export default {
324
343
  class="gl-new-dropdown-contents"
325
344
  tabindex="-1"
326
345
  @keydown="onKeydown"
346
+ @click="handleAutoClose"
327
347
  >
328
348
  <slot>
329
349
  <template v-for="(item, index) in items">
@@ -76,11 +76,10 @@ export default {
76
76
  const { code } = event;
77
77
 
78
78
  if (code === ENTER || code === SPACE) {
79
- stopEvent(event);
80
-
81
79
  if (this.isCustomContent) {
82
80
  this.action();
83
81
  } else {
82
+ stopEvent(event);
84
83
  /** Instead of simply navigating or calling the action, we want
85
84
  * the `a/button` to be the target of the event as it might have additional attributes.
86
85
  * E.g. `a` might have `target` attribute.