@gitlab/ui 109.1.0 → 109.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/CHANGELOG.md +14 -0
- package/dist/components/base/dropdown/dropdown_item.js +13 -1
- package/dist/components/base/form/form_combobox/form_combobox.js +13 -4
- package/dist/components/base/new_dropdowns/listbox/listbox.js +33 -2
- package/dist/components/base/new_dropdowns/listbox/listbox_item.js +6 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/vendor/bootstrap-vue/src/components/dropdown/dropdown-item-button.js +5 -3
- package/package.json +1 -1
- package/src/components/base/dropdown/dropdown_item.vue +16 -1
- package/src/components/base/form/form_combobox/form_combobox.vue +67 -49
- package/src/components/base/new_dropdowns/dropdown_item.scss +7 -0
- package/src/components/base/new_dropdowns/listbox/listbox.vue +34 -1
- package/src/components/base/new_dropdowns/listbox/listbox_item.vue +6 -1
- package/src/vendor/bootstrap-vue/src/components/dropdown/dropdown-item-button.js +5 -3
|
@@ -13,7 +13,8 @@ const props = makePropsConfigurable({
|
|
|
13
13
|
activeClass: makeProp(PROP_TYPE_STRING, 'active'),
|
|
14
14
|
buttonClass: makeProp(PROP_TYPE_ARRAY_OBJECT_STRING),
|
|
15
15
|
disabled: makeProp(PROP_TYPE_BOOLEAN, false),
|
|
16
|
-
variant: makeProp(PROP_TYPE_STRING)
|
|
16
|
+
variant: makeProp(PROP_TYPE_STRING),
|
|
17
|
+
role: makeProp(PROP_TYPE_STRING, 'menuitem')
|
|
17
18
|
}, NAME_DROPDOWN_ITEM_BUTTON);
|
|
18
19
|
|
|
19
20
|
// --- Main component ---
|
|
@@ -36,9 +37,10 @@ const BDropdownItemButton = /*#__PURE__*/extend({
|
|
|
36
37
|
computedAttrs() {
|
|
37
38
|
return {
|
|
38
39
|
...this.bvAttrs,
|
|
39
|
-
role:
|
|
40
|
+
role: this.role,
|
|
40
41
|
type: 'button',
|
|
41
|
-
disabled: this.disabled
|
|
42
|
+
disabled: this.disabled,
|
|
43
|
+
'aria-selected': this.active ? 'true' : null
|
|
42
44
|
};
|
|
43
45
|
}
|
|
44
46
|
},
|
package/package.json
CHANGED
|
@@ -60,6 +60,11 @@ export default {
|
|
|
60
60
|
required: false,
|
|
61
61
|
default: '',
|
|
62
62
|
},
|
|
63
|
+
role: {
|
|
64
|
+
type: String,
|
|
65
|
+
required: false,
|
|
66
|
+
default: null,
|
|
67
|
+
},
|
|
63
68
|
},
|
|
64
69
|
computed: {
|
|
65
70
|
bootstrapComponent() {
|
|
@@ -67,6 +72,11 @@ export default {
|
|
|
67
72
|
// Support 'href' and Vue Router's 'to'
|
|
68
73
|
return href || to ? BDropdownItem : BDropdownItemButton;
|
|
69
74
|
},
|
|
75
|
+
bootstrapComponentProps() {
|
|
76
|
+
const props = { ...this.$attrs };
|
|
77
|
+
if (this.role) props.role = this.role;
|
|
78
|
+
return props;
|
|
79
|
+
},
|
|
70
80
|
iconColorCss() {
|
|
71
81
|
return variantCssColorMap[this.iconColor] || 'gl-fill-icon-default';
|
|
72
82
|
},
|
|
@@ -90,7 +100,12 @@ export default {
|
|
|
90
100
|
</script>
|
|
91
101
|
|
|
92
102
|
<template>
|
|
93
|
-
<component
|
|
103
|
+
<component
|
|
104
|
+
:is="bootstrapComponent"
|
|
105
|
+
class="gl-dropdown-item"
|
|
106
|
+
v-bind="bootstrapComponentProps"
|
|
107
|
+
v-on="$listeners"
|
|
108
|
+
>
|
|
94
109
|
<gl-icon
|
|
95
110
|
v-if="shouldShowCheckIcon"
|
|
96
111
|
name="mobile-issue-close"
|
|
@@ -70,11 +70,12 @@ export default {
|
|
|
70
70
|
userDismissedResults: false,
|
|
71
71
|
suggestionsId: uniqueId('token-suggestions-'),
|
|
72
72
|
inputId: uniqueId('token-input-'),
|
|
73
|
+
dropdownItemComponent: GlDropdownItem,
|
|
73
74
|
};
|
|
74
75
|
},
|
|
75
76
|
computed: {
|
|
76
|
-
|
|
77
|
-
return this.showSuggestions.
|
|
77
|
+
isExpanded() {
|
|
78
|
+
return this.showSuggestions && !this.userDismissedResults;
|
|
78
79
|
},
|
|
79
80
|
showAutocomplete() {
|
|
80
81
|
return this.showSuggestions ? 'off' : 'on';
|
|
@@ -149,6 +150,14 @@ export default {
|
|
|
149
150
|
this.arrowCounter = newCount;
|
|
150
151
|
this.focusItem(newCount);
|
|
151
152
|
},
|
|
153
|
+
onArrowLeft() {
|
|
154
|
+
// don't prevent default, we want the cursor to move
|
|
155
|
+
this.$refs.input.focus();
|
|
156
|
+
},
|
|
157
|
+
onArrowRight() {
|
|
158
|
+
// don't prevent default, we want the cursor to move
|
|
159
|
+
this.$refs.input.focus();
|
|
160
|
+
},
|
|
152
161
|
onEsc() {
|
|
153
162
|
if (!this.showSuggestions) {
|
|
154
163
|
this.$emit('input', '');
|
|
@@ -202,79 +211,88 @@ export default {
|
|
|
202
211
|
},
|
|
203
212
|
};
|
|
204
213
|
</script>
|
|
214
|
+
|
|
205
215
|
<template>
|
|
206
|
-
<div
|
|
207
|
-
class="gl-form-combobox dropdown"
|
|
208
|
-
role="combobox"
|
|
209
|
-
:aria-owns="suggestionsId"
|
|
210
|
-
:aria-expanded="ariaExpanded"
|
|
211
|
-
>
|
|
216
|
+
<div class="gl-form-combobox dropdown">
|
|
212
217
|
<gl-form-group :label="labelText" :label-for="inputId" :label-sr-only="labelSrOnly">
|
|
213
218
|
<gl-form-input
|
|
214
219
|
:id="inputId"
|
|
220
|
+
ref="input"
|
|
215
221
|
:value="displayedValue"
|
|
216
222
|
type="text"
|
|
217
|
-
role="
|
|
218
|
-
:
|
|
219
|
-
aria-autocomplete="list"
|
|
223
|
+
role="combobox"
|
|
224
|
+
:aria-expanded="String(isExpanded)"
|
|
220
225
|
:aria-controls="suggestionsId"
|
|
226
|
+
:aria-owns="isExpanded ? suggestionsId : null"
|
|
227
|
+
aria-autocomplete="list"
|
|
221
228
|
aria-haspopup="listbox"
|
|
229
|
+
:aria-activedescendant="
|
|
230
|
+
arrowCounter >= 0 ? `${suggestionsId}-option-${arrowCounter}` : undefined
|
|
231
|
+
"
|
|
222
232
|
:autofocus="autofocus"
|
|
223
233
|
:placeholder="placeholder"
|
|
224
234
|
@input="onEntry"
|
|
225
235
|
@focus="resetCounter"
|
|
226
236
|
@keydown.down="onArrowDown"
|
|
227
237
|
@keydown.up="onArrowUp"
|
|
238
|
+
@keydown.left="onArrowLeft"
|
|
239
|
+
@keydown.right="onArrowRight"
|
|
228
240
|
@keydown.esc.stop="onEsc"
|
|
229
241
|
@keydown.tab="closeSuggestions"
|
|
230
242
|
/>
|
|
231
243
|
</gl-form-group>
|
|
232
244
|
|
|
233
|
-
<
|
|
234
|
-
v-show="
|
|
245
|
+
<div
|
|
246
|
+
v-show="isExpanded"
|
|
235
247
|
:id="suggestionsId"
|
|
236
248
|
ref="suggestionsMenu"
|
|
237
249
|
data-testid="combobox-dropdown"
|
|
250
|
+
role="listbox"
|
|
238
251
|
class="dropdown-menu gl-form-combobox-inner gl-mb-0 gl-flex gl-w-full gl-list-none gl-flex-col gl-border-dropdown gl-bg-dropdown gl-pl-0"
|
|
239
252
|
@keydown.down="onArrowDown"
|
|
240
253
|
@keydown.up="onArrowUp"
|
|
254
|
+
@keydown.left="onArrowLeft"
|
|
255
|
+
@keydown.right="onArrowRight"
|
|
241
256
|
@keydown.esc.stop="onEsc"
|
|
242
257
|
>
|
|
243
|
-
<
|
|
244
|
-
<
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
</
|
|
259
|
-
</
|
|
260
|
-
|
|
261
|
-
<
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
258
|
+
<div class="gl-overflow-y-auto">
|
|
259
|
+
<gl-dropdown-item
|
|
260
|
+
v-for="(result, i) in results"
|
|
261
|
+
:id="`${suggestionsId}-option-${i}`"
|
|
262
|
+
ref="results"
|
|
263
|
+
:key="i"
|
|
264
|
+
:active="i === arrowCounter"
|
|
265
|
+
tabindex="-1"
|
|
266
|
+
role="option"
|
|
267
|
+
data-testid="combobox-result"
|
|
268
|
+
@click="selectToken(result)"
|
|
269
|
+
@keydown.enter.native="selectToken(result)"
|
|
270
|
+
>
|
|
271
|
+
<!-- @slot The suggestion result item to display. -->
|
|
272
|
+
<slot name="result" :item="result">{{ result }}</slot>
|
|
273
|
+
</gl-dropdown-item>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
<gl-dropdown-divider v-if="resultsLength > 0 && actionList.length > 0" aria-hidden="true" />
|
|
277
|
+
|
|
278
|
+
<div>
|
|
279
|
+
<gl-dropdown-item
|
|
280
|
+
v-for="(action, i) in actionList"
|
|
281
|
+
:id="`${suggestionsId}-option-${i + resultsLength}`"
|
|
282
|
+
:key="i + resultsLength"
|
|
283
|
+
:active="i + resultsLength === arrowCounter"
|
|
284
|
+
tabindex="-1"
|
|
285
|
+
role="option"
|
|
286
|
+
data-testid="combobox-action"
|
|
287
|
+
@click="selectAction(action)"
|
|
288
|
+
@keydown.enter.native="selectAction(action)"
|
|
289
|
+
@keydown.left="onArrowLeft"
|
|
290
|
+
@keydown.right="onArrowRight"
|
|
291
|
+
>
|
|
292
|
+
<!-- @slot The action item to display. -->
|
|
293
|
+
<slot name="action" :item="action">{{ action.label }}</slot>
|
|
294
|
+
</gl-dropdown-item>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
279
297
|
</div>
|
|
280
298
|
</template>
|
|
@@ -93,6 +93,13 @@
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// Used in Listbox to visually highlight the first item after search
|
|
97
|
+
&.gl-new-dropdown-item-highlighted .gl-new-dropdown-item-content {
|
|
98
|
+
color: var(--gl-dropdown-option-text-color-focus);
|
|
99
|
+
background-color: var(--gl-dropdown-option-background-color-unselected-focus);
|
|
100
|
+
@include gl-focus($inset: true);
|
|
101
|
+
}
|
|
102
|
+
|
|
96
103
|
.gl-new-dropdown-item-content {
|
|
97
104
|
@apply gl-rounded-base;
|
|
98
105
|
@apply gl-border-0;
|
|
@@ -7,6 +7,7 @@ import { stopEvent } from '../../../../utils/utils';
|
|
|
7
7
|
import {
|
|
8
8
|
GL_DROPDOWN_SHOWN,
|
|
9
9
|
GL_DROPDOWN_HIDDEN,
|
|
10
|
+
ENTER,
|
|
10
11
|
HOME,
|
|
11
12
|
END,
|
|
12
13
|
ARROW_DOWN,
|
|
@@ -389,6 +390,9 @@ export default {
|
|
|
389
390
|
flattenedOptions() {
|
|
390
391
|
return flattenedOptions(this.items);
|
|
391
392
|
},
|
|
393
|
+
searchHasOptions() {
|
|
394
|
+
return this.flattenedOptions.length > 0 && this.searchStr;
|
|
395
|
+
},
|
|
392
396
|
hasItems() {
|
|
393
397
|
return this.items.length > 0;
|
|
394
398
|
},
|
|
@@ -503,6 +507,16 @@ export default {
|
|
|
503
507
|
/* Every time the list of items changes (on search),
|
|
504
508
|
* the observed elements are recreated, thus we need to start obesrving them again */
|
|
505
509
|
this.observeScroll();
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Every time the list of items changes, and there is a search string,
|
|
513
|
+
* we want to visually highlight the first item
|
|
514
|
+
*/
|
|
515
|
+
if (this.searchHasOptions) {
|
|
516
|
+
this.nextFocusedItemIndex = 0;
|
|
517
|
+
} else {
|
|
518
|
+
this.nextFocusedItemIndex = null;
|
|
519
|
+
}
|
|
506
520
|
});
|
|
507
521
|
},
|
|
508
522
|
},
|
|
@@ -564,6 +578,13 @@ export default {
|
|
|
564
578
|
onShow() {
|
|
565
579
|
if (this.searchable) {
|
|
566
580
|
this.focusSearchInput();
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* If the search string is not empty, highlight the first item
|
|
584
|
+
*/
|
|
585
|
+
if (this.searchHasOptions) {
|
|
586
|
+
this.nextFocusedItemIndex = 0;
|
|
587
|
+
}
|
|
567
588
|
} else {
|
|
568
589
|
this.focusItem(this.selectedIndices[0] ?? 0, this.getFocusableListItemElements());
|
|
569
590
|
}
|
|
@@ -608,6 +629,9 @@ export default {
|
|
|
608
629
|
}
|
|
609
630
|
if (this.searchable && elements.indexOf(target) === 0) {
|
|
610
631
|
this.focusSearchInput();
|
|
632
|
+
if (!this.searchHasOptions) {
|
|
633
|
+
this.nextFocusedItemIndex = null;
|
|
634
|
+
}
|
|
611
635
|
} else {
|
|
612
636
|
this.focusNextItem(event, elements, -1);
|
|
613
637
|
}
|
|
@@ -617,6 +641,11 @@ export default {
|
|
|
617
641
|
} else {
|
|
618
642
|
this.focusNextItem(event, elements, 1);
|
|
619
643
|
}
|
|
644
|
+
} else if (code === ENTER && isSearchInput) {
|
|
645
|
+
if (this.searchHasOptions && elements.length > 0) {
|
|
646
|
+
this.onSelect(this.flattenedOptions[0], true);
|
|
647
|
+
}
|
|
648
|
+
stop = true;
|
|
620
649
|
} else {
|
|
621
650
|
stop = false;
|
|
622
651
|
}
|
|
@@ -651,6 +680,9 @@ export default {
|
|
|
651
680
|
this.onSingleSelect(item.value, isSelected);
|
|
652
681
|
}
|
|
653
682
|
},
|
|
683
|
+
isHighlighted(item) {
|
|
684
|
+
return this.nextFocusedItemIndex === this.flattenedOptions.indexOf(item);
|
|
685
|
+
},
|
|
654
686
|
isSelected(item) {
|
|
655
687
|
return this.selectedValues.some((value) => value === item.value);
|
|
656
688
|
},
|
|
@@ -666,7 +698,6 @@ export default {
|
|
|
666
698
|
*/
|
|
667
699
|
this.$emit('select', value);
|
|
668
700
|
}
|
|
669
|
-
|
|
670
701
|
this.closeAndFocus();
|
|
671
702
|
},
|
|
672
703
|
onMultiSelect(value, isSelected) {
|
|
@@ -866,6 +897,7 @@ export default {
|
|
|
866
897
|
<gl-listbox-item
|
|
867
898
|
:key="item.value"
|
|
868
899
|
:data-testid="`listbox-item-${item.value}`"
|
|
900
|
+
:is-highlighted="isHighlighted(item)"
|
|
869
901
|
:is-selected="isSelected(item)"
|
|
870
902
|
:is-focused="isFocused(item)"
|
|
871
903
|
:is-check-centered="isCheckCentered"
|
|
@@ -895,6 +927,7 @@ export default {
|
|
|
895
927
|
v-for="option in item.options"
|
|
896
928
|
:key="option.value"
|
|
897
929
|
:data-testid="`listbox-item-${option.value}`"
|
|
930
|
+
:is-highlighted="isHighlighted(option)"
|
|
898
931
|
:is-selected="isSelected(option)"
|
|
899
932
|
:is-focused="isFocused(option)"
|
|
900
933
|
:is-check-centered="isCheckCentered"
|
|
@@ -24,6 +24,11 @@ export default {
|
|
|
24
24
|
required: false,
|
|
25
25
|
default: false,
|
|
26
26
|
},
|
|
27
|
+
isHighlighted: {
|
|
28
|
+
type: Boolean,
|
|
29
|
+
default: false,
|
|
30
|
+
required: false,
|
|
31
|
+
},
|
|
27
32
|
},
|
|
28
33
|
computed: {
|
|
29
34
|
checkedClasses() {
|
|
@@ -52,7 +57,7 @@ export default {
|
|
|
52
57
|
|
|
53
58
|
<template>
|
|
54
59
|
<li
|
|
55
|
-
class="gl-new-dropdown-item"
|
|
60
|
+
:class="['gl-new-dropdown-item', { 'gl-new-dropdown-item-highlighted': isHighlighted }]"
|
|
56
61
|
role="option"
|
|
57
62
|
:tabindex="isFocused ? 0 : -1"
|
|
58
63
|
:aria-selected="isSelected"
|
|
@@ -18,7 +18,8 @@ export const props = makePropsConfigurable(
|
|
|
18
18
|
activeClass: makeProp(PROP_TYPE_STRING, 'active'),
|
|
19
19
|
buttonClass: makeProp(PROP_TYPE_ARRAY_OBJECT_STRING),
|
|
20
20
|
disabled: makeProp(PROP_TYPE_BOOLEAN, false),
|
|
21
|
-
variant: makeProp(PROP_TYPE_STRING)
|
|
21
|
+
variant: makeProp(PROP_TYPE_STRING),
|
|
22
|
+
role: makeProp(PROP_TYPE_STRING, 'menuitem')
|
|
22
23
|
},
|
|
23
24
|
NAME_DROPDOWN_ITEM_BUTTON
|
|
24
25
|
)
|
|
@@ -42,9 +43,10 @@ export const BDropdownItemButton = /*#__PURE__*/ extend({
|
|
|
42
43
|
computedAttrs() {
|
|
43
44
|
return {
|
|
44
45
|
...this.bvAttrs,
|
|
45
|
-
role:
|
|
46
|
+
role: this.role,
|
|
46
47
|
type: 'button',
|
|
47
|
-
disabled: this.disabled
|
|
48
|
+
disabled: this.disabled,
|
|
49
|
+
'aria-selected': this.active ? 'true' : null
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
52
|
},
|