@gitlab/ui 64.1.0 → 64.2.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 +14 -0
- package/dist/components/base/filtered_search/filtered_search_token.js +6 -1
- 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 +2 -2
- package/src/components/base/filtered_search/filtered_search_token.spec.js +15 -0
- package/src/components/base/filtered_search/filtered_search_token.stories.js +48 -0
- package/src/components/base/filtered_search/filtered_search_token.vue +6 -0
- 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/src/scss/functions.scss +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [64.2.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v64.1.1...v64.2.0) (2023-06-02)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **GlFilteredSearchToken:** Pass input attributes to data segment ([e8dcc5f](https://gitlab.com/gitlab-org/gitlab-ui/commit/e8dcc5f8fb8ff4b6fa78f1e46b899a18990d39c8))
|
|
7
|
+
|
|
8
|
+
## [64.1.1](https://gitlab.com/gitlab-org/gitlab-ui/compare/v64.1.0...v64.1.1) (2023-06-01)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **dropdowns:** auto-focus only once dropdown is properly positioned ([fe6c39d](https://gitlab.com/gitlab-org/gitlab-ui/commit/fe6c39dee065cbbc35dd225ea0e565dba368af14))
|
|
14
|
+
|
|
1
15
|
# [64.1.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v64.0.2...v64.1.0) (2023-05-30)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -81,6 +81,11 @@ var script = {
|
|
|
81
81
|
type: Boolean,
|
|
82
82
|
required: false,
|
|
83
83
|
default: false
|
|
84
|
+
},
|
|
85
|
+
dataSegmentInputAttributes: {
|
|
86
|
+
type: Object,
|
|
87
|
+
required: false,
|
|
88
|
+
default: () => ({})
|
|
84
89
|
}
|
|
85
90
|
},
|
|
86
91
|
data() {
|
|
@@ -322,7 +327,7 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
|
|
|
322
327
|
var inputValue = ref.inputValue;
|
|
323
328
|
return [_c('gl-token',{staticClass:"gl-filtered-search-token-type",class:_vm.getAdditionalSegmentClasses(_vm.$options.segments.SEGMENT_TITLE),attrs:{"view-only":""}},[_vm._v("\n "+_vm._s(inputValue)+"\n ")])]}}])}),_vm._v(" "),_c('gl-filtered-search-token-segment',{key:"operator-segment",attrs:{"active":_vm.isSegmentActive(_vm.$options.segments.SEGMENT_OPERATOR),"cursor-position":_vm.intendedCursorPosition,"options":_vm.operators,"option-text-field":"value","custom-input-keydown-handler":_vm.handleOperatorKeydown,"view-only":_vm.viewOnly},on:{"activate":function($event){return _vm.activateSegment(_vm.$options.segments.SEGMENT_OPERATOR)},"backspace":_vm.replaceWithTermIfEmpty,"complete":function($event){return _vm.activateSegment(_vm.$options.segments.SEGMENT_DATA)},"deactivate":function($event){return _vm.$emit('deactivate')},"previous":_vm.activatePreviousTitleSegment,"next":_vm.activateNextDataSegment},scopedSlots:_vm._u([{key:"view",fn:function(){return [_c('gl-token',{staticClass:"gl-filtered-search-token-operator",class:_vm.getAdditionalSegmentClasses(_vm.$options.segments.SEGMENT_OPERATOR),attrs:{"variant":"search-value","view-only":""}},[_vm._v("\n "+_vm._s(_vm.operatorDescription)+"\n ")])]},proxy:true},{key:"option",fn:function(ref){
|
|
324
329
|
var option = ref.option;
|
|
325
|
-
return [_c('div',{staticClass:"gl-display-flex"},[_vm._v("\n "+_vm._s(_vm.showFriendlyText ? option.description : option.value)+"\n "),(option.description)?_c('span',{staticClass:"gl-filtered-search-token-operator-description"},[_vm._v("\n "+_vm._s(_vm.showFriendlyText ? option.value : option.description)+"\n ")]):_vm._e()])]}}]),model:{value:(_vm.tokenValue.operator),callback:function ($$v) {_vm.$set(_vm.tokenValue, "operator", $$v);},expression:"tokenValue.operator"}}),_vm._v(" "),(_vm.hasDataOrDataSegmentIsCurrentlyActive)?_c('gl-filtered-search-token-segment',{key:"data-segment",attrs:{"active":_vm.isSegmentActive(_vm.$options.segments.SEGMENT_DATA),"cursor-position":_vm.intendedCursorPosition,"multi-select":_vm.config.multiSelect,"options":_vm.config.options,"view-only":_vm.viewOnly},on:{"activate":_vm.activateDataSegment,"backspace":function($event){return _vm.activateSegment(_vm.$options.segments.SEGMENT_OPERATOR)},"complete":_vm.handleComplete,"select":function($event){return _vm.$emit('select', $event)},"submit":function($event){return _vm.$emit('submit')},"deactivate":function($event){return _vm.$emit('deactivate')},"split":function($event){return _vm.$emit('split', $event)},"previous":_vm.activatePreviousOperatorSegment,"next":function($event){return _vm.$emit('next')}},scopedSlots:_vm._u([{key:"suggestions",fn:function(){return [_vm._t("suggestions")]},proxy:true},{key:"view",fn:function(ref){
|
|
330
|
+
return [_c('div',{staticClass:"gl-display-flex"},[_vm._v("\n "+_vm._s(_vm.showFriendlyText ? option.description : option.value)+"\n "),(option.description)?_c('span',{staticClass:"gl-filtered-search-token-operator-description"},[_vm._v("\n "+_vm._s(_vm.showFriendlyText ? option.value : option.description)+"\n ")]):_vm._e()])]}}]),model:{value:(_vm.tokenValue.operator),callback:function ($$v) {_vm.$set(_vm.tokenValue, "operator", $$v);},expression:"tokenValue.operator"}}),_vm._v(" "),(_vm.hasDataOrDataSegmentIsCurrentlyActive)?_c('gl-filtered-search-token-segment',{key:"data-segment",attrs:{"active":_vm.isSegmentActive(_vm.$options.segments.SEGMENT_DATA),"cursor-position":_vm.intendedCursorPosition,"multi-select":_vm.config.multiSelect,"options":_vm.config.options,"view-only":_vm.viewOnly,"search-input-attributes":_vm.dataSegmentInputAttributes},on:{"activate":_vm.activateDataSegment,"backspace":function($event){return _vm.activateSegment(_vm.$options.segments.SEGMENT_OPERATOR)},"complete":_vm.handleComplete,"select":function($event){return _vm.$emit('select', $event)},"submit":function($event){return _vm.$emit('submit')},"deactivate":function($event){return _vm.$emit('deactivate')},"split":function($event){return _vm.$emit('split', $event)},"previous":_vm.activatePreviousOperatorSegment,"next":function($event){return _vm.$emit('next')}},scopedSlots:_vm._u([{key:"suggestions",fn:function(){return [_vm._t("suggestions")]},proxy:true},{key:"view",fn:function(ref){
|
|
326
331
|
var inputValue = ref.inputValue;
|
|
327
332
|
return [_vm._t("view-token",function(){return [_c('gl-token',_vm._g({staticClass:"gl-filtered-search-token-data",class:_vm.getAdditionalSegmentClasses(_vm.$options.segments.SEGMENT_DATA),attrs:{"variant":"search-value","view-only":_vm.viewOnly}},_vm.eventListeners),[_c('span',{staticClass:"gl-filtered-search-token-data-content"},[_vm._t("view",function(){return [_vm._v(_vm._s(inputValue))]},null,{ inputValue: inputValue })],2)])]},null,{
|
|
328
333
|
inputValue: inputValue,
|
|
@@ -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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/ui",
|
|
3
|
-
"version": "64.
|
|
3
|
+
"version": "64.2.0",
|
|
4
4
|
"description": "GitLab UI Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"devDependencies": {
|
|
86
86
|
"@arkweid/lefthook": "0.7.7",
|
|
87
87
|
"@babel/core": "^7.22.1",
|
|
88
|
-
"@babel/preset-env": "^7.22.
|
|
88
|
+
"@babel/preset-env": "^7.22.4",
|
|
89
89
|
"@babel/preset-react": "^7.22.3",
|
|
90
90
|
"@gitlab/eslint-plugin": "19.0.0",
|
|
91
91
|
"@gitlab/fonts": "^1.2.0",
|
|
@@ -210,6 +210,21 @@ describe('Filtered search token', () => {
|
|
|
210
210
|
expect(wrapper.emitted().replace[0][0].value).toStrictEqual({ data: '' });
|
|
211
211
|
});
|
|
212
212
|
|
|
213
|
+
it('sets input attributes on data segment when provided', () => {
|
|
214
|
+
const dataSegmentInputAttributes = {
|
|
215
|
+
placeholder: 'YYYY-MM-DD',
|
|
216
|
+
id: 'this-id',
|
|
217
|
+
};
|
|
218
|
+
createComponent({
|
|
219
|
+
active: true,
|
|
220
|
+
dataSegmentInputAttributes,
|
|
221
|
+
value: { operator: '=', data: 'something' },
|
|
222
|
+
});
|
|
223
|
+
expect(findDataSegment().props().searchInputAttributes).toStrictEqual(
|
|
224
|
+
dataSegmentInputAttributes
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
|
|
213
228
|
describe('integration tests', () => {
|
|
214
229
|
beforeAll(() => {
|
|
215
230
|
if (!HTMLElement.prototype.scrollIntoView) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import PortalVue from 'portal-vue';
|
|
2
2
|
import Vue from 'vue';
|
|
3
3
|
import GlIcon from '../icon/icon.vue';
|
|
4
|
+
import GlDatepicker from '../datepicker/datepicker.vue';
|
|
4
5
|
import { provide } from './common_story_options';
|
|
5
6
|
import GlFilteredSearchSuggestion from './filtered_search_suggestion.vue';
|
|
6
7
|
import readme from './filtered_search_token.md';
|
|
@@ -153,6 +154,53 @@ export const WithStaticOptions = (args, { argTypes }) => ({
|
|
|
153
154
|
});
|
|
154
155
|
WithStaticOptions.args = generateProps();
|
|
155
156
|
|
|
157
|
+
// eslint-disable-next-line no-unused-vars
|
|
158
|
+
export const WithDataSegmentInputAttributes = (args, { argTypes }) => ({
|
|
159
|
+
components: {
|
|
160
|
+
GlFilteredSearchToken,
|
|
161
|
+
GlDatepicker,
|
|
162
|
+
},
|
|
163
|
+
provide,
|
|
164
|
+
props: ['active'],
|
|
165
|
+
data() {
|
|
166
|
+
return {
|
|
167
|
+
value: { operator: '<', data: null },
|
|
168
|
+
config: {
|
|
169
|
+
title: 'Date',
|
|
170
|
+
operators: [
|
|
171
|
+
{ value: '<', description: 'before' },
|
|
172
|
+
{ value: '>', description: 'after' },
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
dataSegmentInputAttributes: {
|
|
176
|
+
placeholder: 'YYYY-MM-DD',
|
|
177
|
+
id: 'this-id',
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
},
|
|
181
|
+
mounted() {
|
|
182
|
+
this.$nextTick(() => {
|
|
183
|
+
document.activeElement.blur();
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
template: `
|
|
187
|
+
<div>
|
|
188
|
+
<div> {{ value }} </div>
|
|
189
|
+
<div class="gl-border-1 gl-border-solid gl-border-gray-200">
|
|
190
|
+
<gl-filtered-search-token
|
|
191
|
+
v-model="value"
|
|
192
|
+
class="gl-h-full"
|
|
193
|
+
:config="config"
|
|
194
|
+
:active="active"
|
|
195
|
+
:data-segment-input-attributes="dataSegmentInputAttributes"
|
|
196
|
+
/>
|
|
197
|
+
<gl-datepicker target='#this-id' />
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
`,
|
|
201
|
+
});
|
|
202
|
+
WithDataSegmentInputAttributes.args = generateProps();
|
|
203
|
+
|
|
156
204
|
export default {
|
|
157
205
|
title: 'base/filtered-search/token',
|
|
158
206
|
component: GlFilteredSearchToken,
|
|
@@ -78,6 +78,11 @@ export default {
|
|
|
78
78
|
required: false,
|
|
79
79
|
default: false,
|
|
80
80
|
},
|
|
81
|
+
dataSegmentInputAttributes: {
|
|
82
|
+
type: Object,
|
|
83
|
+
required: false,
|
|
84
|
+
default: () => ({}),
|
|
85
|
+
},
|
|
81
86
|
},
|
|
82
87
|
data() {
|
|
83
88
|
return {
|
|
@@ -405,6 +410,7 @@ export default {
|
|
|
405
410
|
:multi-select="config.multiSelect"
|
|
406
411
|
:options="config.options"
|
|
407
412
|
:view-only="viewOnly"
|
|
413
|
+
:search-input-attributes="dataSegmentInputAttributes"
|
|
408
414
|
@activate="activateDataSegment"
|
|
409
415
|
@backspace="activateSegment($options.segments.SEGMENT_OPERATOR)"
|
|
410
416
|
@complete="handleComplete"
|
|
@@ -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',
|
package/src/scss/functions.scss
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
@return $value;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
$converted: $value / $font-size-base;
|
|
24
|
+
$converted: calc($value / $font-size-base);
|
|
25
25
|
|
|
26
26
|
@return strip-unit($converted) * 1rem;
|
|
27
27
|
}
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
$min-width: px-to-rem($min-width);
|
|
55
55
|
$max-width: px-to-rem($max-width);
|
|
56
56
|
|
|
57
|
-
$slope: ($max - $min) / ($max-width - $min-width);
|
|
57
|
+
$slope: calc(($max - $min) / ($max-width - $min-width));
|
|
58
58
|
$intersection: (-$min-width * $slope) + $min;
|
|
59
59
|
|
|
60
60
|
// Use calc() inside of clamp() function to work around SassC
|