@gitlab/ui 38.0.1 → 38.1.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 +7 -0
- package/README.md +1 -1
- package/dist/components/base/breadcrumb/breadcrumb.js +10 -5
- package/dist/components/base/{filtered_search/examples/filtered_search.single_unique.example.js → breadcrumb/breadcrumb_item.js} +32 -28
- package/dist/components/base/filtered_search/filtered_search.documentation.js +2 -66
- package/dist/components/base/filtered_search/filtered_search.js +38 -0
- package/dist/components/base/filtered_search/filtered_search_suggestion.documentation.js +2 -8
- package/dist/components/base/filtered_search/filtered_search_suggestion.js +4 -0
- package/dist/components/base/filtered_search/filtered_search_suggestion_list.documentation.js +2 -7
- package/dist/components/base/filtered_search/filtered_search_suggestion_list.js +4 -0
- package/dist/components/base/filtered_search/filtered_search_term.documentation.js +2 -44
- package/dist/components/base/filtered_search/filtered_search_term.js +37 -0
- package/dist/components/base/filtered_search/filtered_search_token.documentation.js +2 -31
- package/dist/components/base/filtered_search/filtered_search_token.js +49 -0
- package/dist/components/base/filtered_search/filtered_search_token_segment.documentation.js +2 -46
- package/dist/components/base/filtered_search/filtered_search_token_segment.js +48 -0
- package/dist/components/charts/series_label/series_label.js +6 -1
- package/documentation/documented_stories.js +6 -0
- package/package.json +9 -7
- package/src/components/base/breadcrumb/breadcrumb.spec.js +24 -10
- package/src/components/base/breadcrumb/breadcrumb.vue +11 -6
- package/src/components/base/breadcrumb/breadcrumb_item.spec.js +45 -0
- package/src/components/base/breadcrumb/breadcrumb_item.vue +43 -0
- package/src/components/base/filtered_search/filtered_search.documentation.js +0 -76
- package/src/components/base/filtered_search/filtered_search.md +3 -4
- package/src/components/base/filtered_search/filtered_search.stories.js +248 -13
- package/src/components/base/filtered_search/filtered_search.vue +45 -0
- package/src/components/base/filtered_search/filtered_search_suggestion.documentation.js +0 -6
- package/src/components/base/filtered_search/filtered_search_suggestion.md +1 -7
- package/src/components/base/filtered_search/filtered_search_suggestion.stories.js +26 -18
- package/src/components/base/filtered_search/filtered_search_suggestion.vue +5 -0
- package/src/components/base/filtered_search/filtered_search_suggestion_list.documentation.js +0 -5
- package/src/components/base/filtered_search/filtered_search_suggestion_list.md +1 -7
- package/src/components/base/filtered_search/filtered_search_suggestion_list.stories.js +33 -25
- package/src/components/base/filtered_search/filtered_search_suggestion_list.vue +5 -0
- package/src/components/base/filtered_search/filtered_search_term.documentation.js +0 -41
- package/src/components/base/filtered_search/filtered_search_term.md +0 -2
- package/src/components/base/filtered_search/filtered_search_term.stories.js +33 -26
- package/src/components/base/filtered_search/filtered_search_term.vue +54 -0
- package/src/components/base/filtered_search/filtered_search_token.documentation.js +0 -26
- package/src/components/base/filtered_search/filtered_search_token.md +1 -3
- package/src/components/base/filtered_search/filtered_search_token.stories.js +136 -132
- package/src/components/base/filtered_search/filtered_search_token.vue +63 -0
- package/src/components/base/filtered_search/filtered_search_token_segment.documentation.js +0 -43
- package/src/components/base/filtered_search/filtered_search_token_segment.md +0 -2
- package/src/components/base/filtered_search/filtered_search_token_segment.stories.js +86 -79
- package/src/components/base/filtered_search/filtered_search_token_segment.vue +42 -0
- package/src/components/base/form/form_radio/form_radio.spec.js +21 -8
- package/src/components/charts/series_label/series_label.stories.js +6 -3
- package/src/components/charts/series_label/series_label.vue +3 -0
- package/dist/components/base/filtered_search/examples/filtered_search.default.example.js +0 -422
- package/dist/components/base/filtered_search/examples/filtered_search.friendly.example.js +0 -423
- package/dist/components/base/filtered_search/examples/filtered_search.history.example.js +0 -91
- package/dist/components/base/filtered_search/examples/filtered_search.multi_select.example.js +0 -196
- package/dist/components/base/filtered_search/examples/index.js +0 -32
- package/src/components/base/filtered_search/examples/filtered_search.default.example.vue +0 -298
- package/src/components/base/filtered_search/examples/filtered_search.friendly.example.vue +0 -300
- package/src/components/base/filtered_search/examples/filtered_search.history.example.vue +0 -50
- package/src/components/base/filtered_search/examples/filtered_search.multi_select.example.vue +0 -132
- package/src/components/base/filtered_search/examples/filtered_search.single_unique.example.vue +0 -31
- package/src/components/base/filtered_search/examples/index.js +0 -38
|
@@ -17,6 +17,7 @@ const DEFAULT_OPERATORS = [{
|
|
|
17
17
|
description: 'is not'
|
|
18
18
|
}];
|
|
19
19
|
var script = {
|
|
20
|
+
name: 'GlFilteredSearchToken',
|
|
20
21
|
components: {
|
|
21
22
|
GlToken,
|
|
22
23
|
GlFilteredSearchTokenSegment
|
|
@@ -28,11 +29,19 @@ var script = {
|
|
|
28
29
|
required: false,
|
|
29
30
|
default: () => []
|
|
30
31
|
},
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Token configuration with available operators and options.
|
|
35
|
+
*/
|
|
31
36
|
config: {
|
|
32
37
|
type: Object,
|
|
33
38
|
required: false,
|
|
34
39
|
default: () => ({})
|
|
35
40
|
},
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Determines if the token is being edited or not.
|
|
44
|
+
*/
|
|
36
45
|
active: {
|
|
37
46
|
type: Boolean,
|
|
38
47
|
required: false,
|
|
@@ -43,6 +52,10 @@ var script = {
|
|
|
43
52
|
required: false,
|
|
44
53
|
default: () => []
|
|
45
54
|
},
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Current token value.
|
|
58
|
+
*/
|
|
46
59
|
value: {
|
|
47
60
|
type: Object,
|
|
48
61
|
required: false,
|
|
@@ -51,6 +64,10 @@ var script = {
|
|
|
51
64
|
data: ''
|
|
52
65
|
})
|
|
53
66
|
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Display operators' descriptions instead of their values (e.g., "is" instead of "=").
|
|
70
|
+
*/
|
|
54
71
|
showFriendlyText: {
|
|
55
72
|
type: Boolean,
|
|
56
73
|
required: false,
|
|
@@ -95,6 +112,12 @@ var script = {
|
|
|
95
112
|
deep: true,
|
|
96
113
|
|
|
97
114
|
handler(newValue) {
|
|
115
|
+
/**
|
|
116
|
+
* Emitted when the token changes its value.
|
|
117
|
+
*
|
|
118
|
+
* @event input
|
|
119
|
+
* @type {object} dataObj Object containing the update value.
|
|
120
|
+
*/
|
|
98
121
|
this.$emit('input', newValue);
|
|
99
122
|
}
|
|
100
123
|
|
|
@@ -109,6 +132,12 @@ var script = {
|
|
|
109
132
|
}
|
|
110
133
|
} else if (this.value.data === '') {
|
|
111
134
|
this.activeSegment = null;
|
|
135
|
+
/**
|
|
136
|
+
* Emitted when token is about to be destroyed.
|
|
137
|
+
*
|
|
138
|
+
* @event destroy
|
|
139
|
+
*/
|
|
140
|
+
|
|
112
141
|
this.$emit('destroy');
|
|
113
142
|
}
|
|
114
143
|
}
|
|
@@ -137,6 +166,11 @@ var script = {
|
|
|
137
166
|
this.activeSegment = segment;
|
|
138
167
|
|
|
139
168
|
if (!this.active) {
|
|
169
|
+
/**
|
|
170
|
+
* Emitted when this term token is clicked.
|
|
171
|
+
*
|
|
172
|
+
* @event activate
|
|
173
|
+
*/
|
|
140
174
|
this.$emit('activate');
|
|
141
175
|
}
|
|
142
176
|
},
|
|
@@ -153,6 +187,10 @@ var script = {
|
|
|
153
187
|
|
|
154
188
|
replaceWithTermIfEmpty() {
|
|
155
189
|
if (this.value.operator === '' && this.value.data === '') {
|
|
190
|
+
/**
|
|
191
|
+
* Emitted when this token is converted to another type
|
|
192
|
+
* @property {object} token Replacement token configuration
|
|
193
|
+
*/
|
|
156
194
|
this.$emit('replace', {
|
|
157
195
|
type: TERM_TOKEN_TYPE,
|
|
158
196
|
value: {
|
|
@@ -167,6 +205,11 @@ var script = {
|
|
|
167
205
|
|
|
168
206
|
if (newTokenConfig === this.config) {
|
|
169
207
|
this.$nextTick(() => {
|
|
208
|
+
/**
|
|
209
|
+
* Emitted when this term token will lose its focus.
|
|
210
|
+
*
|
|
211
|
+
* @event deactivate
|
|
212
|
+
*/
|
|
170
213
|
this.$emit('deactivate');
|
|
171
214
|
});
|
|
172
215
|
return;
|
|
@@ -230,6 +273,12 @@ var script = {
|
|
|
230
273
|
data: this.multiSelectValues.join(COMMA)
|
|
231
274
|
});
|
|
232
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Emitted when the token entry has been completed.
|
|
278
|
+
*
|
|
279
|
+
* @event complete
|
|
280
|
+
*/
|
|
281
|
+
|
|
233
282
|
|
|
234
283
|
this.$emit('complete');
|
|
235
284
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var filtered_search_token_segment = "
|
|
1
|
+
var filtered_search_token_segment = "The filtered search token segment is a component for managing token input either via free typing\nor by selecting item through dropdown list\n\n## Usage\n\nThis component is internal and is not intended to be used by `@gitlab/ui` users.\n\n## Internet Explorer 11\n\nThis component uses [`String.prototype.startsWith()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\nand [`String.prototype.endsWith()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith)\nunder the hood. Make sure those methods are polyfilled if you plan on using the component on IE11.\n\n> NOTE: These methods are already polyfilled in GitLab: [`app/assets/javascripts/commons/polyfills.js#L15-16`](https://gitlab.com/gitlab-org/gitlab/blob/dc60dee6ed6234dda9f032195577cd8fad9646d8/app/assets/javascripts/commons/polyfills.js#L15-16)\n";
|
|
2
2
|
|
|
3
3
|
var description = /*#__PURE__*/Object.freeze({
|
|
4
4
|
__proto__: null,
|
|
@@ -6,51 +6,7 @@ var description = /*#__PURE__*/Object.freeze({
|
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
var filtered_search_token_segment_documentation = {
|
|
9
|
-
description
|
|
10
|
-
bootstrapComponent: null,
|
|
11
|
-
propsInfo: {
|
|
12
|
-
active: {
|
|
13
|
-
additionalInfo: 'If this term token is currently active'
|
|
14
|
-
},
|
|
15
|
-
options: {
|
|
16
|
-
additionalInfo: ''
|
|
17
|
-
},
|
|
18
|
-
optionTextField: {
|
|
19
|
-
additionalInfo: ''
|
|
20
|
-
},
|
|
21
|
-
customInputKeydownHandler: {
|
|
22
|
-
additionalInfo: ''
|
|
23
|
-
},
|
|
24
|
-
value: {
|
|
25
|
-
additionalInfo: 'Current term value'
|
|
26
|
-
},
|
|
27
|
-
searchInputAttributes: {
|
|
28
|
-
additionalInfo: 'HTML attributes to add to the search input'
|
|
29
|
-
},
|
|
30
|
-
isLastToken: {
|
|
31
|
-
additionalInfo: 'If this is the last token'
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
events: [{
|
|
35
|
-
event: 'activate',
|
|
36
|
-
description: 'Emitted on mousedown event on the main component'
|
|
37
|
-
}, {
|
|
38
|
-
event: 'backspace',
|
|
39
|
-
description: 'Emitted when Backspace is pressed and the value is empty'
|
|
40
|
-
}, {
|
|
41
|
-
event: 'complete',
|
|
42
|
-
description: 'Emitted when suggestion is selected from the suggestion list'
|
|
43
|
-
}, {
|
|
44
|
-
event: 'submit',
|
|
45
|
-
description: 'Emitted when Enter is pressed and no suggestion is selected'
|
|
46
|
-
}, {
|
|
47
|
-
event: 'split',
|
|
48
|
-
args: [{
|
|
49
|
-
arg: 'newStrings',
|
|
50
|
-
description: '(Array of strings) New strings to be converted into term tokens'
|
|
51
|
-
}],
|
|
52
|
-
description: 'Emitted when Space appears in token segment value'
|
|
53
|
-
}]
|
|
9
|
+
description
|
|
54
10
|
};
|
|
55
11
|
|
|
56
12
|
export default filtered_search_token_segment_documentation;
|
|
@@ -7,6 +7,7 @@ import { splitOnQuotes, wrapTokenInQuotes } from './filtered_search_utils';
|
|
|
7
7
|
import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
|
|
8
8
|
|
|
9
9
|
var script = {
|
|
10
|
+
name: 'GlFilteredSearchTokenSegment',
|
|
10
11
|
components: {
|
|
11
12
|
Portal,
|
|
12
13
|
GlFilteredSearchSuggestionList,
|
|
@@ -15,6 +16,9 @@ var script = {
|
|
|
15
16
|
inject: ['portalName', 'alignSuggestions'],
|
|
16
17
|
inheritAttrs: false,
|
|
17
18
|
props: {
|
|
19
|
+
/**
|
|
20
|
+
* If this token segment is currently being edited.
|
|
21
|
+
*/
|
|
18
22
|
active: {
|
|
19
23
|
type: Boolean,
|
|
20
24
|
required: false,
|
|
@@ -45,15 +49,27 @@ var script = {
|
|
|
45
49
|
required: false,
|
|
46
50
|
default: () => () => false
|
|
47
51
|
},
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Current term value
|
|
55
|
+
*/
|
|
48
56
|
value: {
|
|
49
57
|
required: true,
|
|
50
58
|
validator: () => true
|
|
51
59
|
},
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* HTML attributes to add to the search input
|
|
63
|
+
*/
|
|
52
64
|
searchInputAttributes: {
|
|
53
65
|
type: Object,
|
|
54
66
|
required: false,
|
|
55
67
|
default: () => ({})
|
|
56
68
|
},
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* If this is the last token
|
|
72
|
+
*/
|
|
57
73
|
isLastToken: {
|
|
58
74
|
type: Boolean,
|
|
59
75
|
required: false,
|
|
@@ -91,6 +107,11 @@ var script = {
|
|
|
91
107
|
set(v) {
|
|
92
108
|
var _this$getMatchingOpti, _this$getMatchingOpti2;
|
|
93
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Emitted when this token segment's value changes.
|
|
112
|
+
*
|
|
113
|
+
* @type {object} option The current option.
|
|
114
|
+
*/
|
|
94
115
|
this.$emit('input', (_this$getMatchingOpti = (_this$getMatchingOpti2 = this.getMatchingOptionForInputValue(v)) === null || _this$getMatchingOpti2 === void 0 ? void 0 : _this$getMatchingOpti2.value) !== null && _this$getMatchingOpti !== void 0 ? _this$getMatchingOpti : v);
|
|
95
116
|
}
|
|
96
117
|
|
|
@@ -152,6 +173,10 @@ var script = {
|
|
|
152
173
|
this.$emit('input', (_this$getMatchingOpti3 = (_this$getMatchingOpti4 = this.getMatchingOptionForInputValue(firstWord)) === null || _this$getMatchingOpti4 === void 0 ? void 0 : _this$getMatchingOpti4.value) !== null && _this$getMatchingOpti3 !== void 0 ? _this$getMatchingOpti3 : firstWord);
|
|
153
174
|
|
|
154
175
|
if (otherWords.length) {
|
|
176
|
+
/**
|
|
177
|
+
* Emitted when Space appears in token segment value
|
|
178
|
+
* @property {array|string} newStrings New strings to be converted into term tokens
|
|
179
|
+
*/
|
|
155
180
|
this.$emit('split', otherWords);
|
|
156
181
|
}
|
|
157
182
|
}
|
|
@@ -160,6 +185,9 @@ var script = {
|
|
|
160
185
|
methods: {
|
|
161
186
|
emitIfInactive(e) {
|
|
162
187
|
if (!this.active) {
|
|
188
|
+
/**
|
|
189
|
+
* Emitted on mousedown event on the main component.
|
|
190
|
+
*/
|
|
163
191
|
this.$emit('activate');
|
|
164
192
|
e.preventDefault();
|
|
165
193
|
}
|
|
@@ -208,6 +236,12 @@ var script = {
|
|
|
208
236
|
|
|
209
237
|
applySuggestion(suggestedValue) {
|
|
210
238
|
const formattedSuggestedValue = wrapTokenInQuotes(suggestedValue);
|
|
239
|
+
/**
|
|
240
|
+
* Emitted when autocomplete entry is selected.
|
|
241
|
+
*
|
|
242
|
+
* @type {string} value The selected value.
|
|
243
|
+
*/
|
|
244
|
+
|
|
211
245
|
this.$emit('select', formattedSuggestedValue);
|
|
212
246
|
|
|
213
247
|
if (!this.multiSelect) {
|
|
@@ -228,6 +262,10 @@ var script = {
|
|
|
228
262
|
if (key === 'Backspace') {
|
|
229
263
|
if (this.inputValue === '') {
|
|
230
264
|
e.preventDefault();
|
|
265
|
+
/**
|
|
266
|
+
* Emitted when Backspace is pressed and the value is empty
|
|
267
|
+
*/
|
|
268
|
+
|
|
231
269
|
this.$emit('backspace');
|
|
232
270
|
}
|
|
233
271
|
|
|
@@ -241,6 +279,9 @@ var script = {
|
|
|
241
279
|
if (suggestedValue != null) {
|
|
242
280
|
this.applySuggestion(suggestedValue);
|
|
243
281
|
} else {
|
|
282
|
+
/**
|
|
283
|
+
* Emitted when Enter is pressed and no suggestion is selected
|
|
284
|
+
*/
|
|
244
285
|
this.$emit('submit');
|
|
245
286
|
}
|
|
246
287
|
},
|
|
@@ -252,6 +293,10 @@ var script = {
|
|
|
252
293
|
},
|
|
253
294
|
Escape: () => {
|
|
254
295
|
e.preventDefault();
|
|
296
|
+
/**
|
|
297
|
+
* Emitted when suggestion is selected from the suggestion list
|
|
298
|
+
*/
|
|
299
|
+
|
|
255
300
|
this.$emit('complete');
|
|
256
301
|
}
|
|
257
302
|
};
|
|
@@ -282,6 +327,9 @@ var script = {
|
|
|
282
327
|
if (this.multiSelect) {
|
|
283
328
|
this.$emit('complete');
|
|
284
329
|
} else if (this.active) {
|
|
330
|
+
/**
|
|
331
|
+
* Emitted when this term token will lose its focus.
|
|
332
|
+
*/
|
|
285
333
|
this.$emit('deactivate');
|
|
286
334
|
}
|
|
287
335
|
}
|
|
@@ -91,6 +91,12 @@ export const setupStorybookReadme = () =>
|
|
|
91
91
|
'GlFormCheckbox',
|
|
92
92
|
'GlAccordion',
|
|
93
93
|
'GlAccordionItem',
|
|
94
|
+
'GlFilteredSearch',
|
|
95
|
+
'GlFilteredSearchSuggestion',
|
|
96
|
+
'GlFilteredSearchSuggestionList',
|
|
97
|
+
'GlFilteredSearchTerm',
|
|
98
|
+
'GlFilteredSearchToken',
|
|
99
|
+
'GlFilteredSearchTokenSegment',
|
|
94
100
|
'GlIntersperse',
|
|
95
101
|
'GlFormSelect',
|
|
96
102
|
'GlDaterangePicker',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/ui",
|
|
3
|
-
"version": "38.0
|
|
3
|
+
"version": "38.1.0",
|
|
4
4
|
"description": "GitLab UI Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,9 +37,10 @@
|
|
|
37
37
|
"test:unit": "NODE_ENV=test jest --testPathIgnorePatterns storyshots.spec.js",
|
|
38
38
|
"test:unit:watch": "yarn test:unit --watch --notify",
|
|
39
39
|
"test:unit:debug": "NODE_ENV=test node --inspect node_modules/.bin/jest --testPathIgnorePatterns storyshot.spec.js --watch --runInBand",
|
|
40
|
-
"test:visual": "
|
|
40
|
+
"test:visual": "./bin/run-visual-tests.sh 'jest ./tests/storyshots.spec.js'",
|
|
41
41
|
"test:visual:minimal": "node ./bin/run_minimal_visual_tests.js",
|
|
42
|
-
"test:visual:update": "
|
|
42
|
+
"test:visual:update": "./bin/run-visual-tests.sh 'JEST_IMAGE_SNAPSHOT_TRACK_OBSOLETE=1 jest ./tests/storyshots.spec.js --updateSnapshot'",
|
|
43
|
+
"test:visual:internal": "NODE_ENV=test IS_VISUAL_TEST=true start-test http-get://localhost:9001",
|
|
43
44
|
"prettier": "prettier --check '**/*.{js,vue}'",
|
|
44
45
|
"prettier:fix": "prettier --write '**/*.{js,vue}'",
|
|
45
46
|
"eslint": "eslint --max-warnings 0 --ext .js,.vue .",
|
|
@@ -77,6 +78,8 @@
|
|
|
77
78
|
},
|
|
78
79
|
"resolutions": {
|
|
79
80
|
"chokidar": "^3.5.2",
|
|
81
|
+
"node-gyp": "^9.0.0",
|
|
82
|
+
"node-sass": "^6.0.0",
|
|
80
83
|
"sane": "^5.0.1"
|
|
81
84
|
},
|
|
82
85
|
"devDependencies": {
|
|
@@ -118,11 +121,9 @@
|
|
|
118
121
|
"jest": "^26.6.3",
|
|
119
122
|
"jest-raw-loader": "^1.0.1",
|
|
120
123
|
"jest-serializer-vue": "^2.0.2",
|
|
121
|
-
"markdown-loader-jest": "^0.1.1",
|
|
122
124
|
"markdownlint-cli": "^0.29.0",
|
|
123
125
|
"mockdate": "^2.0.5",
|
|
124
|
-
"node-sass": "^
|
|
125
|
-
"node-sass-magic-importer": "^5.3.2",
|
|
126
|
+
"node-sass": "^6.0.0",
|
|
126
127
|
"npm-run-all": "^4.1.5",
|
|
127
128
|
"pikaday": "^1.8.0",
|
|
128
129
|
"plop": "^2.5.4",
|
|
@@ -139,8 +140,9 @@
|
|
|
139
140
|
"rollup-plugin-string": "^3.0.0",
|
|
140
141
|
"rollup-plugin-svg": "^2.0.0",
|
|
141
142
|
"rollup-plugin-vue": "^5.1.6",
|
|
143
|
+
"sass": "^1.49.9",
|
|
142
144
|
"sass-export": "^1.0.3",
|
|
143
|
-
"sass-loader": "^
|
|
145
|
+
"sass-loader": "^10.2.0",
|
|
144
146
|
"sass-true": "^5.0.0",
|
|
145
147
|
"start-server-and-test": "^1.10.6",
|
|
146
148
|
"storybook-readme": "5.0.9",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { shallowMount } from '@vue/test-utils';
|
|
2
|
-
import {
|
|
2
|
+
import { nextTick } from 'vue';
|
|
3
3
|
import Breadcrumb, { COLLAPSE_AT_SIZE } from './breadcrumb.vue';
|
|
4
|
+
import GlBreadcrumbItem from './breadcrumb_item.vue';
|
|
4
5
|
import { createMockDirective } from '~helpers/vue_mock_directive';
|
|
5
6
|
|
|
6
|
-
describe('
|
|
7
|
+
describe('Breadcrumb component', () => {
|
|
7
8
|
let wrapper;
|
|
8
9
|
|
|
9
10
|
const items = [
|
|
@@ -25,7 +26,7 @@ describe('Broadcast message component', () => {
|
|
|
25
26
|
|
|
26
27
|
const findAvatarSlot = () => wrapper.find('[data-testid="avatar-slot"]');
|
|
27
28
|
const findSeparatorSlot = () => wrapper.find('[data-testid="separator-slot"]');
|
|
28
|
-
const findBreadcrumbItems = () => wrapper.findAllComponents(
|
|
29
|
+
const findBreadcrumbItems = () => wrapper.findAllComponents(GlBreadcrumbItem);
|
|
29
30
|
const findAllSeparators = () => wrapper.findAll('[data-testid="separator"]');
|
|
30
31
|
const findCollapsedListExpander = () => wrapper.find('[data-testid="collapsed-expander"]');
|
|
31
32
|
const findExpanderSeparator = () => wrapper.find('[data-testid="expander-separator"]');
|
|
@@ -42,6 +43,9 @@ describe('Broadcast message component', () => {
|
|
|
42
43
|
separator: '<div data-testid="separator-slot"></div>',
|
|
43
44
|
},
|
|
44
45
|
directives: { GlTooltip: createMockDirective('gl-tooltip') },
|
|
46
|
+
stubs: {
|
|
47
|
+
GlBreadcrumbItem,
|
|
48
|
+
},
|
|
45
49
|
});
|
|
46
50
|
|
|
47
51
|
wrapper.vm.$refs.firstItem = [
|
|
@@ -86,21 +90,31 @@ describe('Broadcast message component', () => {
|
|
|
86
90
|
});
|
|
87
91
|
|
|
88
92
|
describe('bindings', () => {
|
|
89
|
-
|
|
93
|
+
beforeEach(() => {
|
|
90
94
|
createComponent();
|
|
95
|
+
});
|
|
91
96
|
|
|
92
|
-
|
|
97
|
+
it('first breadcrumb has text, href && ariaCurrent=`false` bound', () => {
|
|
98
|
+
expect(findBreadcrumbItems().at(0).props()).toMatchObject({
|
|
93
99
|
text: items[0].text,
|
|
94
100
|
href: items[0].href,
|
|
101
|
+
ariaCurrent: false,
|
|
95
102
|
});
|
|
96
103
|
});
|
|
97
104
|
|
|
98
|
-
it('second breadcrumb has text
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
expect(findBreadcrumbItems().at(1).attributes()).toMatchObject({
|
|
105
|
+
it('second breadcrumb has text, to && ariaCurrent=`false` bound', () => {
|
|
106
|
+
expect(findBreadcrumbItems().at(1).props()).toMatchObject({
|
|
102
107
|
text: items[1].text,
|
|
103
108
|
to: items[1].to,
|
|
109
|
+
ariaCurrent: false,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('last breadcrumb has text, to && ariaCurrent=`page` bound', () => {
|
|
114
|
+
expect(findBreadcrumbItems().at(2).props()).toMatchObject({
|
|
115
|
+
text: items[2].text,
|
|
116
|
+
href: items[2].href,
|
|
117
|
+
ariaCurrent: 'page',
|
|
104
118
|
});
|
|
105
119
|
});
|
|
106
120
|
});
|
|
@@ -139,7 +153,7 @@ describe('Broadcast message component', () => {
|
|
|
139
153
|
|
|
140
154
|
it('should expand the list on expander click', async () => {
|
|
141
155
|
findCollapsedListExpander().vm.$emit('click');
|
|
142
|
-
await
|
|
156
|
+
await nextTick();
|
|
143
157
|
expect(findHiddenBreadcrumbItems()).toHaveLength(0);
|
|
144
158
|
expect(findVisibleBreadcrumbItems()).toHaveLength(items.length + extraItems.length);
|
|
145
159
|
});
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { BBreadcrumb
|
|
2
|
+
import { BBreadcrumb } from 'bootstrap-vue';
|
|
3
3
|
import GlIcon from '../icon/icon.vue';
|
|
4
4
|
import GlButton from '../button/button.vue';
|
|
5
5
|
import { GlTooltipDirective } from '../../../directives/tooltip';
|
|
6
|
+
import GlBreadcrumbItem from './breadcrumb_item.vue';
|
|
6
7
|
|
|
7
8
|
export const COLLAPSE_AT_SIZE = 4;
|
|
8
9
|
|
|
9
10
|
export default {
|
|
10
11
|
components: {
|
|
11
12
|
BBreadcrumb,
|
|
12
|
-
BBreadcrumbItem,
|
|
13
13
|
GlIcon,
|
|
14
14
|
GlButton,
|
|
15
|
+
GlBreadcrumbItem,
|
|
15
16
|
},
|
|
16
17
|
directives: {
|
|
17
18
|
GlTooltip: GlTooltipDirective,
|
|
@@ -58,11 +59,12 @@ export default {
|
|
|
58
59
|
},
|
|
59
60
|
expandBreadcrumbs() {
|
|
60
61
|
this.isListCollapsed = false;
|
|
62
|
+
|
|
61
63
|
try {
|
|
62
64
|
this.$refs.firstItem[0].querySelector('a').focus();
|
|
63
65
|
} catch (e) {
|
|
64
66
|
/* eslint-disable-next-line no-console */
|
|
65
|
-
console.error(`Failed to set focus on the
|
|
67
|
+
console.error(`Failed to set focus on the first breadcrumb item.`);
|
|
66
68
|
}
|
|
67
69
|
},
|
|
68
70
|
showCollapsedBreadcrumbsExpander(index) {
|
|
@@ -73,6 +75,9 @@ export default {
|
|
|
73
75
|
this.hasCollapsible && this.isListCollapsed && !this.nonCollapsibleIndices.includes(index)
|
|
74
76
|
);
|
|
75
77
|
},
|
|
78
|
+
getAriaCurrentAttr(index) {
|
|
79
|
+
return this.isLastItem(index) ? 'page' : false;
|
|
80
|
+
},
|
|
76
81
|
},
|
|
77
82
|
};
|
|
78
83
|
</script>
|
|
@@ -82,14 +87,14 @@ export default {
|
|
|
82
87
|
<slot name="avatar"></slot>
|
|
83
88
|
<b-breadcrumb class="gl-breadcrumb-list" v-bind="$attrs" v-on="$listeners">
|
|
84
89
|
<template v-for="(item, index) in items">
|
|
85
|
-
<
|
|
90
|
+
<gl-breadcrumb-item
|
|
86
91
|
:key="index"
|
|
87
92
|
:ref="isFirstItem(index) ? 'firstItem' : null"
|
|
88
|
-
class="gl-breadcrumb-item"
|
|
89
93
|
:text="item.text"
|
|
90
94
|
:href="item.href"
|
|
91
95
|
:to="item.to"
|
|
92
96
|
:class="{ 'gl-display-none': isItemCollapsed(index) }"
|
|
97
|
+
:aria-current="getAriaCurrentAttr(index)"
|
|
93
98
|
>
|
|
94
99
|
<span>{{ item.text }}</span>
|
|
95
100
|
<span
|
|
@@ -103,7 +108,7 @@ export default {
|
|
|
103
108
|
<gl-icon name="chevron-right" />
|
|
104
109
|
</slot>
|
|
105
110
|
</span>
|
|
106
|
-
</
|
|
111
|
+
</gl-breadcrumb-item>
|
|
107
112
|
|
|
108
113
|
<template v-if="showCollapsedBreadcrumbsExpander(index)">
|
|
109
114
|
<!-- eslint-disable-next-line vue/valid-v-for -->
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import { BLink } from 'bootstrap-vue';
|
|
3
|
+
import BreadcrumbItem from './breadcrumb_item.vue';
|
|
4
|
+
|
|
5
|
+
describe('Breadcrumb Item Component', () => {
|
|
6
|
+
let wrapper;
|
|
7
|
+
|
|
8
|
+
const item = { href: 'http://about.gitlab.com', to: { name: 'about' }, ariaCurrent: 'page' };
|
|
9
|
+
|
|
10
|
+
const findBLink = () => wrapper.findComponent(BLink);
|
|
11
|
+
const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]');
|
|
12
|
+
|
|
13
|
+
const createComponent = () => {
|
|
14
|
+
wrapper = shallowMount(BreadcrumbItem, {
|
|
15
|
+
propsData: {
|
|
16
|
+
href: item.href,
|
|
17
|
+
to: item.to,
|
|
18
|
+
ariaCurrent: item.ariaCurrent,
|
|
19
|
+
},
|
|
20
|
+
slots: {
|
|
21
|
+
default: '<div data-testid="default-slot"></div>',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe('slots', () => {
|
|
27
|
+
it('renders provided content to default slot', () => {
|
|
28
|
+
createComponent();
|
|
29
|
+
|
|
30
|
+
expect(findDefaultSlot().exists()).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('bindings', () => {
|
|
35
|
+
it('passes provided props down to BLink', () => {
|
|
36
|
+
createComponent();
|
|
37
|
+
|
|
38
|
+
const bLink = findBLink();
|
|
39
|
+
|
|
40
|
+
expect(bLink.props('to')).toMatchObject(item.to);
|
|
41
|
+
expect(bLink.props('href')).toBe(item.href);
|
|
42
|
+
expect(bLink.attributes('aria-current')).toBe(item.ariaCurrent);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { BLink } from 'bootstrap-vue';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
components: {
|
|
6
|
+
BLink,
|
|
7
|
+
},
|
|
8
|
+
inheritAttrs: false,
|
|
9
|
+
props: {
|
|
10
|
+
text: {
|
|
11
|
+
type: String,
|
|
12
|
+
required: false,
|
|
13
|
+
default: null,
|
|
14
|
+
},
|
|
15
|
+
to: {
|
|
16
|
+
type: [String, Object],
|
|
17
|
+
required: false,
|
|
18
|
+
default: null,
|
|
19
|
+
},
|
|
20
|
+
href: {
|
|
21
|
+
type: String,
|
|
22
|
+
required: false,
|
|
23
|
+
default: null,
|
|
24
|
+
},
|
|
25
|
+
ariaCurrent: {
|
|
26
|
+
type: [String, Boolean],
|
|
27
|
+
required: false,
|
|
28
|
+
default: false,
|
|
29
|
+
validator(value) {
|
|
30
|
+
return [false, 'page'].indexOf(value) !== -1;
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<li class="gl-breadcrumb-item">
|
|
39
|
+
<b-link :href="href" :to="to" :aria-current="ariaCurrent">
|
|
40
|
+
<slot>{{ text }}</slot>
|
|
41
|
+
</b-link>
|
|
42
|
+
</li>
|
|
43
|
+
</template>
|