@gitlab/ui 72.8.0 → 72.8.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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [72.8.1](https://gitlab.com/gitlab-org/gitlab-ui/compare/v72.8.0...v72.8.1) (2024-01-18)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **dropdowns:** do not steal focus back from the consumer ([69295fa](https://gitlab.com/gitlab-org/gitlab-ui/commit/69295faa28dfe48b90e75ab86c9d3ba615760c96))
7
+
1
8
  # [72.8.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v72.7.0...v72.8.0) (2024-01-16)
2
9
 
3
10
 
@@ -44,7 +44,7 @@ var script = {
44
44
  const __vue_script__ = script;
45
45
 
46
46
  /* template */
47
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{class:_vm.rootClass,attrs:{"data-qa-selector":("" + (_vm.$options.qaPrefix) + (_vm.option.value))}},[_c('gl-form-checkbox',{class:_vm.checkboxClass,attrs:{"checked":_vm.node.isChecked,"indeterminate":_vm.node.isIndeterminate},on:{"change":function($event){return _vm.tree.toggleOption(_vm.option, $event)}}},[_vm._v("\n "+_vm._s(_vm.label)+"\n ")]),_vm._v(" "),_vm._l((_vm.option.children),function(child){return _c('gl-form-checkbox-tree-node',{key:child.value,attrs:{"option":child,"is-nested":""}})})],2)};
47
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{class:_vm.rootClass,attrs:{"data-testid":("" + (_vm.$options.qaPrefix) + (_vm.option.value))}},[_c('gl-form-checkbox',{class:_vm.checkboxClass,attrs:{"checked":_vm.node.isChecked,"indeterminate":_vm.node.isIndeterminate},on:{"change":function($event){return _vm.tree.toggleOption(_vm.option, $event)}}},[_vm._v("\n "+_vm._s(_vm.label)+"\n ")]),_vm._v(" "),_vm._l((_vm.option.children),function(child){return _c('gl-form-checkbox-tree-node',{key:child.value,attrs:{"option":child,"is-nested":""}})})],2)};
48
48
  var __vue_staticRenderFns__ = [];
49
49
 
50
50
  /* style */
@@ -359,11 +359,20 @@ var script = {
359
359
  }
360
360
  this.toggle(event);
361
361
  },
362
+ /**
363
+ * Closes the dropdown and returns the focus to the toggle unless it has has moved outside
364
+ * of the dropdown, meaning that the consumer needed to put some other element in focus.
365
+ *
366
+ * @param {KeyboardEvent?} event The keyboard event that caused the dropdown to close.
367
+ */
362
368
  async closeAndFocus(event) {
363
369
  if (!this.visible) {
364
370
  return;
365
371
  }
366
372
  const hasToggled = await this.toggle(event);
373
+ if (!this.$el.contains(document.activeElement)) {
374
+ return;
375
+ }
367
376
  if (hasToggled) {
368
377
  this.focusToggle();
369
378
  }
@@ -91,7 +91,7 @@ var script = {
91
91
  default: null
92
92
  },
93
93
  /**
94
- * HTML attributes to add to the text input. Helpful for adding `data-testid` and `data-qa-selector` attributes
94
+ * HTML attributes to add to the text input. Helpful for adding `data-testid` attributes
95
95
  */
96
96
  textInputAttrs: {
97
97
  type: Object,
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Mon, 15 Jan 2024 23:51:36 GMT
3
+ * Generated on Thu, 18 Jan 2024 06:11:39 GMT
4
4
  */
5
5
 
6
6
  :root {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Mon, 15 Jan 2024 23:51:36 GMT
3
+ * Generated on Thu, 18 Jan 2024 06:11:39 GMT
4
4
  */
5
5
 
6
6
  :root.gl-dark {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Mon, 15 Jan 2024 23:51:36 GMT
3
+ * Generated on Thu, 18 Jan 2024 06:11:39 GMT
4
4
  */
5
5
 
6
6
  export const DATA_VIZ_GREEN_50 = "#133a03";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly
3
- * Generated on Mon, 15 Jan 2024 23:51:36 GMT
3
+ * Generated on Thu, 18 Jan 2024 06:11:39 GMT
4
4
  */
5
5
 
6
6
  export const DATA_VIZ_GREEN_50 = "#ddfab7";
@@ -1,6 +1,6 @@
1
1
 
2
2
  // Do not edit directly
3
- // Generated on Mon, 15 Jan 2024 23:51:36 GMT
3
+ // Generated on Thu, 18 Jan 2024 06:11:39 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 Mon, 15 Jan 2024 23:51:36 GMT
3
+ // Generated on Thu, 18 Jan 2024 06:11:39 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": "72.8.0",
3
+ "version": "72.8.1",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -476,7 +476,7 @@ describe('Filtered search', () => {
476
476
  });
477
477
 
478
478
  it('passes `searchButtonAttributes` prop to `GlSearchBoxByClick`', () => {
479
- const searchButtonAttributes = { 'data-qa-selector': 'foo-bar' };
479
+ const searchButtonAttributes = { 'data-prop': 'foo-bar' };
480
480
 
481
481
  createComponent({ searchButtonAttributes });
482
482
 
@@ -484,7 +484,7 @@ describe('Filtered search', () => {
484
484
  });
485
485
 
486
486
  it('passes `searchInputAttributes` prop to search term', async () => {
487
- const searchInputAttributes = { 'data-qa-selector': 'foo-bar' };
487
+ const searchInputAttributes = { 'data-prop': 'foo-bar' };
488
488
 
489
489
  createComponent({
490
490
  value: ['one'],
@@ -15,7 +15,7 @@ const pointerClass = 'gl-cursor-pointer';
15
15
  describe('Filtered search term', () => {
16
16
  let wrapper;
17
17
 
18
- const searchInputAttributes = { 'data-qa-selector': 'foo-bar' };
18
+ const searchInputAttributes = { 'data-testid': 'foo-bar', 'data-prop': 'foo-property' };
19
19
 
20
20
  const defaultProps = {
21
21
  availableTokens: [],
@@ -182,9 +182,7 @@ describe('Filtered search term', () => {
182
182
  searchInputAttributes,
183
183
  });
184
184
 
185
- expect(findSearchInput().attributes('data-qa-selector')).toBe(
186
- searchInputAttributes['data-qa-selector']
187
- );
185
+ expect(findSearchInput().attributes('data-prop')).toBe(searchInputAttributes['data-prop']);
188
186
  });
189
187
 
190
188
  it('activates and deactivates when the input is focused/blurred', async () => {
@@ -10,7 +10,7 @@ const OPTIONS = [
10
10
  describe('Filtered search token segment', () => {
11
11
  let wrapper;
12
12
 
13
- const searchInputAttributes = { 'data-qa-selector': 'foo-bar' };
13
+ const searchInputAttributes = { 'data-prop': 'foo-bar' };
14
14
 
15
15
  beforeAll(() => {
16
16
  if (!HTMLElement.prototype.scrollIntoView) {
@@ -336,7 +336,7 @@ describe('Filtered search token segment', () => {
336
336
  });
337
337
 
338
338
  it('does not add `searchInputAttributes` prop to search token segment', () => {
339
- expect(wrapper.attributes('data-qa-selector')).toBe(undefined);
339
+ expect(wrapper.attributes('data-prop')).toBe(undefined);
340
340
  });
341
341
  }
342
342
  );
@@ -353,9 +353,7 @@ describe('Filtered search token segment', () => {
353
353
  });
354
354
 
355
355
  it('adds `searchInputAttributes` prop to search token segment', () => {
356
- expect(wrapper.attributes('data-qa-selector')).toBe(
357
- searchInputAttributes['data-qa-selector']
358
- );
356
+ expect(wrapper.attributes('data-prop')).toBe(searchInputAttributes['data-prop']);
359
357
  });
360
358
  });
361
359
 
@@ -363,8 +361,8 @@ describe('Filtered search token segment', () => {
363
361
  it('adds `searchInputAttributes` prop to search token segment input', () => {
364
362
  createComponent({ active: true, value: 'something', searchInputAttributes });
365
363
 
366
- expect(wrapper.find('input').attributes('data-qa-selector')).toBe(
367
- searchInputAttributes['data-qa-selector']
364
+ expect(wrapper.find('input').attributes('data-prop')).toBe(
365
+ searchInputAttributes['data-prop']
368
366
  );
369
367
  });
370
368
 
@@ -39,7 +39,7 @@ export default {
39
39
  </script>
40
40
 
41
41
  <template>
42
- <div :class="rootClass" :data-qa-selector="`${$options.qaPrefix}${option.value}`">
42
+ <div :class="rootClass" :data-testid="`${$options.qaPrefix}${option.value}`">
43
43
  <gl-form-checkbox
44
44
  :checked="node.isChecked"
45
45
  :indeterminate="node.isIndeterminate"
@@ -87,7 +87,7 @@ describe('GlFormCheckboxTree', () => {
87
87
  const findCheckboxes = (el = wrapper) => el.findAllComponents(GlFormCheckbox);
88
88
  const countIndeterminate = () => wrapper.findAll('.js-is-indeterminate').length || 0;
89
89
  const countChecked = () => wrapper.findAll('.js-is-checked').length || 0;
90
- const findCheckboxByValue = (value) => wrapper.find(`[data-qa-selector="${QA_PREFIX}${value}"]`);
90
+ const findCheckboxByValue = (value) => wrapper.find(`[data-testid="${QA_PREFIX}${value}"]`);
91
91
  const getCheckboxesCount = (el) => findCheckboxes(el).length;
92
92
  const findCheckboxInput = (checkbox) => checkbox.find('input[type="checkbox"]');
93
93
  const expectCheckboxUnchecked = (checkbox) => {
@@ -152,7 +152,7 @@ describe('GlFormCheckboxTree', () => {
152
152
 
153
153
  beforeEach(async () => {
154
154
  createWrapper({ options: getOptions(shape) });
155
- checkbox = wrapper.find(`[data-qa-selector="${QA_PREFIX}${boxToCheck}"]`);
155
+ checkbox = wrapper.find(`[data-testid="${QA_PREFIX}${boxToCheck}"]`);
156
156
  await checkbox.find('input').setChecked();
157
157
  });
158
158
 
@@ -36,7 +36,7 @@ describe('base dropdown', () => {
36
36
  ...propsData,
37
37
  },
38
38
  slots: {
39
- default: `<div class="${GL_DROPDOWN_CONTENTS_CLASS}" />`,
39
+ default: `<div class="${GL_DROPDOWN_CONTENTS_CLASS}"><button /></div>`,
40
40
  ...slots,
41
41
  },
42
42
  attachTo: document.body,
@@ -57,6 +57,8 @@ describe('base dropdown', () => {
57
57
  const findDropdownToggleText = () => findDefaultDropdownToggle().find('.gl-button-text');
58
58
  const findDropdownMenu = () => wrapper.find('.gl-new-dropdown-panel');
59
59
 
60
+ const moveFocusWithinDropdown = () => findDropdownMenu().find('button').element.focus();
61
+
60
62
  describe('Floating UI instance', () => {
61
63
  it("starts Floating UI's when opening the dropdown", async () => {
62
64
  buildWrapper();
@@ -324,14 +326,47 @@ describe('base dropdown', () => {
324
326
  expect(menu.classes('gl-display-block!')).toBe(true);
325
327
  expect(toggle.attributes('aria-expanded')).toBe('true');
326
328
 
327
- // close menu clicking toggle btn
328
- menu.element.focus();
329
+ moveFocusWithinDropdown();
330
+
331
+ // close menu by pressing ESC key
329
332
  await menu.trigger('keydown.esc');
333
+
330
334
  expect(menu.classes('gl-display-block!')).toBe(false);
331
335
  expect(toggle.attributes('aria-expanded')).toBeUndefined();
332
336
  expect(wrapper.emitted(GL_DROPDOWN_HIDDEN)).toHaveLength(1);
333
337
  expect(toggle.element).toHaveFocus();
334
338
  });
339
+
340
+ describe('when the consumer takes over the focus', () => {
341
+ let consumerButton;
342
+
343
+ beforeEach(() => {
344
+ consumerButton = document.createElement('button');
345
+ document.body.appendChild(consumerButton);
346
+ });
347
+
348
+ afterEach(() => {
349
+ consumerButton.remove();
350
+ });
351
+
352
+ it('does not steal the focus back from the consumer when closing the dropdown', async () => {
353
+ const toggle = findDefaultDropdownToggle();
354
+ const menu = findDropdownMenu();
355
+
356
+ // open menu clicking toggle btn
357
+ await toggle.trigger('click');
358
+
359
+ moveFocusWithinDropdown();
360
+
361
+ // consumer focuses some element
362
+ consumerButton.focus();
363
+
364
+ // close menu by pressing ESC key
365
+ await menu.trigger('keydown.esc');
366
+
367
+ expect(consumerButton).toHaveFocus();
368
+ });
369
+ });
335
370
  });
336
371
 
337
372
  describe('beforeClose event', () => {
@@ -353,7 +388,7 @@ describe('base dropdown', () => {
353
388
 
354
389
  await toggle.trigger('click');
355
390
 
356
- menu.element.focus();
391
+ moveFocusWithinDropdown();
357
392
  await menu.trigger('keydown.esc');
358
393
  expect(menu.classes('gl-display-block!')).toBe(true);
359
394
  expect(toggle.attributes('aria-expanded')).toBeDefined();
@@ -365,6 +400,7 @@ describe('base dropdown', () => {
365
400
  const toggle = findDefaultDropdownToggle();
366
401
  const menu = findDropdownMenu();
367
402
  await toggle.trigger('click');
403
+ moveFocusWithinDropdown();
368
404
  await menu.trigger('keydown.esc');
369
405
  expect(event.type).toBe('keydown');
370
406
  });
@@ -444,6 +480,7 @@ describe('base dropdown', () => {
444
480
  expect(menu.classes('gl-display-block!')).toBe(true);
445
481
 
446
482
  // close menu pressing ESC on it
483
+ moveFocusWithinDropdown();
447
484
  await menu.trigger('keydown.esc');
448
485
  expect(menu.classes('gl-display-block!')).toBe(false);
449
486
  expect(firstToggleChild.attributes('aria-expanded')).toBe('false');
@@ -382,11 +382,23 @@ export default {
382
382
  }
383
383
  this.toggle(event);
384
384
  },
385
+ /**
386
+ * Closes the dropdown and returns the focus to the toggle unless it has has moved outside
387
+ * of the dropdown, meaning that the consumer needed to put some other element in focus.
388
+ *
389
+ * @param {KeyboardEvent?} event The keyboard event that caused the dropdown to close.
390
+ */
385
391
  async closeAndFocus(event) {
386
392
  if (!this.visible) {
387
393
  return;
388
394
  }
395
+
389
396
  const hasToggled = await this.toggle(event);
397
+
398
+ if (!this.$el.contains(document.activeElement)) {
399
+ return;
400
+ }
401
+
390
402
  if (hasToggled) {
391
403
  this.focusToggle();
392
404
  }
@@ -162,12 +162,10 @@ describe('search box by click component', () => {
162
162
  });
163
163
 
164
164
  it('adds `searchButtonAttributes` prop to search button', () => {
165
- const searchButtonAttributes = { 'data-qa-selector': 'foo-bar' };
165
+ const searchButtonAttributes = { 'data-prop': 'foo-bar' };
166
166
 
167
167
  createComponent({ searchButtonAttributes });
168
168
 
169
- expect(findSearchButton().attributes('data-qa-selector')).toBe(
170
- searchButtonAttributes['data-qa-selector']
171
- );
169
+ expect(findSearchButton().attributes('data-prop')).toBe(searchButtonAttributes['data-prop']);
172
170
  });
173
171
  });
@@ -254,12 +254,12 @@ describe('GlTokenSelector', () => {
254
254
  createComponent({
255
255
  propsData: {
256
256
  textInputAttrs: {
257
- 'data-qa-selector': 'foo_bar',
257
+ 'data-prop': 'foo_bar',
258
258
  },
259
259
  },
260
260
  });
261
261
 
262
- expect(findTextInput().attributes('data-qa-selector')).toBe('foo_bar');
262
+ expect(findTextInput().attributes('data-prop')).toBe('foo_bar');
263
263
  });
264
264
  });
265
265
 
@@ -91,7 +91,7 @@ export default {
91
91
  default: null,
92
92
  },
93
93
  /**
94
- * HTML attributes to add to the text input. Helpful for adding `data-testid` and `data-qa-selector` attributes
94
+ * HTML attributes to add to the text input. Helpful for adding `data-testid` attributes
95
95
  */
96
96
  textInputAttrs: {
97
97
  type: Object,