@gitlab/ui 43.17.1 → 43.19.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 +15 -0
- package/dist/components/base/filtered_search/filtered_search.js +18 -5
- package/dist/components/base/filtered_search/filtered_search_term.js +6 -1
- package/dist/components/base/filtered_search/filtered_search_token.js +28 -8
- package/dist/components/base/filtered_search/filtered_search_token_segment.js +10 -4
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/utility_classes.css +1 -1
- package/dist/utility_classes.css.map +1 -1
- package/dist/utils/constants.js +2 -1
- package/package.json +13 -13
- package/src/components/base/filtered_search/filtered_search.spec.js +53 -2
- package/src/components/base/filtered_search/filtered_search.stories.js +13 -2
- package/src/components/base/filtered_search/filtered_search.vue +24 -6
- package/src/components/base/filtered_search/filtered_search_term.spec.js +23 -3
- package/src/components/base/filtered_search/filtered_search_term.vue +8 -0
- package/src/components/base/filtered_search/filtered_search_token.scss +10 -8
- package/src/components/base/filtered_search/filtered_search_token.spec.js +74 -21
- package/src/components/base/filtered_search/filtered_search_token.vue +25 -4
- package/src/components/base/filtered_search/filtered_search_token_segment.spec.js +18 -0
- package/src/components/base/filtered_search/filtered_search_token_segment.vue +10 -3
- package/src/scss/utilities.scss +104 -92
- package/src/scss/utility-mixins/position.scss +6 -0
- package/src/utils/constants.js +2 -0
package/dist/utils/constants.js
CHANGED
|
@@ -9,6 +9,7 @@ function appendDefaultOption(options) {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const COMMA = ',';
|
|
12
|
+
const LEFT_MOUSE_BUTTON = 0;
|
|
12
13
|
const glThemes = ['indigo', 'blue', 'light-blue', 'green', 'red', 'light-red'];
|
|
13
14
|
const variantOptions = {
|
|
14
15
|
primary: 'primary',
|
|
@@ -244,4 +245,4 @@ const loadingIconSizes = {
|
|
|
244
245
|
'xl (64x64)': 'xl'
|
|
245
246
|
};
|
|
246
247
|
|
|
247
|
-
export { COMMA, alertVariantIconMap, alertVariantOptions, alignOptions, avatarShapeOptions, avatarSizeOptions, avatarsInlineSizeOptions, badgeForButtonOptions, badgeSizeOptions, badgeVariantOptions, bannerVariants, buttonCategoryOptions, buttonSizeOptions, buttonSizeOptionsMap, buttonVariantOptions, colorThemes, columnOptions, defaultDateFormat, drawerVariants, dropdownVariantOptions, focusableTags, formInputSizes, formStateOptions, glThemes, iconSizeOptions, keyboard, labelColorOptions, labelSizeOptions, loadingIconSizes, maxZIndex, modalButtonDefaults, modalSizeOptions, popoverPlacements, resizeDebounceTime, tabsButtonDefaults, targetOptions, toggleLabelPosition, tokenVariants, tooltipActionEvents, tooltipDelay, tooltipPlacements, triggerVariantOptions, truncateOptions, variantCssColorMap, variantOptions, variantOptionsWithNoDefault, viewModeOptions };
|
|
248
|
+
export { COMMA, LEFT_MOUSE_BUTTON, alertVariantIconMap, alertVariantOptions, alignOptions, avatarShapeOptions, avatarSizeOptions, avatarsInlineSizeOptions, badgeForButtonOptions, badgeSizeOptions, badgeVariantOptions, bannerVariants, buttonCategoryOptions, buttonSizeOptions, buttonSizeOptionsMap, buttonVariantOptions, colorThemes, columnOptions, defaultDateFormat, drawerVariants, dropdownVariantOptions, focusableTags, formInputSizes, formStateOptions, glThemes, iconSizeOptions, keyboard, labelColorOptions, labelSizeOptions, loadingIconSizes, maxZIndex, modalButtonDefaults, modalSizeOptions, popoverPlacements, resizeDebounceTime, tabsButtonDefaults, targetOptions, toggleLabelPosition, tokenVariants, tooltipActionEvents, tooltipDelay, tooltipPlacements, triggerVariantOptions, truncateOptions, variantCssColorMap, variantOptions, variantOptionsWithNoDefault, viewModeOptions };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/ui",
|
|
3
|
-
"version": "43.
|
|
3
|
+
"version": "43.19.0",
|
|
4
4
|
"description": "GitLab UI Components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -77,22 +77,22 @@
|
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@arkweid/lefthook": "0.7.7",
|
|
80
|
-
"@babel/core": "^7.
|
|
81
|
-
"@babel/preset-env": "^7.
|
|
80
|
+
"@babel/core": "^7.19.1",
|
|
81
|
+
"@babel/preset-env": "^7.19.1",
|
|
82
82
|
"@gitlab/eslint-plugin": "17.0.0",
|
|
83
83
|
"@gitlab/stylelint-config": "4.1.0",
|
|
84
84
|
"@gitlab/svgs": "3.3.0",
|
|
85
85
|
"@rollup/plugin-commonjs": "^11.1.0",
|
|
86
86
|
"@rollup/plugin-node-resolve": "^7.1.3",
|
|
87
87
|
"@rollup/plugin-replace": "^2.3.2",
|
|
88
|
-
"@storybook/addon-a11y": "6.5.
|
|
89
|
-
"@storybook/addon-docs": "6.5.
|
|
90
|
-
"@storybook/addon-essentials": "6.5.
|
|
91
|
-
"@storybook/addon-storyshots": "6.5.
|
|
92
|
-
"@storybook/addon-storyshots-puppeteer": "6.5.
|
|
93
|
-
"@storybook/addon-viewport": "6.5.
|
|
94
|
-
"@storybook/theming": "6.5.
|
|
95
|
-
"@storybook/vue": "6.5.
|
|
88
|
+
"@storybook/addon-a11y": "6.5.12",
|
|
89
|
+
"@storybook/addon-docs": "6.5.12",
|
|
90
|
+
"@storybook/addon-essentials": "6.5.12",
|
|
91
|
+
"@storybook/addon-storyshots": "6.5.12",
|
|
92
|
+
"@storybook/addon-storyshots-puppeteer": "6.5.12",
|
|
93
|
+
"@storybook/addon-viewport": "6.5.12",
|
|
94
|
+
"@storybook/theming": "6.5.12",
|
|
95
|
+
"@storybook/vue": "6.5.12",
|
|
96
96
|
"@vue/test-utils": "1.3.0",
|
|
97
97
|
"@vue/vue2-jest": "29.0.0",
|
|
98
98
|
"autoprefixer": "^9.7.6",
|
|
@@ -102,9 +102,9 @@
|
|
|
102
102
|
"babel-plugin-require-context-hook": "^1.0.0",
|
|
103
103
|
"babel-preset-vue": "^2.0.2",
|
|
104
104
|
"bootstrap": "4.5.3",
|
|
105
|
-
"cypress": "^10.
|
|
105
|
+
"cypress": "^10.8.0",
|
|
106
106
|
"emoji-regex": "^10.0.0",
|
|
107
|
-
"eslint": "8.23.
|
|
107
|
+
"eslint": "8.23.1",
|
|
108
108
|
"eslint-import-resolver-jest": "3.0.2",
|
|
109
109
|
"eslint-plugin-cypress": "2.12.1",
|
|
110
110
|
"eslint-plugin-storybook": "0.6.4",
|
|
@@ -18,9 +18,11 @@ const FakeToken = {
|
|
|
18
18
|
|
|
19
19
|
Vue.directive('GlTooltip', () => {});
|
|
20
20
|
|
|
21
|
+
let wrapper;
|
|
22
|
+
|
|
23
|
+
const findFilteredSearchInput = () => wrapper.find('[data-testid="filtered-search-input"]');
|
|
21
24
|
const stripId = (token) => (typeof token === 'object' ? omit(token, 'id') : token);
|
|
22
25
|
|
|
23
|
-
let wrapper;
|
|
24
26
|
describe('Filtered search', () => {
|
|
25
27
|
const defaultProps = {
|
|
26
28
|
availableTokens: [{ type: 'faketoken', token: FakeToken }],
|
|
@@ -41,6 +43,10 @@ describe('Filtered search', () => {
|
|
|
41
43
|
});
|
|
42
44
|
};
|
|
43
45
|
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
wrapper = null;
|
|
48
|
+
});
|
|
49
|
+
|
|
44
50
|
describe('value manipulation', () => {
|
|
45
51
|
it('creates term when empty', () => {
|
|
46
52
|
createComponent();
|
|
@@ -284,7 +290,7 @@ describe('Filtered search', () => {
|
|
|
284
290
|
|
|
285
291
|
await nextTick();
|
|
286
292
|
|
|
287
|
-
wrapper.
|
|
293
|
+
wrapper.findAll(`.gl-filtered-search-item`).wrappers.forEach((searchTermWrapper) => {
|
|
288
294
|
expect(searchTermWrapper.props('active')).toBe(false);
|
|
289
295
|
});
|
|
290
296
|
});
|
|
@@ -483,6 +489,51 @@ describe('Filtered search', () => {
|
|
|
483
489
|
{ type: 'filtered-search-term', value: { data: '' } },
|
|
484
490
|
]);
|
|
485
491
|
});
|
|
492
|
+
|
|
493
|
+
it('the search input is enabled by default', () => {
|
|
494
|
+
createComponent();
|
|
495
|
+
|
|
496
|
+
expect(findFilteredSearchInput().attributes('disabled')).toBe(undefined);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
describe('view-only state', () => {
|
|
500
|
+
const createViewOnlyComponent = (viewOnly) =>
|
|
501
|
+
createComponent({
|
|
502
|
+
value: ['one', { type: 'faketoken', value: '' }],
|
|
503
|
+
viewOnly,
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it.each([true, false])(
|
|
507
|
+
'passes the value of viewOnly to the search term when view-only is %s',
|
|
508
|
+
(viewOnly) => {
|
|
509
|
+
createViewOnlyComponent(viewOnly);
|
|
510
|
+
|
|
511
|
+
expect(wrapper.findComponent(GlFilteredSearchTerm).props('viewOnly')).toBe(viewOnly);
|
|
512
|
+
}
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
describe('when view-only is true', () => {
|
|
516
|
+
beforeEach(() => {
|
|
517
|
+
createViewOnlyComponent(true);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('disables the search input', () => {
|
|
521
|
+
expect(findFilteredSearchInput().attributes('disabled')).toBe('disabled');
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('prevents tokens from activating', async () => {
|
|
525
|
+
await wrapper.findComponent(FakeToken).vm.$emit('activate');
|
|
526
|
+
|
|
527
|
+
wrapper.findAll(`.gl-filtered-search-item`).wrappers.forEach((searchTermWrapper) => {
|
|
528
|
+
expect(searchTermWrapper.props('active')).toBe(false);
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('does not apply the last token class', async () => {
|
|
533
|
+
expect(wrapper.find('.gl-filtered-search-last-item').exists()).toBe(false);
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
});
|
|
486
537
|
});
|
|
487
538
|
|
|
488
539
|
describe('Filtered search integration tests', () => {
|
|
@@ -186,7 +186,7 @@ const LabelToken = {
|
|
|
186
186
|
GlToken,
|
|
187
187
|
GlDropdownDivider,
|
|
188
188
|
},
|
|
189
|
-
props: ['value', 'active'],
|
|
189
|
+
props: ['value', 'active', 'viewOnly'],
|
|
190
190
|
inheritAttrs: false,
|
|
191
191
|
data() {
|
|
192
192
|
return {
|
|
@@ -251,7 +251,7 @@ const LabelToken = {
|
|
|
251
251
|
v-on="$listeners"
|
|
252
252
|
>
|
|
253
253
|
<template #view-token="{ inputValue, cssClasses, listeners }">
|
|
254
|
-
<gl-token variant="search-value" :class="cssClasses" :style="containerStyle" v-on="listeners">
|
|
254
|
+
<gl-token variant="search-value" :view-only="viewOnly" :class="cssClasses" :style="containerStyle" v-on="listeners">
|
|
255
255
|
{{ activeLabel ? activeLabel.title : inputValue }}
|
|
256
256
|
</gl-token>
|
|
257
257
|
</template>
|
|
@@ -323,6 +323,17 @@ export const Default = () => ({
|
|
|
323
323
|
template: `<gl-filtered-search :available-tokens="tokens" :value="value" />`,
|
|
324
324
|
});
|
|
325
325
|
|
|
326
|
+
export const ViewOnly = () => ({
|
|
327
|
+
data() {
|
|
328
|
+
return {
|
|
329
|
+
tokens,
|
|
330
|
+
value: [{ type: 'author', value: { data: 'epsilon', operator: '=' } }, 'raw text'],
|
|
331
|
+
};
|
|
332
|
+
},
|
|
333
|
+
components,
|
|
334
|
+
template: `<gl-filtered-search view-only :available-tokens="tokens" :value="value" />`,
|
|
335
|
+
});
|
|
336
|
+
|
|
326
337
|
export const WithHistoryItems = () => ({
|
|
327
338
|
components,
|
|
328
339
|
data() {
|
|
@@ -113,6 +113,11 @@ export default {
|
|
|
113
113
|
required: false,
|
|
114
114
|
default: () => ({}),
|
|
115
115
|
},
|
|
116
|
+
viewOnly: {
|
|
117
|
+
type: Boolean,
|
|
118
|
+
required: false,
|
|
119
|
+
default: false,
|
|
120
|
+
},
|
|
116
121
|
},
|
|
117
122
|
data() {
|
|
118
123
|
return {
|
|
@@ -162,7 +167,7 @@ export default {
|
|
|
162
167
|
}
|
|
163
168
|
}
|
|
164
169
|
|
|
165
|
-
if (this.tokens.length === 0 || !this.isLastTokenEmpty()) {
|
|
170
|
+
if ((this.tokens.length === 0 || !this.isLastTokenEmpty()) && !this.viewOnly) {
|
|
166
171
|
this.tokens.push(createTerm());
|
|
167
172
|
}
|
|
168
173
|
|
|
@@ -191,6 +196,10 @@ export default {
|
|
|
191
196
|
this.tokens = needDenormalization(newValue) ? denormalizeTokens(newValue) : newValue;
|
|
192
197
|
},
|
|
193
198
|
|
|
199
|
+
isActiveToken(idx) {
|
|
200
|
+
return this.activeTokenIdx === idx;
|
|
201
|
+
},
|
|
202
|
+
|
|
194
203
|
isLastToken(idx) {
|
|
195
204
|
return !this.activeTokenIdx && idx === this.lastTokenIdx;
|
|
196
205
|
},
|
|
@@ -207,8 +216,14 @@ export default {
|
|
|
207
216
|
return this.getTokenEntry(type)?.token || GlFilteredSearchTerm;
|
|
208
217
|
},
|
|
209
218
|
|
|
219
|
+
getLastTokenClassList(idx) {
|
|
220
|
+
return this.isLastToken(idx) && !this.viewOnly ? 'gl-filtered-search-last-item' : '';
|
|
221
|
+
},
|
|
222
|
+
|
|
210
223
|
activate(idx) {
|
|
211
|
-
this.
|
|
224
|
+
if (!this.viewOnly) {
|
|
225
|
+
this.activeTokenIdx = idx;
|
|
226
|
+
}
|
|
212
227
|
},
|
|
213
228
|
|
|
214
229
|
activatePreviousToken() {
|
|
@@ -336,6 +351,7 @@ export default {
|
|
|
336
351
|
:history-items="historyItems"
|
|
337
352
|
:clearable="hasValue"
|
|
338
353
|
:search-button-attributes="searchButtonAttributes"
|
|
354
|
+
:disabled="viewOnly"
|
|
339
355
|
data-testid="filtered-search-input"
|
|
340
356
|
@submit="submit"
|
|
341
357
|
@input="applyNewValue"
|
|
@@ -348,7 +364,10 @@ export default {
|
|
|
348
364
|
<slot name="history-item" v-bind="slotScope"></slot>
|
|
349
365
|
</template>
|
|
350
366
|
<template #input>
|
|
351
|
-
<div
|
|
367
|
+
<div
|
|
368
|
+
class="gl-filtered-search-scrollable"
|
|
369
|
+
:class="{ 'gl-bg-gray-10! gl-inset-border-1-gray-100!': viewOnly }"
|
|
370
|
+
>
|
|
352
371
|
<template v-for="(token, idx) in tokens">
|
|
353
372
|
<component
|
|
354
373
|
:is="getTokenComponent(token.type)"
|
|
@@ -364,11 +383,10 @@ export default {
|
|
|
364
383
|
:placeholder="termPlaceholder"
|
|
365
384
|
:show-friendly-text="showFriendlyText"
|
|
366
385
|
:search-input-attributes="searchInputAttributes"
|
|
386
|
+
:view-only="viewOnly"
|
|
367
387
|
:is-last-token="isLastToken(idx)"
|
|
368
388
|
class="gl-filtered-search-item"
|
|
369
|
-
:class="{
|
|
370
|
-
'gl-filtered-search-last-item': isLastToken(idx),
|
|
371
|
-
}"
|
|
389
|
+
:class="{ 'gl-filtered-search-last-item': isLastToken(idx) && !viewOnly }"
|
|
372
390
|
@activate="activate(idx)"
|
|
373
391
|
@deactivate="deactivate(token)"
|
|
374
392
|
@destroy="destroyToken(idx, $event)"
|
|
@@ -23,7 +23,7 @@ describe('Filtered search term', () => {
|
|
|
23
23
|
const segmentStub = {
|
|
24
24
|
name: 'gl-filtered-search-token-segment-stub',
|
|
25
25
|
template: '<div><slot name="view"></slot><slot name="suggestions"></slot></div>',
|
|
26
|
-
props: ['searchInputAttributes', 'isLastToken', 'currentValue'],
|
|
26
|
+
props: ['searchInputAttributes', 'isLastToken', 'currentValue', 'viewOnly'],
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
const createComponent = (props) => {
|
|
@@ -35,6 +35,7 @@ describe('Filtered search term', () => {
|
|
|
35
35
|
});
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
+
const findSearchInput = () => wrapper.find('input');
|
|
38
39
|
const findTokenSegmentComponent = () => wrapper.findComponent(segmentStub);
|
|
39
40
|
|
|
40
41
|
it('renders value in inactive mode', () => {
|
|
@@ -85,8 +86,9 @@ describe('Filtered search term', () => {
|
|
|
85
86
|
}
|
|
86
87
|
);
|
|
87
88
|
|
|
88
|
-
it('passes `searchInputAttributes`, `isLastToken`,
|
|
89
|
+
it('passes `searchInputAttributes`, `isLastToken`, `currentValue` & `viewOnly` props to `GlFilteredSearchTokenSegment`', () => {
|
|
89
90
|
const isLastToken = true;
|
|
91
|
+
const viewOnly = true;
|
|
90
92
|
const currentValue = [
|
|
91
93
|
{ type: 'filtered-search-term', value: { data: 'something' } },
|
|
92
94
|
{ type: 'filtered-search-term', value: { data: '' } },
|
|
@@ -97,23 +99,41 @@ describe('Filtered search term', () => {
|
|
|
97
99
|
searchInputAttributes,
|
|
98
100
|
isLastToken,
|
|
99
101
|
currentValue,
|
|
102
|
+
viewOnly,
|
|
100
103
|
});
|
|
101
104
|
|
|
102
105
|
expect(findTokenSegmentComponent().props()).toEqual({
|
|
103
106
|
searchInputAttributes,
|
|
104
107
|
isLastToken,
|
|
105
108
|
currentValue,
|
|
109
|
+
viewOnly,
|
|
106
110
|
});
|
|
107
111
|
});
|
|
108
112
|
|
|
113
|
+
it('by default sets `viewOnly` to false on `GlFilteredSearchTokenSegment`', () => {
|
|
114
|
+
createComponent();
|
|
115
|
+
|
|
116
|
+
expect(findTokenSegmentComponent().props('viewOnly')).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
109
119
|
it('adds `searchInputAttributes` prop to search term input', () => {
|
|
110
120
|
createComponent({
|
|
111
121
|
placeholder: 'placeholder-stub',
|
|
112
122
|
searchInputAttributes,
|
|
113
123
|
});
|
|
114
124
|
|
|
115
|
-
expect(
|
|
125
|
+
expect(findSearchInput().attributes('data-qa-selector')).toBe(
|
|
116
126
|
searchInputAttributes['data-qa-selector']
|
|
117
127
|
);
|
|
118
128
|
});
|
|
129
|
+
|
|
130
|
+
describe.each([true, false])('when `viewOnly` is %s', (viewOnly) => {
|
|
131
|
+
beforeEach(() => {
|
|
132
|
+
createComponent({ viewOnly, searchInputAttributes, placeholder: 'placeholder-stub' });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it(`${viewOnly ? 'adds' : 'does not add'} \`gl-bg-gray-10\` class to search term input`, () => {
|
|
136
|
+
expect(findSearchInput().classes('gl-bg-gray-10')).toBe(viewOnly);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
119
139
|
});
|
|
@@ -69,6 +69,11 @@ export default {
|
|
|
69
69
|
default: 'end',
|
|
70
70
|
validator: (value) => ['start', 'end'].includes(value),
|
|
71
71
|
},
|
|
72
|
+
viewOnly: {
|
|
73
|
+
type: Boolean,
|
|
74
|
+
required: false,
|
|
75
|
+
default: false,
|
|
76
|
+
},
|
|
72
77
|
},
|
|
73
78
|
computed: {
|
|
74
79
|
suggestedTokens() {
|
|
@@ -145,6 +150,7 @@ export default {
|
|
|
145
150
|
:search-input-attributes="searchInputAttributes"
|
|
146
151
|
:is-last-token="isLastToken"
|
|
147
152
|
:current-value="currentValue"
|
|
153
|
+
:view-only="viewOnly"
|
|
148
154
|
@activate="$emit('activate')"
|
|
149
155
|
@deactivate="$emit('deactivate')"
|
|
150
156
|
@complete="$emit('replace', { type: $event })"
|
|
@@ -170,8 +176,10 @@ export default {
|
|
|
170
176
|
v-if="placeholder"
|
|
171
177
|
v-bind="searchInputAttributes"
|
|
172
178
|
class="gl-filtered-search-term-input"
|
|
179
|
+
:class="{ 'gl-bg-gray-10': viewOnly }"
|
|
173
180
|
:placeholder="placeholder"
|
|
174
181
|
:aria-label="placeholder"
|
|
182
|
+
:readonly="viewOnly"
|
|
175
183
|
data-testid="filtered-search-term-input"
|
|
176
184
|
/>
|
|
177
185
|
|
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
@include gl-white-space-nowrap;
|
|
9
9
|
@include gl-cursor-pointer;
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
&.gl-filtered-search-token-hover {
|
|
12
|
+
&:hover {
|
|
13
|
+
.gl-filtered-search-token-type {
|
|
14
|
+
@include gl-bg-gray-100;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.gl-filtered-search-token-data,
|
|
18
|
+
.gl-filtered-search-token-operator {
|
|
19
|
+
@include gl-bg-gray-200;
|
|
20
|
+
}
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
}
|
|
@@ -39,6 +39,27 @@ describe('Filtered search token', () => {
|
|
|
39
39
|
});
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
const mountComponent = (props) => {
|
|
43
|
+
wrapper = mount(GlFilteredSearchToken, {
|
|
44
|
+
provide: {
|
|
45
|
+
portalName: 'fake target',
|
|
46
|
+
alignSuggestions: function fakeAlignSuggestions() {},
|
|
47
|
+
},
|
|
48
|
+
stubs: {
|
|
49
|
+
Portal: {
|
|
50
|
+
template: '<div><slot></slot></div>',
|
|
51
|
+
},
|
|
52
|
+
GlFilteredSearchSuggestionList: {
|
|
53
|
+
template: '<div></div>',
|
|
54
|
+
methods: {
|
|
55
|
+
getValue: () => '=',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
propsData: { ...defaultProps, ...props },
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
42
63
|
describe('when activated', () => {
|
|
43
64
|
it('emits activate when operator segment is clicked', () => {
|
|
44
65
|
createComponent();
|
|
@@ -201,27 +222,6 @@ describe('Filtered search token', () => {
|
|
|
201
222
|
}
|
|
202
223
|
});
|
|
203
224
|
|
|
204
|
-
const mountComponent = (props) => {
|
|
205
|
-
wrapper = mount(GlFilteredSearchToken, {
|
|
206
|
-
provide: {
|
|
207
|
-
portalName: 'fake target',
|
|
208
|
-
alignSuggestions: function fakeAlignSuggestions() {},
|
|
209
|
-
},
|
|
210
|
-
stubs: {
|
|
211
|
-
Portal: {
|
|
212
|
-
template: '<div><slot></slot></div>',
|
|
213
|
-
},
|
|
214
|
-
GlFilteredSearchSuggestionList: {
|
|
215
|
-
template: '<div></div>',
|
|
216
|
-
methods: {
|
|
217
|
-
getValue: () => '=',
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
|
-
},
|
|
221
|
-
propsData: { ...defaultProps, ...props },
|
|
222
|
-
});
|
|
223
|
-
};
|
|
224
|
-
|
|
225
225
|
it('emits close event when data token is closed', () => {
|
|
226
226
|
mountComponent({ value: { operator: '=', data: 'something' } });
|
|
227
227
|
const closeWrapper = wrapper.find('.gl-token-close');
|
|
@@ -302,4 +302,57 @@ describe('Filtered search token', () => {
|
|
|
302
302
|
expect(findDataSegment().props('value')).toEqual('gamma');
|
|
303
303
|
});
|
|
304
304
|
});
|
|
305
|
+
|
|
306
|
+
describe('view-only state', () => {
|
|
307
|
+
it('prevents segments from activating when view-only is true', async () => {
|
|
308
|
+
createComponent({
|
|
309
|
+
active: true,
|
|
310
|
+
value: { operator: '=' },
|
|
311
|
+
viewOnly: true,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
await findTitleSegment().vm.$emit('activate');
|
|
315
|
+
|
|
316
|
+
expect(findTitleSegment().props().active).toBe(false);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('does not add a mousedown listener to the token-data when view-only is true', async () => {
|
|
320
|
+
mountComponent({ value: { operator: '=', data: 'something' }, viewOnly: true });
|
|
321
|
+
|
|
322
|
+
const tokenData = wrapper.find('.gl-filtered-search-token-data');
|
|
323
|
+
tokenData.element.closest = jest.fn(() => tokenData.element);
|
|
324
|
+
|
|
325
|
+
await tokenData.trigger('mousedown');
|
|
326
|
+
|
|
327
|
+
expect(tokenData.element.closest).not.toHaveBeenCalled();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe.each`
|
|
331
|
+
viewOnly | hoverClassExists | cursorClassExists | propValue
|
|
332
|
+
${true} | ${false} | ${true} | ${true}
|
|
333
|
+
${false} | ${true} | ${false} | ${false}
|
|
334
|
+
`(
|
|
335
|
+
'when view-only is $viewOnly',
|
|
336
|
+
({ viewOnly, hoverClassExists, cursorClassExists, propValue }) => {
|
|
337
|
+
beforeEach(() => {
|
|
338
|
+
createComponent({
|
|
339
|
+
active: true,
|
|
340
|
+
value: { operator: '=' },
|
|
341
|
+
viewOnly,
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it(`${viewOnly ? 'applies' : 'does not apply'} the view-only style classes`, () => {
|
|
346
|
+
expect(wrapper.find('.gl-filtered-search-token-hover').exists()).toBe(hoverClassExists);
|
|
347
|
+
expect(wrapper.find('.gl-cursor-default').exists()).toBe(cursorClassExists);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it(`sets the view-only prop to ${viewOnly} on the title, data and operator segments`, () => {
|
|
351
|
+
expect(findTitleSegment().props('viewOnly')).toBe(propValue);
|
|
352
|
+
expect(findDataSegment().props('viewOnly')).toBe(propValue);
|
|
353
|
+
expect(findOperatorSegment().props('viewOnly')).toBe(propValue);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
});
|
|
305
358
|
});
|
|
@@ -71,6 +71,11 @@ export default {
|
|
|
71
71
|
default: 'end',
|
|
72
72
|
validator: (value) => ['start', 'end'].includes(value),
|
|
73
73
|
},
|
|
74
|
+
viewOnly: {
|
|
75
|
+
type: Boolean,
|
|
76
|
+
required: false,
|
|
77
|
+
default: false,
|
|
78
|
+
},
|
|
74
79
|
},
|
|
75
80
|
data() {
|
|
76
81
|
return {
|
|
@@ -100,6 +105,10 @@ export default {
|
|
|
100
105
|
const operator = this.operators.find((op) => op.value === this.tokenValue.operator);
|
|
101
106
|
return this.showFriendlyText ? operator?.description : operator?.value;
|
|
102
107
|
},
|
|
108
|
+
|
|
109
|
+
eventListeners() {
|
|
110
|
+
return this.viewOnly ? {} : { mousedown: this.destroyByClose };
|
|
111
|
+
},
|
|
103
112
|
},
|
|
104
113
|
segments: {
|
|
105
114
|
SEGMENT_TITLE,
|
|
@@ -165,6 +174,8 @@ export default {
|
|
|
165
174
|
|
|
166
175
|
methods: {
|
|
167
176
|
activateSegment(segment) {
|
|
177
|
+
if (this.viewOnly) return;
|
|
178
|
+
|
|
168
179
|
this.activeSegment = segment;
|
|
169
180
|
|
|
170
181
|
if (!this.active) {
|
|
@@ -178,6 +189,9 @@ export default {
|
|
|
178
189
|
},
|
|
179
190
|
|
|
180
191
|
getAdditionalSegmentClasses(segment) {
|
|
192
|
+
if (this.viewOnly) {
|
|
193
|
+
return 'gl-cursor-text';
|
|
194
|
+
}
|
|
181
195
|
return { 'gl-cursor-pointer': !this.isSegmentActive(segment) };
|
|
182
196
|
},
|
|
183
197
|
|
|
@@ -293,7 +307,11 @@ export default {
|
|
|
293
307
|
<template>
|
|
294
308
|
<div
|
|
295
309
|
class="gl-filtered-search-token"
|
|
296
|
-
:class="{
|
|
310
|
+
:class="{
|
|
311
|
+
'gl-filtered-search-token-active': active,
|
|
312
|
+
'gl-filtered-search-token-hover': !viewOnly,
|
|
313
|
+
'gl-cursor-default': viewOnly,
|
|
314
|
+
}"
|
|
297
315
|
data-testid="filtered-search-token"
|
|
298
316
|
>
|
|
299
317
|
<!--
|
|
@@ -307,6 +325,7 @@ export default {
|
|
|
307
325
|
:active="isSegmentActive($options.segments.SEGMENT_TITLE)"
|
|
308
326
|
:cursor-position="intendedCursorPosition"
|
|
309
327
|
:options="availableTokensWithSelf"
|
|
328
|
+
:view-only="viewOnly"
|
|
310
329
|
@activate="activateSegment($options.segments.SEGMENT_TITLE)"
|
|
311
330
|
@deactivate="$emit('deactivate')"
|
|
312
331
|
@complete="replaceToken"
|
|
@@ -333,7 +352,7 @@ export default {
|
|
|
333
352
|
:cursor-position="intendedCursorPosition"
|
|
334
353
|
:options="operators"
|
|
335
354
|
:custom-input-keydown-handler="handleOperatorKeydown"
|
|
336
|
-
view-only
|
|
355
|
+
:view-only="viewOnly"
|
|
337
356
|
@activate="activateSegment($options.segments.SEGMENT_OPERATOR)"
|
|
338
357
|
@backspace="replaceWithTermIfEmpty"
|
|
339
358
|
@complete="activateSegment($options.segments.SEGMENT_DATA)"
|
|
@@ -382,6 +401,7 @@ export default {
|
|
|
382
401
|
:cursor-position="intendedCursorPosition"
|
|
383
402
|
:multi-select="config.multiSelect"
|
|
384
403
|
:options="config.options"
|
|
404
|
+
:view-only="viewOnly"
|
|
385
405
|
option-text-field="title"
|
|
386
406
|
@activate="activateDataSegment"
|
|
387
407
|
@backspace="activateSegment($options.segments.SEGMENT_OPERATOR)"
|
|
@@ -406,7 +426,7 @@ export default {
|
|
|
406
426
|
name="view-token"
|
|
407
427
|
v-bind="{
|
|
408
428
|
inputValue,
|
|
409
|
-
listeners:
|
|
429
|
+
listeners: eventListeners,
|
|
410
430
|
cssClasses: {
|
|
411
431
|
'gl-filtered-search-token-data': true,
|
|
412
432
|
...getAdditionalSegmentClasses($options.segments.SEGMENT_DATA),
|
|
@@ -417,7 +437,8 @@ export default {
|
|
|
417
437
|
class="gl-filtered-search-token-data"
|
|
418
438
|
variant="search-value"
|
|
419
439
|
:class="getAdditionalSegmentClasses($options.segments.SEGMENT_DATA)"
|
|
420
|
-
|
|
440
|
+
:view-only="viewOnly"
|
|
441
|
+
v-on="eventListeners"
|
|
421
442
|
>
|
|
422
443
|
<span class="gl-filtered-search-token-data-content">
|
|
423
444
|
<!--
|
|
@@ -77,6 +77,14 @@ describe('Filtered search token segment', () => {
|
|
|
77
77
|
expect(wrapper.emitted().activate).toHaveLength(1);
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
it('does not emit activate when view-only is true', () => {
|
|
81
|
+
createComponent({ viewOnly: true, value: '' });
|
|
82
|
+
|
|
83
|
+
wrapper.trigger('mousedown.left');
|
|
84
|
+
|
|
85
|
+
expect(wrapper.emitted().activate).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
|
|
80
88
|
it('ignores mousedown if active', () => {
|
|
81
89
|
createComponent({ value: '', active: true });
|
|
82
90
|
|
|
@@ -312,5 +320,15 @@ describe('Filtered search token segment', () => {
|
|
|
312
320
|
searchInputAttributes['data-qa-selector']
|
|
313
321
|
);
|
|
314
322
|
});
|
|
323
|
+
|
|
324
|
+
describe.each([true, false])('and viewOnly is %s', (viewOnly) => {
|
|
325
|
+
const readonly = viewOnly ? 'readonly' : undefined;
|
|
326
|
+
|
|
327
|
+
it(`sets the input \`readonly\` atttribute to ${readonly}`, () => {
|
|
328
|
+
createWrappedComponent({ value: 'test', active: true, viewOnly });
|
|
329
|
+
|
|
330
|
+
expect(wrapper.find('input').attributes('readonly')).toBe(readonly);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
315
333
|
});
|
|
316
334
|
});
|