@gitlab/ui 58.1.0 → 58.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "58.1.0",
3
+ "version": "58.2.1",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -83,12 +83,12 @@
83
83
  },
84
84
  "devDependencies": {
85
85
  "@arkweid/lefthook": "0.7.7",
86
- "@babel/core": "^7.21.0",
86
+ "@babel/core": "^7.21.3",
87
87
  "@babel/preset-env": "^7.20.2",
88
88
  "@gitlab/eslint-plugin": "18.2.0",
89
89
  "@gitlab/fonts": "^1.2.0",
90
90
  "@gitlab/stylelint-config": "4.1.0",
91
- "@gitlab/svgs": "3.24.0",
91
+ "@gitlab/svgs": "3.26.0",
92
92
  "@rollup/plugin-commonjs": "^11.1.0",
93
93
  "@rollup/plugin-node-resolve": "^7.1.3",
94
94
  "@rollup/plugin-replace": "^2.3.2",
@@ -1,6 +1,12 @@
1
1
  import { mount } from '@vue/test-utils';
2
2
  import { nextTick } from 'vue';
3
- import { ENTER, GL_DROPDOWN_HIDDEN, GL_DROPDOWN_SHOWN, POPPER_CONFIG } from '../constants';
3
+ import {
4
+ ARROW_DOWN,
5
+ GL_DROPDOWN_FOCUS_CONTENT,
6
+ GL_DROPDOWN_HIDDEN,
7
+ GL_DROPDOWN_SHOWN,
8
+ POPPER_CONFIG,
9
+ } from '../constants';
4
10
  import GlBaseDropdown from './base_dropdown.vue';
5
11
 
6
12
  const destroyPopper = jest.fn();
@@ -157,42 +163,50 @@ describe('base dropdown', () => {
157
163
  });
158
164
  });
159
165
 
160
- describe('toggle visibility', () => {
166
+ describe('default toggle', () => {
161
167
  beforeEach(() => {
162
168
  buildWrapper();
163
169
  });
164
170
 
165
- it('should toggle menu visibility on toggle button click', async () => {
171
+ it('should open the menu on click but keep focus on toggle', async () => {
166
172
  const toggle = findDefaultDropdownToggle();
167
173
  const menu = findDropdownMenu();
168
174
 
175
+ toggle.element.focus();
169
176
  // open menu clicking toggle btn
170
177
  await toggle.trigger('click');
171
178
  expect(menu.classes('gl-display-block!')).toBe(true);
172
179
  expect(toggle.attributes('aria-expanded')).toBe('true');
173
- await nextTick();
174
- expect(wrapper.emitted(GL_DROPDOWN_SHOWN).length).toBe(1);
180
+ expect(toggle.element).toHaveFocus();
175
181
 
176
- // close menu clicking toggle btn again
182
+ // close menu clicking toggle btn
177
183
  await toggle.trigger('click');
178
184
  expect(menu.classes('gl-display-block!')).toBe(false);
179
185
  expect(toggle.attributes('aria-expanded')).toBeUndefined();
180
- expect(wrapper.emitted(GL_DROPDOWN_HIDDEN).length).toBe(1);
186
+ expect(wrapper.emitted(GL_DROPDOWN_HIDDEN)).toHaveLength(1);
187
+ expect(toggle.element).toHaveFocus();
188
+ });
189
+
190
+ it('should emit `GL_DROPDOWN_FOCUS_CONTENT` event on `ARROW_DOWN`', () => {
191
+ findDefaultDropdownToggle().trigger('keydown', { code: ARROW_DOWN });
192
+ expect(wrapper.emitted(GL_DROPDOWN_FOCUS_CONTENT)).toHaveLength(1);
181
193
  });
182
194
 
183
- it('should close the menu when Escape is pressed inside menu and focus toggle', async () => {
195
+ it('should close menu on Escape and focus toggle', async () => {
184
196
  const toggle = findDefaultDropdownToggle();
185
197
  const menu = findDropdownMenu();
186
198
 
187
199
  // open menu clicking toggle btn
188
200
  await toggle.trigger('click');
189
201
  expect(menu.classes('gl-display-block!')).toBe(true);
202
+ expect(toggle.attributes('aria-expanded')).toBe('true');
190
203
 
191
- // close menu pressing ESC on it
204
+ // close menu clicking toggle btn
205
+ menu.element.focus();
192
206
  await menu.trigger('keydown.esc');
193
207
  expect(menu.classes('gl-display-block!')).toBe(false);
194
208
  expect(toggle.attributes('aria-expanded')).toBeUndefined();
195
- expect(wrapper.emitted(GL_DROPDOWN_HIDDEN).length).toBe(1);
209
+ expect(wrapper.emitted(GL_DROPDOWN_HIDDEN)).toHaveLength(1);
196
210
  expect(toggle.element).toHaveFocus();
197
211
  });
198
212
  });
@@ -221,22 +235,22 @@ describe('base dropdown', () => {
221
235
  });
222
236
 
223
237
  describe('toggle visibility', () => {
224
- it('should toggle menu visibility on toggle button ENTER', async () => {
238
+ it('should toggle menu visibility on toggle click', async () => {
225
239
  const toggle = findCustomDropdownToggle();
226
240
  const firstToggleChild = findFirstToggleElement();
227
241
  const menu = findDropdownMenu();
228
242
  // open menu clicking toggle btn
229
- await toggle.trigger('keydown', { code: ENTER });
243
+ await toggle.trigger('click');
230
244
  expect(menu.classes('gl-display-block!')).toBe(true);
231
245
  expect(firstToggleChild.attributes('aria-expanded')).toBe('true');
232
246
  await nextTick();
233
- expect(wrapper.emitted(GL_DROPDOWN_SHOWN).length).toBe(1);
247
+ expect(wrapper.emitted(GL_DROPDOWN_SHOWN)).toHaveLength(1);
234
248
 
235
249
  // close menu clicking toggle btn again
236
- await toggle.trigger('keydown', { code: ENTER });
250
+ await toggle.trigger('click');
237
251
  expect(menu.classes('gl-display-block!')).toBe(false);
238
252
  expect(firstToggleChild.attributes('aria-expanded')).toBe('false');
239
- expect(wrapper.emitted(GL_DROPDOWN_HIDDEN).length).toBe(1);
253
+ expect(wrapper.emitted(GL_DROPDOWN_HIDDEN)).toHaveLength(1);
240
254
  });
241
255
 
242
256
  it('should close the menu when Escape is pressed inside menu and focus first child in the toggle', async () => {
@@ -251,9 +265,15 @@ describe('base dropdown', () => {
251
265
  await menu.trigger('keydown.esc');
252
266
  expect(menu.classes('gl-display-block!')).toBe(false);
253
267
  expect(firstToggleChild.attributes('aria-expanded')).toBe('false');
254
- expect(wrapper.emitted(GL_DROPDOWN_HIDDEN).length).toBe(1);
268
+ expect(wrapper.emitted(GL_DROPDOWN_HIDDEN)).toHaveLength(1);
255
269
  expect(toggle.find(`[data-testid="${customToggleTestId}"]`).element).toHaveFocus();
256
270
  });
257
271
  });
272
+
273
+ it('should emit `GL_DROPDOWN_FOCUS_CONTENT` event on `ARROW_DOWN`', () => {
274
+ const toggle = findCustomDropdownToggle();
275
+ toggle.trigger('keydown', { code: ARROW_DOWN });
276
+ expect(wrapper.emitted(GL_DROPDOWN_FOCUS_CONTENT)).toHaveLength(1);
277
+ });
258
278
  });
259
279
  });
@@ -7,7 +7,15 @@ import {
7
7
  dropdownPlacements,
8
8
  dropdownVariantOptions,
9
9
  } from '../../../../utils/constants';
10
- import { POPPER_CONFIG, GL_DROPDOWN_SHOWN, GL_DROPDOWN_HIDDEN, ENTER, SPACE } from '../constants';
10
+ import {
11
+ POPPER_CONFIG,
12
+ GL_DROPDOWN_SHOWN,
13
+ GL_DROPDOWN_HIDDEN,
14
+ GL_DROPDOWN_FOCUS_CONTENT,
15
+ ENTER,
16
+ SPACE,
17
+ ARROW_DOWN,
18
+ } from '../constants';
11
19
  import { logWarning, isElementTabbable, isElementFocusable } from '../../../../utils/utils';
12
20
 
13
21
  import GlButton from '../../button/button.vue';
@@ -162,6 +170,7 @@ export default {
162
170
  class: this.toggleButtonClasses,
163
171
  ...this.ariaAttributes,
164
172
  listeners: {
173
+ keydown: (event) => this.onKeydown(event),
165
174
  click: () => this.toggle(),
166
175
  },
167
176
  };
@@ -260,11 +269,28 @@ export default {
260
269
  this.toggleElement.focus();
261
270
  },
262
271
  onKeydown(event) {
263
- const { code } = event;
272
+ const {
273
+ code,
274
+ target: { tagName },
275
+ } = event;
276
+
277
+ let toggleOnEnter = true;
278
+ let toggleOnSpace = true;
279
+
280
+ if (tagName === 'BUTTON') {
281
+ toggleOnEnter = false;
282
+ toggleOnSpace = false;
283
+ } else if (tagName === 'A') {
284
+ toggleOnEnter = false;
285
+ }
264
286
 
265
- if (code === ENTER || code === SPACE) {
287
+ if ((code === ENTER && toggleOnEnter) || (code === SPACE && toggleOnSpace)) {
266
288
  this.toggle();
267
289
  }
290
+
291
+ if (code === ARROW_DOWN) {
292
+ this.$emit(GL_DROPDOWN_FOCUS_CONTENT, event);
293
+ }
268
294
  },
269
295
  },
270
296
  };
@@ -12,6 +12,7 @@ export const POPPER_CONFIG = {
12
12
  // base dropdown events
13
13
  export const GL_DROPDOWN_SHOWN = 'shown';
14
14
  export const GL_DROPDOWN_HIDDEN = 'hidden';
15
+ export const GL_DROPDOWN_FOCUS_CONTENT = 'focusContent';
15
16
 
16
17
  // KEY Codes
17
18
  export const ARROW_DOWN = 'ArrowDown';
@@ -1,3 +1,35 @@
1
+ .btn-group {
2
+ .gl-disclosure-dropdown:not(:last-child) {
3
+ .gl-new-dropdown-toggle {
4
+ @include gl-rounded-top-right-none;
5
+ @include gl-rounded-bottom-right-none;
6
+ }
7
+ }
8
+
9
+ .gl-disclosure-dropdown:not(:first-child) {
10
+ .gl-new-dropdown-toggle {
11
+ @include gl-rounded-top-left-none;
12
+ @include gl-rounded-bottom-left-none;
13
+
14
+ // Prevent double borders when buttons are next to each other
15
+ margin-left: -1px;
16
+ }
17
+ }
18
+
19
+ .gl-disclosure-dropdown:hover {
20
+ .gl-new-dropdown-toggle {
21
+ @include gl-z-index-1;
22
+ }
23
+ }
24
+
25
+ .gl-disclosure-dropdown {
26
+ .gl-new-dropdown-toggle:focus,
27
+ .gl-new-dropdown-toggle:active {
28
+ @include gl-z-index-1;
29
+ }
30
+ }
31
+ }
32
+
1
33
  .gl-disclosure-dropdown {
2
34
  &.gl-new-dropdown {
3
35
  .gl-new-dropdown-inner {
@@ -4,6 +4,7 @@ import GlBaseDropdown from '../base_dropdown/base_dropdown.vue';
4
4
  import {
5
5
  GL_DROPDOWN_SHOWN,
6
6
  GL_DROPDOWN_HIDDEN,
7
+ GL_DROPDOWN_FOCUS_CONTENT,
7
8
  ARROW_DOWN,
8
9
  ARROW_UP,
9
10
  HOME,
@@ -82,10 +83,6 @@ describe('GlDisclosureDropdown', () => {
82
83
  it('should re-emit the event', () => {
83
84
  expect(wrapper.emitted(GL_DROPDOWN_SHOWN)).toHaveLength(1);
84
85
  });
85
-
86
- it('should focus the first item', () => {
87
- expect(findDisclosureItems().at(0).find(ITEM_SELECTOR).element).toHaveFocus();
88
- });
89
86
  });
90
87
 
91
88
  describe('onHide', () => {
@@ -112,7 +109,11 @@ describe('GlDisclosureDropdown', () => {
112
109
  thirdItem = findListItem(2);
113
110
  });
114
111
 
115
- it('should move the focus down the list of items on `ARROW_DOWN` and stop on the last item', async () => {
112
+ it('should move the focus from toggle and down the list of items on `ARROW_DOWN` and stop on the last item', async () => {
113
+ findBaseDropdown().vm.$emit(
114
+ GL_DROPDOWN_FOCUS_CONTENT,
115
+ new KeyboardEvent('keydown', { code: ARROW_DOWN })
116
+ );
116
117
  expect(firstItem.element).toHaveFocus();
117
118
  await firstItem.trigger('keydown', { code: ARROW_DOWN });
118
119
  expect(secondItem.element).toHaveFocus();
@@ -135,6 +136,10 @@ describe('GlDisclosureDropdown', () => {
135
136
  });
136
137
 
137
138
  it('should move focus to the last item on `END` keydown', async () => {
139
+ findBaseDropdown().vm.$emit(
140
+ GL_DROPDOWN_FOCUS_CONTENT,
141
+ new KeyboardEvent('keydown', { code: ARROW_DOWN })
142
+ );
138
143
  expect(firstItem.element).toHaveFocus();
139
144
  await firstItem.trigger('keydown', { code: END });
140
145
  expect(thirdItem.element).toHaveFocus();
@@ -158,6 +163,10 @@ describe('GlDisclosureDropdown', () => {
158
163
  });
159
164
 
160
165
  it('should skip over it', async () => {
166
+ findBaseDropdown().vm.$emit(
167
+ GL_DROPDOWN_FOCUS_CONTENT,
168
+ new KeyboardEvent('keydown', { code: ARROW_DOWN })
169
+ );
161
170
  expect(firstItem.element).toHaveFocus();
162
171
  await firstItem.trigger('keydown', { code: ARROW_DOWN });
163
172
 
@@ -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_FOCUS_CONTENT,
9
10
  HOME,
10
11
  END,
11
12
  ARROW_DOWN,
@@ -26,6 +27,7 @@ export default {
26
27
  events: {
27
28
  GL_DROPDOWN_SHOWN,
28
29
  GL_DROPDOWN_HIDDEN,
30
+ GL_DROPDOWN_FOCUS_CONTENT,
29
31
  },
30
32
  components: {
31
33
  GlBaseDropdown,
@@ -188,12 +190,6 @@ export default {
188
190
  this.$refs.baseDropdown.close();
189
191
  },
190
192
  onShow() {
191
- const items = this.getFocusableListItemElements();
192
-
193
- if (items.length) {
194
- this.focusItem(0, items);
195
- }
196
-
197
193
  /**
198
194
  * Emitted when dropdown is shown
199
195
  *
@@ -285,6 +281,7 @@ export default {
285
281
  class="gl-disclosure-dropdown"
286
282
  @[$options.events.GL_DROPDOWN_SHOWN]="onShow"
287
283
  @[$options.events.GL_DROPDOWN_HIDDEN]="onHide"
284
+ @[$options.events.GL_DROPDOWN_FOCUS_CONTENT]="onKeydown"
288
285
  >
289
286
  <template v-if="hasCustomToggle" #toggle>
290
287
  <!-- @slot Custom toggle content -->