@gitlab/ui 64.1.0 → 64.1.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 +7 -0
- package/dist/components/base/new_dropdowns/base_dropdown/base_dropdown.js +26 -20
- package/dist/components/base/new_dropdowns/listbox/mock_data.js +30 -1
- package/package.json +1 -1
- package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.spec.js +7 -2
- package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.vue +29 -21
- package/src/components/base/new_dropdowns/listbox/listbox.spec.js +4 -5
- package/src/components/base/new_dropdowns/listbox/listbox.stories.js +28 -1
- package/src/components/base/new_dropdowns/listbox/mock_data.js +43 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [64.1.1](https://gitlab.com/gitlab-org/gitlab-ui/compare/v64.1.0...v64.1.1) (2023-06-01)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **dropdowns:** auto-focus only once dropdown is properly positioned ([fe6c39d](https://gitlab.com/gitlab-org/gitlab-ui/commit/fe6c39dee065cbbc35dd225ea0e565dba368af14))
|
|
7
|
+
|
|
1
8
|
# [64.1.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v64.0.2...v64.1.0) (2023-05-30)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -138,7 +138,6 @@ var script = {
|
|
|
138
138
|
data() {
|
|
139
139
|
return {
|
|
140
140
|
visible: false,
|
|
141
|
-
openedYet: false,
|
|
142
141
|
baseDropdownId: uniqueId('base-dropdown-')
|
|
143
142
|
};
|
|
144
143
|
},
|
|
@@ -264,7 +263,7 @@ var script = {
|
|
|
264
263
|
Use 'a' or 'button' element instead or make sure to add 'role="button"' along with 'tabindex' otherwise.`, this.$el);
|
|
265
264
|
}
|
|
266
265
|
},
|
|
267
|
-
startFloating() {
|
|
266
|
+
async startFloating() {
|
|
268
267
|
this.calculateNonScrollableAreaHeight();
|
|
269
268
|
this.observer = new MutationObserver(this.calculateNonScrollableAreaHeight);
|
|
270
269
|
this.observer.observe(this.$refs.content, {
|
|
@@ -272,22 +271,26 @@ var script = {
|
|
|
272
271
|
childList: true,
|
|
273
272
|
subtree: true
|
|
274
273
|
});
|
|
275
|
-
|
|
276
|
-
const {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
274
|
+
await new Promise(resolve => {
|
|
275
|
+
const stopAutoUpdate = autoUpdate(this.toggleElement, this.$refs.content, async () => {
|
|
276
|
+
const {
|
|
277
|
+
x,
|
|
278
|
+
y
|
|
279
|
+
} = await computePosition(this.toggleElement, this.$refs.content, this.floatingUIConfig);
|
|
280
280
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
281
|
+
/**
|
|
282
|
+
* Due to the asynchronous nature of computePosition, it's technically possible for the
|
|
283
|
+
* component to have been destroyed by the time the promise resolves. In such case, we exit
|
|
284
|
+
* early to prevent a TypeError.
|
|
285
|
+
*/
|
|
286
|
+
if (!this.$refs.content) return;
|
|
287
|
+
Object.assign(this.$refs.content.style, {
|
|
288
|
+
left: `${x}px`,
|
|
289
|
+
top: `${y}px`
|
|
290
|
+
});
|
|
291
|
+
resolve(stopAutoUpdate);
|
|
290
292
|
});
|
|
293
|
+
this.stopAutoUpdate = stopAutoUpdate;
|
|
291
294
|
});
|
|
292
295
|
},
|
|
293
296
|
stopFloating() {
|
|
@@ -298,12 +301,15 @@ var script = {
|
|
|
298
301
|
async toggle() {
|
|
299
302
|
this.visible = !this.visible;
|
|
300
303
|
if (this.visible) {
|
|
304
|
+
// The dropdown needs to be actually visible before we compute its position with Floating UI.
|
|
305
|
+
await this.$nextTick();
|
|
306
|
+
|
|
301
307
|
/**
|
|
302
|
-
* We
|
|
303
|
-
*
|
|
308
|
+
* We wait until the dropdown's position has been computed before emitting the `shown` event.
|
|
309
|
+
* This ensures that, if the parent component attempts to focus an inner element, the dropdown
|
|
310
|
+
* is already properly placed in the page. Otherwise, the page would scroll back to the top.
|
|
304
311
|
*/
|
|
305
|
-
await this
|
|
306
|
-
this.startFloating();
|
|
312
|
+
await this.startFloating();
|
|
307
313
|
this.$emit(GL_DROPDOWN_SHOWN);
|
|
308
314
|
} else {
|
|
309
315
|
this.stopFloating();
|
|
@@ -57,6 +57,35 @@ const mockGroups = [{
|
|
|
57
57
|
value: 'v2.1'
|
|
58
58
|
}]
|
|
59
59
|
}];
|
|
60
|
+
const mockGroupsWithTextSrOnly = [{
|
|
61
|
+
text: 'Default',
|
|
62
|
+
options: [{
|
|
63
|
+
text: 'main',
|
|
64
|
+
value: 'main'
|
|
65
|
+
}, {
|
|
66
|
+
text: 'development',
|
|
67
|
+
value: 'development'
|
|
68
|
+
}],
|
|
69
|
+
textSrOnly: true
|
|
70
|
+
}, {
|
|
71
|
+
text: 'Feature branches',
|
|
72
|
+
options: [{
|
|
73
|
+
text: 'feature/add-avatar',
|
|
74
|
+
value: 'add'
|
|
75
|
+
}, {
|
|
76
|
+
text: 'feature/improve-panel',
|
|
77
|
+
value: 'improve'
|
|
78
|
+
}]
|
|
79
|
+
}, {
|
|
80
|
+
text: 'Bugfix branches',
|
|
81
|
+
options: [{
|
|
82
|
+
text: 'fix/border-of-avatar',
|
|
83
|
+
value: 'fix-border'
|
|
84
|
+
}, {
|
|
85
|
+
text: 'fix/radius-panel',
|
|
86
|
+
value: 'fix-radius'
|
|
87
|
+
}]
|
|
88
|
+
}];
|
|
60
89
|
const mockUsers = [{
|
|
61
90
|
value: 'mikegreiling',
|
|
62
91
|
text: 'Mike Greiling',
|
|
@@ -74,4 +103,4 @@ const mockUsers = [{
|
|
|
74
103
|
icon: 'bin'
|
|
75
104
|
}];
|
|
76
105
|
|
|
77
|
-
export { mockGroups, mockOptions, mockUsers };
|
|
106
|
+
export { mockGroups, mockGroupsWithTextSrOnly, mockOptions, mockUsers };
|
package/package.json
CHANGED
|
@@ -62,7 +62,7 @@ describe('base dropdown', () => {
|
|
|
62
62
|
expect(autoUpdate).toHaveBeenCalledTimes(1);
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
-
it(
|
|
65
|
+
it('stops Floating UI when closing the dropdown', async () => {
|
|
66
66
|
buildWrapper();
|
|
67
67
|
await findDefaultDropdownToggle().trigger('click');
|
|
68
68
|
await findDefaultDropdownToggle().trigger('click');
|
|
@@ -71,7 +71,7 @@ describe('base dropdown', () => {
|
|
|
71
71
|
expect(mockStopAutoUpdate).toHaveBeenCalledTimes(1);
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
it(
|
|
74
|
+
it('restarts Floating UI when reopening the dropdown', async () => {
|
|
75
75
|
buildWrapper();
|
|
76
76
|
await findDefaultDropdownToggle().trigger('click');
|
|
77
77
|
await findDefaultDropdownToggle().trigger('click');
|
|
@@ -335,6 +335,11 @@ describe('base dropdown', () => {
|
|
|
335
335
|
});
|
|
336
336
|
|
|
337
337
|
describe('toggle visibility', () => {
|
|
338
|
+
beforeEach(() => {
|
|
339
|
+
autoUpdate.mockImplementation(jest.requireActual('@floating-ui/dom').autoUpdate);
|
|
340
|
+
computePosition.mockImplementation(() => Promise.resolve);
|
|
341
|
+
});
|
|
342
|
+
|
|
338
343
|
it('should toggle menu visibility on toggle click', async () => {
|
|
339
344
|
const toggle = findCustomDropdownToggle();
|
|
340
345
|
const firstToggleChild = findFirstToggleElement();
|
|
@@ -150,7 +150,6 @@ export default {
|
|
|
150
150
|
data() {
|
|
151
151
|
return {
|
|
152
152
|
visible: false,
|
|
153
|
-
openedYet: false,
|
|
154
153
|
baseDropdownId: uniqueId('base-dropdown-'),
|
|
155
154
|
};
|
|
156
155
|
},
|
|
@@ -282,7 +281,7 @@ export default {
|
|
|
282
281
|
);
|
|
283
282
|
}
|
|
284
283
|
},
|
|
285
|
-
startFloating() {
|
|
284
|
+
async startFloating() {
|
|
286
285
|
this.calculateNonScrollableAreaHeight();
|
|
287
286
|
this.observer = new MutationObserver(this.calculateNonScrollableAreaHeight);
|
|
288
287
|
this.observer.observe(this.$refs.content, {
|
|
@@ -291,24 +290,29 @@ export default {
|
|
|
291
290
|
subtree: true,
|
|
292
291
|
});
|
|
293
292
|
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
293
|
+
await new Promise((resolve) => {
|
|
294
|
+
const stopAutoUpdate = autoUpdate(this.toggleElement, this.$refs.content, async () => {
|
|
295
|
+
const { x, y } = await computePosition(
|
|
296
|
+
this.toggleElement,
|
|
297
|
+
this.$refs.content,
|
|
298
|
+
this.floatingUIConfig
|
|
299
|
+
);
|
|
300
300
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
301
|
+
/**
|
|
302
|
+
* Due to the asynchronous nature of computePosition, it's technically possible for the
|
|
303
|
+
* component to have been destroyed by the time the promise resolves. In such case, we exit
|
|
304
|
+
* early to prevent a TypeError.
|
|
305
|
+
*/
|
|
306
|
+
if (!this.$refs.content) return;
|
|
307
|
+
|
|
308
|
+
Object.assign(this.$refs.content.style, {
|
|
309
|
+
left: `${x}px`,
|
|
310
|
+
top: `${y}px`,
|
|
311
|
+
});
|
|
307
312
|
|
|
308
|
-
|
|
309
|
-
left: `${x}px`,
|
|
310
|
-
top: `${y}px`,
|
|
313
|
+
resolve(stopAutoUpdate);
|
|
311
314
|
});
|
|
315
|
+
this.stopAutoUpdate = stopAutoUpdate;
|
|
312
316
|
});
|
|
313
317
|
},
|
|
314
318
|
stopFloating() {
|
|
@@ -319,12 +323,16 @@ export default {
|
|
|
319
323
|
this.visible = !this.visible;
|
|
320
324
|
|
|
321
325
|
if (this.visible) {
|
|
326
|
+
// The dropdown needs to be actually visible before we compute its position with Floating UI.
|
|
327
|
+
await this.$nextTick();
|
|
328
|
+
|
|
322
329
|
/**
|
|
323
|
-
* We
|
|
324
|
-
*
|
|
330
|
+
* We wait until the dropdown's position has been computed before emitting the `shown` event.
|
|
331
|
+
* This ensures that, if the parent component attempts to focus an inner element, the dropdown
|
|
332
|
+
* is already properly placed in the page. Otherwise, the page would scroll back to the top.
|
|
325
333
|
*/
|
|
326
|
-
await this
|
|
327
|
-
|
|
334
|
+
await this.startFloating();
|
|
335
|
+
|
|
328
336
|
this.$emit(GL_DROPDOWN_SHOWN);
|
|
329
337
|
} else {
|
|
330
338
|
this.stopFloating();
|
|
@@ -18,7 +18,7 @@ import GlIntersectionObserver from '../../../utilities/intersection_observer/int
|
|
|
18
18
|
import GlCollapsibleListbox, { ITEM_SELECTOR } from './listbox.vue';
|
|
19
19
|
import GlListboxItem from './listbox_item.vue';
|
|
20
20
|
import GlListboxGroup from './listbox_group.vue';
|
|
21
|
-
import { mockOptions, mockGroups } from './mock_data';
|
|
21
|
+
import { mockOptions, mockGroups, mockGroupsWithTextSrOnly } from './mock_data';
|
|
22
22
|
|
|
23
23
|
jest.mock('@floating-ui/dom');
|
|
24
24
|
autoUpdate.mockImplementation(() => {
|
|
@@ -398,14 +398,13 @@ describe('GlCollapsibleListbox', () => {
|
|
|
398
398
|
});
|
|
399
399
|
|
|
400
400
|
it('passes the `textSrOnly` prop', () => {
|
|
401
|
-
const mockGroupsWithTextSrOnly = JSON.parse(JSON.stringify(mockGroups));
|
|
402
|
-
mockGroupsWithTextSrOnly[0].textSrOnly = true;
|
|
403
|
-
mockGroupsWithTextSrOnly[1].textSrOnly = false;
|
|
404
401
|
buildWrapper({ items: mockGroupsWithTextSrOnly });
|
|
405
402
|
|
|
406
403
|
const groups = findListboxGroups();
|
|
407
404
|
|
|
408
|
-
const expectedTextSrOnlyProps = mockGroupsWithTextSrOnly.map(
|
|
405
|
+
const expectedTextSrOnlyProps = mockGroupsWithTextSrOnly.map(
|
|
406
|
+
(group) => group.textSrOnly ?? false
|
|
407
|
+
);
|
|
409
408
|
const actualTextSrOnlyProps = groups.wrappers.map((group) => group.props('textSrOnly'));
|
|
410
409
|
|
|
411
410
|
expect(actualTextSrOnlyProps).toEqual(expectedTextSrOnlyProps);
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
} from '../../../../utils/stories_constants';
|
|
25
25
|
import { POSITION } from '../../../utilities/truncate/constants';
|
|
26
26
|
import readme from './listbox.md';
|
|
27
|
-
import { mockOptions, mockGroups, mockUsers } from './mock_data';
|
|
27
|
+
import { mockOptions, mockGroups, mockGroupsWithTextSrOnly, mockUsers } from './mock_data';
|
|
28
28
|
import { flattenedOptions } from './utils';
|
|
29
29
|
import GlCollapsibleListbox from './listbox.vue';
|
|
30
30
|
|
|
@@ -462,6 +462,33 @@ export const CustomGroupsAndItems = makeGroupedExample({
|
|
|
462
462
|
`),
|
|
463
463
|
});
|
|
464
464
|
|
|
465
|
+
export const GroupWithoutLabel = (args, { argTypes }) => ({
|
|
466
|
+
props: Object.keys(argTypes),
|
|
467
|
+
components: {
|
|
468
|
+
GlBadge,
|
|
469
|
+
GlCollapsibleListbox,
|
|
470
|
+
},
|
|
471
|
+
data() {
|
|
472
|
+
return {
|
|
473
|
+
selected: mockGroupsWithTextSrOnly[1].options[1].value,
|
|
474
|
+
};
|
|
475
|
+
},
|
|
476
|
+
mounted() {
|
|
477
|
+
if (this.startOpened) {
|
|
478
|
+
openListbox(this);
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
template: template(`
|
|
482
|
+
<template #list-item="{ item }">
|
|
483
|
+
{{ item.text }} <gl-badge v-if="item.value === 'main'" size="sm">default</gl-badge>
|
|
484
|
+
</template>
|
|
485
|
+
`),
|
|
486
|
+
});
|
|
487
|
+
GroupWithoutLabel.args = generateProps({
|
|
488
|
+
items: mockGroupsWithTextSrOnly,
|
|
489
|
+
headerText: 'Select branch',
|
|
490
|
+
});
|
|
491
|
+
|
|
465
492
|
export default {
|
|
466
493
|
title: 'base/new-dropdowns/listbox',
|
|
467
494
|
component: GlCollapsibleListbox,
|
|
@@ -67,6 +67,49 @@ export const mockGroups = [
|
|
|
67
67
|
},
|
|
68
68
|
];
|
|
69
69
|
|
|
70
|
+
export const mockGroupsWithTextSrOnly = [
|
|
71
|
+
{
|
|
72
|
+
text: 'Default',
|
|
73
|
+
options: [
|
|
74
|
+
{
|
|
75
|
+
text: 'main',
|
|
76
|
+
value: 'main',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
text: 'development',
|
|
80
|
+
value: 'development',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
textSrOnly: true,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
text: 'Feature branches',
|
|
87
|
+
options: [
|
|
88
|
+
{
|
|
89
|
+
text: 'feature/add-avatar',
|
|
90
|
+
value: 'add',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
text: 'feature/improve-panel',
|
|
94
|
+
value: 'improve',
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
text: 'Bugfix branches',
|
|
100
|
+
options: [
|
|
101
|
+
{
|
|
102
|
+
text: 'fix/border-of-avatar',
|
|
103
|
+
value: 'fix-border',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
text: 'fix/radius-panel',
|
|
107
|
+
value: 'fix-radius',
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
|
|
70
113
|
export const mockUsers = [
|
|
71
114
|
{
|
|
72
115
|
value: 'mikegreiling',
|