@gitlab/ui 39.3.2 → 39.5.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/components/base/alert/alert.js +1 -1
  3. package/dist/components/base/filtered_search/filtered_search_term.js +2 -1
  4. package/dist/components/base/filtered_search/filtered_search_token.js +3 -2
  5. package/dist/components/base/filtered_search/filtered_search_token_segment.js +3 -2
  6. package/dist/components/base/new_dropdowns/base_dropdown/base_dropdown.js +240 -0
  7. package/dist/components/base/new_dropdowns/constants.js +20 -0
  8. package/dist/components/base/new_dropdowns/listbox/listbox.js +381 -0
  9. package/dist/components/base/new_dropdowns/listbox/listbox_item.js +77 -0
  10. package/dist/index.css +1 -1
  11. package/dist/index.css.map +1 -1
  12. package/dist/index.js +2 -0
  13. package/dist/utility_classes.css +1 -1
  14. package/dist/utility_classes.css.map +1 -1
  15. package/dist/utils/utils.js +24 -1
  16. package/package.json +5 -4
  17. package/src/components/base/alert/alert.spec.js +3 -1
  18. package/src/components/base/alert/alert.vue +1 -1
  19. package/src/components/base/dropdown/dropdown.scss +10 -3
  20. package/src/components/base/dropdown/dropdown_item.scss +1 -0
  21. package/src/components/base/filtered_search/filtered_search_term.vue +9 -1
  22. package/src/components/base/filtered_search/filtered_search_token.vue +16 -3
  23. package/src/components/base/filtered_search/filtered_search_token_segment.vue +5 -4
  24. package/src/components/base/link/link.stories.js +9 -7
  25. package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.spec.js +171 -0
  26. package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.vue +221 -0
  27. package/src/components/base/new_dropdowns/constants.js +22 -0
  28. package/src/components/base/new_dropdowns/listbox/listbox.md +71 -0
  29. package/src/components/base/new_dropdowns/listbox/listbox.spec.js +236 -0
  30. package/src/components/base/new_dropdowns/listbox/listbox.stories.js +276 -0
  31. package/src/components/base/new_dropdowns/listbox/listbox.vue +348 -0
  32. package/src/components/base/new_dropdowns/listbox/listbox_item.spec.js +104 -0
  33. package/src/components/base/new_dropdowns/listbox/listbox_item.vue +59 -0
  34. package/src/components/utilities/friendly_wrap/friendly_wrap.stories.js +10 -11
  35. package/src/components/utilities/sprintf/sprintf.stories.js +11 -9
  36. package/src/index.js +4 -0
  37. package/src/scss/utilities.scss +10 -0
  38. package/src/scss/utility-mixins/composite.scss +20 -0
  39. package/src/scss/utility-mixins/index.scss +1 -0
  40. package/src/utils/data_utils.js +2 -21
  41. package/src/utils/utils.js +18 -0
  42. package/src/utils/utils.spec.js +41 -1
@@ -0,0 +1,381 @@
1
+ import _uniqueId from 'lodash/uniqueId';
2
+ import _clamp from 'lodash/clamp';
3
+ import { stopEvent } from '../../../../utils/utils';
4
+ import { GL_DROPDOWN_SHOWN, GL_DROPDOWN_HIDDEN, HOME, END, ARROW_UP, ARROW_DOWN } from '../constants';
5
+ import { buttonCategoryOptions, dropdownVariantOptions, buttonSizeOptions } from '../../../../utils/constants';
6
+ import GlBaseDropdown from '../base_dropdown/base_dropdown';
7
+ import GlListboxItem from './listbox_item';
8
+ import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
9
+
10
+ const ITEM_SELECTOR = '[role="option"]';
11
+ var script = {
12
+ events: {
13
+ GL_DROPDOWN_SHOWN,
14
+ GL_DROPDOWN_HIDDEN
15
+ },
16
+ components: {
17
+ GlBaseDropdown,
18
+ GlListboxItem
19
+ },
20
+ model: {
21
+ prop: 'selected',
22
+ event: 'select'
23
+ },
24
+ props: {
25
+ /**
26
+ * Items to display in the dropdown
27
+ */
28
+ items: {
29
+ type: Array,
30
+ required: false,
31
+ default: () => [],
32
+ validator: items => {
33
+ return items.every(_ref => {
34
+ let {
35
+ value
36
+ } = _ref;
37
+ return typeof value === 'string';
38
+ });
39
+ }
40
+ },
41
+
42
+ /**
43
+ * array of selected items values for multi-select and selected item value for single-select
44
+ */
45
+ selected: {
46
+ type: [Array, String, Number],
47
+ required: false,
48
+ default: () => []
49
+ },
50
+
51
+ /**
52
+ * Allow multi-selection
53
+ */
54
+ multiple: {
55
+ type: Boolean,
56
+ required: false,
57
+ default: false
58
+ },
59
+
60
+ /**
61
+ * Toggle button text
62
+ */
63
+ toggleText: {
64
+ type: String,
65
+ required: false,
66
+ default: ''
67
+ },
68
+
69
+ /**
70
+ * Toggle text to be read by screen readers only
71
+ */
72
+ textSrOnly: {
73
+ type: Boolean,
74
+ required: false,
75
+ default: false
76
+ },
77
+
78
+ /**
79
+ * Styling option - dropdown's toggle category
80
+ */
81
+ category: {
82
+ type: String,
83
+ required: false,
84
+ default: buttonCategoryOptions.primary,
85
+ validator: value => Object.keys(buttonCategoryOptions).includes(value)
86
+ },
87
+
88
+ /**
89
+ * Styling option - dropdown's toggle variant
90
+ */
91
+ variant: {
92
+ type: String,
93
+ required: false,
94
+ default: dropdownVariantOptions.default,
95
+ validator: value => Object.keys(dropdownVariantOptions).includes(value)
96
+ },
97
+
98
+ /**
99
+ * The size of the dropdown toggle
100
+ */
101
+ size: {
102
+ type: String,
103
+ required: false,
104
+ default: buttonSizeOptions.medium,
105
+ validator: value => Object.keys(buttonSizeOptions).includes(value)
106
+ },
107
+
108
+ /**
109
+ * Icon name that will be rendered in the toggle button
110
+ */
111
+ icon: {
112
+ type: String,
113
+ required: false,
114
+ default: ''
115
+ },
116
+
117
+ /**
118
+ * Set to "true" to disable the dropdown
119
+ */
120
+ disabled: {
121
+ type: Boolean,
122
+ required: false,
123
+ default: false
124
+ },
125
+
126
+ /**
127
+ * Set to "true" when dropdown content (items) is loading
128
+ */
129
+ loading: {
130
+ type: Boolean,
131
+ required: false,
132
+ default: false
133
+ },
134
+
135
+ /**
136
+ * Additional CSS classes to customize toggle appearance
137
+ */
138
+ toggleClass: {
139
+ type: [String, Array, Object],
140
+ required: false,
141
+ default: null
142
+ },
143
+
144
+ /**
145
+ * Set to "true" to hide the caret
146
+ */
147
+ noCaret: {
148
+ type: Boolean,
149
+ required: false,
150
+ default: false
151
+ },
152
+
153
+ /**
154
+ * Right align listbox menu with respect to the toggle button
155
+ */
156
+ right: {
157
+ type: Boolean,
158
+ required: false,
159
+ default: false
160
+ },
161
+
162
+ /**
163
+ * The `aria-labelledby` attribute value for the toggle button
164
+ */
165
+ ariaLabelledby: {
166
+ type: String,
167
+ required: false,
168
+ default: null
169
+ }
170
+ },
171
+
172
+ data() {
173
+ return {
174
+ selectedValues: [],
175
+ toggleId: _uniqueId('dropdown-toggle-btn-'),
176
+ nextFocusedItemIndex: null
177
+ };
178
+ },
179
+
180
+ computed: {
181
+ listboxToggleText() {
182
+ if (!this.toggleText) {
183
+ if (!this.multiple && this.selectedValues.length) {
184
+ var _this$items$find;
185
+
186
+ return (_this$items$find = this.items.find(_ref2 => {
187
+ let {
188
+ value
189
+ } = _ref2;
190
+ return value === this.selectedValues[0];
191
+ })) === null || _this$items$find === void 0 ? void 0 : _this$items$find.text;
192
+ }
193
+
194
+ return '';
195
+ }
196
+
197
+ return this.toggleText;
198
+ },
199
+
200
+ selectedIndices() {
201
+ return this.selectedValues.map(selected => this.items.findIndex(_ref3 => {
202
+ let {
203
+ value
204
+ } = _ref3;
205
+ return value === selected;
206
+ })).sort();
207
+ }
208
+
209
+ },
210
+ watch: {
211
+ selected: {
212
+ immediate: true,
213
+
214
+ handler(newSelected) {
215
+ if (Array.isArray(newSelected)) {
216
+ if (!this.multiple && newSelected.length) {
217
+ throw new Error('To allow multi-selection, please, set "multiple" property to "true"');
218
+ }
219
+
220
+ this.selectedValues = [...newSelected];
221
+ } else {
222
+ this.selectedValues = [newSelected];
223
+ }
224
+ }
225
+
226
+ }
227
+ },
228
+ methods: {
229
+ onShow() {
230
+ this.$nextTick(() => {
231
+ var _this$selectedIndices;
232
+
233
+ this.focusItem((_this$selectedIndices = this.selectedIndices[0]) !== null && _this$selectedIndices !== void 0 ? _this$selectedIndices : 0, this.getFocusableListItemElements());
234
+ /**
235
+ * Emitted when dropdown is shown
236
+ *
237
+ * @event shown
238
+ */
239
+
240
+ this.$emit(GL_DROPDOWN_SHOWN);
241
+ });
242
+ },
243
+
244
+ onHide() {
245
+ /**
246
+ * Emitted when dropdown is hidden
247
+ *
248
+ * @event hidden
249
+ */
250
+ this.$emit(GL_DROPDOWN_HIDDEN);
251
+ this.nextFocusedItemIndex = null;
252
+ },
253
+
254
+ onKeydown(event) {
255
+ const {
256
+ code
257
+ } = event;
258
+ const elements = this.getFocusableListItemElements();
259
+ if (elements.length < 1) return;
260
+ let stop = true;
261
+
262
+ if (code === HOME) {
263
+ this.focusItem(0, elements);
264
+ } else if (code === END) {
265
+ this.focusItem(elements.length - 1, elements);
266
+ } else if (code === ARROW_UP) {
267
+ this.focusNextItem(event, elements, -1);
268
+ } else if (code === ARROW_DOWN) {
269
+ this.focusNextItem(event, elements, 1);
270
+ } else {
271
+ stop = false;
272
+ }
273
+
274
+ if (stop) {
275
+ stopEvent(event);
276
+ }
277
+ },
278
+
279
+ getFocusableListItemElements() {
280
+ const items = this.$refs.list.querySelectorAll(ITEM_SELECTOR);
281
+ return Array.from(items);
282
+ },
283
+
284
+ focusNextItem(event, elements, offset) {
285
+ const {
286
+ target
287
+ } = event;
288
+ const currentIndex = elements.indexOf(target);
289
+
290
+ const nextIndex = _clamp(currentIndex + offset, 0, elements.length - 1);
291
+
292
+ this.focusItem(nextIndex, elements);
293
+ },
294
+
295
+ focusItem(index, elements) {
296
+ this.nextFocusedItemIndex = index;
297
+ this.$nextTick(() => {
298
+ var _elements$index;
299
+
300
+ (_elements$index = elements[index]) === null || _elements$index === void 0 ? void 0 : _elements$index.focus();
301
+ });
302
+ },
303
+
304
+ onSelect(_ref4, isSelected) {
305
+ let {
306
+ value
307
+ } = _ref4;
308
+
309
+ if (this.multiple) {
310
+ this.onMultiSelect(value, isSelected);
311
+ } else {
312
+ this.onSingleSelect(value, isSelected);
313
+ }
314
+ },
315
+
316
+ isSelected(item) {
317
+ return this.selectedValues.some(value => value === item.value);
318
+ },
319
+
320
+ onSingleSelect(value, isSelected) {
321
+ if (isSelected) {
322
+ /**
323
+ * Emitted when selection is changed
324
+ *
325
+ * @event select
326
+ */
327
+ this.$emit('select', value);
328
+ }
329
+
330
+ this.$refs.baseDropdown.closeAndFocus();
331
+ },
332
+
333
+ onMultiSelect(value, isSelected) {
334
+ if (isSelected) {
335
+ this.$emit('select', [...this.selectedValues, value]);
336
+ } else {
337
+ this.$emit('select', this.selectedValues.filter(selectedValue => selectedValue !== value));
338
+ }
339
+ }
340
+
341
+ }
342
+ };
343
+
344
+ /* script */
345
+ const __vue_script__ = script;
346
+
347
+ /* template */
348
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-base-dropdown',{ref:"baseDropdown",attrs:{"aria-haspopup":"listbox","aria-labelledby":_vm.ariaLabelledby,"toggle-id":_vm.toggleId,"toggle-text":_vm.listboxToggleText,"toggle-class":_vm.toggleClass,"text-sr-only":_vm.textSrOnly,"category":_vm.category,"variant":_vm.variant,"size":_vm.size,"icon":_vm.icon,"disabled":_vm.disabled,"loading":_vm.loading,"no-caret":_vm.noCaret,"right":_vm.right},on:_vm._d({},[_vm.$options.events.GL_DROPDOWN_SHOWN,_vm.onShow,_vm.$options.events.GL_DROPDOWN_HIDDEN,_vm.onHide])},[_vm._t("header"),_vm._v(" "),_c('ul',{ref:"list",staticClass:"gl-new-dropdown-contents gl-list-style-none gl-pl-0 gl-mb-0",attrs:{"aria-labelledby":_vm.toggleId,"role":"listbox","tabindex":"-1"},on:{"keydown":_vm.onKeydown}},_vm._l((_vm.items),function(item,index){return _c('gl-listbox-item',{key:item.value,attrs:{"is-selected":_vm.isSelected(item),"is-focused":_vm.nextFocusedItemIndex === index},on:{"select":function($event){return _vm.onSelect(item, $event)}}},[_vm._t("list-item",[_vm._v("\n "+_vm._s(item.text)+"\n ")],{"item":item})],2)}),1),_vm._v(" "),_vm._t("footer")],2)};
349
+ var __vue_staticRenderFns__ = [];
350
+
351
+ /* style */
352
+ const __vue_inject_styles__ = undefined;
353
+ /* scoped */
354
+ const __vue_scope_id__ = undefined;
355
+ /* module identifier */
356
+ const __vue_module_identifier__ = undefined;
357
+ /* functional template */
358
+ const __vue_is_functional_template__ = false;
359
+ /* style inject */
360
+
361
+ /* style inject SSR */
362
+
363
+ /* style inject shadow dom */
364
+
365
+
366
+
367
+ const __vue_component__ = __vue_normalize__(
368
+ { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
369
+ __vue_inject_styles__,
370
+ __vue_script__,
371
+ __vue_scope_id__,
372
+ __vue_is_functional_template__,
373
+ __vue_module_identifier__,
374
+ false,
375
+ undefined,
376
+ undefined,
377
+ undefined
378
+ );
379
+
380
+ export default __vue_component__;
381
+ export { ITEM_SELECTOR };
@@ -0,0 +1,77 @@
1
+ import GlIcon from '../../icon/icon';
2
+ import { ENTER, SPACE } from '../constants';
3
+ import { stopEvent } from '../../../../utils/utils';
4
+ import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
5
+
6
+ var script = {
7
+ components: {
8
+ GlIcon
9
+ },
10
+ props: {
11
+ isSelected: {
12
+ type: Boolean,
13
+ default: false,
14
+ required: false
15
+ },
16
+ isFocused: {
17
+ type: Boolean,
18
+ default: false,
19
+ required: false
20
+ }
21
+ },
22
+ methods: {
23
+ toggleSelection() {
24
+ this.$emit('select', !this.isSelected);
25
+ },
26
+
27
+ onKeydown(event) {
28
+ const {
29
+ code
30
+ } = event;
31
+
32
+ if (code === ENTER || code === SPACE) {
33
+ stopEvent(event);
34
+ this.toggleSelection();
35
+ }
36
+ }
37
+
38
+ }
39
+ };
40
+
41
+ /* script */
42
+ const __vue_script__ = script;
43
+
44
+ /* template */
45
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('li',{staticClass:"gl-new-dropdown-item",attrs:{"role":"option","tabindex":_vm.isFocused ? 0 : -1,"aria-selected":_vm.isSelected},on:{"click":_vm.toggleSelection,"keydown":_vm.onKeydown}},[_c('span',{staticClass:"dropdown-item"},[_c('gl-icon',{staticClass:"gl-mt-3 gl-align-self-start",class:['gl-new-dropdown-item-check-icon', { 'gl-visibility-hidden': !_vm.isSelected }],attrs:{"name":"mobile-issue-close","data-testid":"dropdown-item-checkbox"}}),_vm._v(" "),_c('span',{staticClass:"gl-new-dropdown-item-text-wrapper"},[_vm._t("default")],2)],1)])};
46
+ var __vue_staticRenderFns__ = [];
47
+
48
+ /* style */
49
+ const __vue_inject_styles__ = undefined;
50
+ /* scoped */
51
+ const __vue_scope_id__ = undefined;
52
+ /* module identifier */
53
+ const __vue_module_identifier__ = undefined;
54
+ /* functional template */
55
+ const __vue_is_functional_template__ = false;
56
+ /* style inject */
57
+
58
+ /* style inject SSR */
59
+
60
+ /* style inject shadow dom */
61
+
62
+
63
+
64
+ const __vue_component__ = __vue_normalize__(
65
+ { render: __vue_render__, staticRenderFns: __vue_staticRenderFns__ },
66
+ __vue_inject_styles__,
67
+ __vue_script__,
68
+ __vue_scope_id__,
69
+ __vue_is_functional_template__,
70
+ __vue_module_identifier__,
71
+ false,
72
+ undefined,
73
+ undefined,
74
+ undefined
75
+ );
76
+
77
+ export default __vue_component__;