@gitlab/ui 59.1.1 → 59.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 +16 -0
- package/dist/components/base/filtered_search/filtered_search.js +8 -5
- package/dist/components/base/filtered_search/filtered_search_suggestion.js +2 -1
- package/dist/components/base/filtered_search/filtered_search_suggestion_list.js +2 -2
- package/dist/components/base/filtered_search/filtered_search_term.js +1 -1
- package/dist/components/base/filtered_search/filtered_search_token_segment.js +2 -2
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/package.json +1 -1
- package/src/components/base/button/button.scss +4 -0
- package/src/components/base/filtered_search/filtered_search.md +4 -3
- package/src/components/base/filtered_search/filtered_search.spec.js +13 -0
- package/src/components/base/filtered_search/filtered_search.stories.js +6 -1
- package/src/components/base/filtered_search/filtered_search.vue +8 -10
- package/src/components/base/filtered_search/filtered_search_suggestion.vue +1 -1
- package/src/components/base/filtered_search/filtered_search_suggestion_list.spec.js +11 -1
- package/src/components/base/filtered_search/filtered_search_suggestion_list.vue +2 -2
- package/src/components/base/filtered_search/filtered_search_term.spec.js +16 -1
- package/src/components/base/filtered_search/filtered_search_term.vue +2 -1
- package/src/components/base/filtered_search/filtered_search_token_segment.vue +2 -2
package/package.json
CHANGED
|
@@ -20,9 +20,10 @@ Prepare array of available token configurations with the following fields:
|
|
|
20
20
|
(e.g. `{ value: '=', description: 'is', default: 'true' }`)
|
|
21
21
|
- `multiSelect`: (optional) when `true`, the suggestions list becomes multi-select instead of single-select.
|
|
22
22
|
It is discouraged to use this together with `unique`, as `unique` is intended for single-select.
|
|
23
|
-
- `options`: (optional) an array of options which the user can pick after the
|
|
24
|
-
The option object can have the following
|
|
25
|
-
|
|
23
|
+
- `options`: (optional) an array of options which the user can pick after the
|
|
24
|
+
operator has been selected. The option object can have the following
|
|
25
|
+
properties defined: `value: string`, `icon: string`, `title: string` and
|
|
26
|
+
`default: boolean`. If the `default` is omitted, the `value` of the first
|
|
26
27
|
option will be displayed as a suggestion
|
|
27
28
|
- any additional fields required to configure your component
|
|
28
29
|
|
|
@@ -709,6 +709,19 @@ describe('Filtered search integration tests', () => {
|
|
|
709
709
|
});
|
|
710
710
|
});
|
|
711
711
|
|
|
712
|
+
it('sets the gl-filtered-search-last-item class only when no token is active', async () => {
|
|
713
|
+
mountComponent({ value: ['one', 'two'] });
|
|
714
|
+
await nextTick();
|
|
715
|
+
|
|
716
|
+
expect(wrapper.findAll('.gl-filtered-search-last-item')).toHaveLength(1);
|
|
717
|
+
|
|
718
|
+
activate(0);
|
|
719
|
+
|
|
720
|
+
await nextTick();
|
|
721
|
+
|
|
722
|
+
expect(wrapper.findAll('.gl-filtered-search-last-item')).toHaveLength(0);
|
|
723
|
+
});
|
|
724
|
+
|
|
712
725
|
it('does not render unique token in suggestions list if it is already present', async () => {
|
|
713
726
|
mountComponent({ value: ['token', { type: 'unique', value: { data: 'something' } }] });
|
|
714
727
|
activate(0);
|
|
@@ -118,7 +118,12 @@ const UserToken = {
|
|
|
118
118
|
const MilestoneToken = {
|
|
119
119
|
name: 'MilestoneToken',
|
|
120
120
|
__v_skip: true /* temporary workaround for @vue/compat */,
|
|
121
|
-
components: {
|
|
121
|
+
components: {
|
|
122
|
+
GlFilteredSearchToken,
|
|
123
|
+
GlFilteredSearchSuggestion,
|
|
124
|
+
GlLoadingIcon,
|
|
125
|
+
GlDropdownDivider,
|
|
126
|
+
},
|
|
122
127
|
props: ['value', 'active'],
|
|
123
128
|
inheritAttrs: false,
|
|
124
129
|
data() {
|
|
@@ -220,7 +220,7 @@ export default {
|
|
|
220
220
|
},
|
|
221
221
|
|
|
222
222
|
isLastToken(idx) {
|
|
223
|
-
return
|
|
223
|
+
return this.activeTokenIdx === null && idx === this.lastTokenIdx;
|
|
224
224
|
},
|
|
225
225
|
|
|
226
226
|
isLastTokenEmpty() {
|
|
@@ -235,8 +235,11 @@ export default {
|
|
|
235
235
|
return this.getTokenEntry(type)?.token || GlFilteredSearchTerm;
|
|
236
236
|
},
|
|
237
237
|
|
|
238
|
-
|
|
239
|
-
return
|
|
238
|
+
getTokenClassList(idx) {
|
|
239
|
+
return {
|
|
240
|
+
'gl-filtered-search-item': true,
|
|
241
|
+
'gl-filtered-search-last-item': this.isLastToken(idx) && !this.viewOnly,
|
|
242
|
+
};
|
|
240
243
|
},
|
|
241
244
|
|
|
242
245
|
activate(idx) {
|
|
@@ -315,11 +318,7 @@ export default {
|
|
|
315
318
|
},
|
|
316
319
|
|
|
317
320
|
createTokens(idx, newStrings = ['']) {
|
|
318
|
-
if (
|
|
319
|
-
this.activeTokenIdx !== this.lastTokenIdx &&
|
|
320
|
-
newStrings.length === 1 &&
|
|
321
|
-
newStrings[0] === ''
|
|
322
|
-
) {
|
|
321
|
+
if (!this.isLastTokenActive && newStrings.length === 1 && newStrings[0] === '') {
|
|
323
322
|
this.activeTokenIdx = this.lastTokenIdx;
|
|
324
323
|
return;
|
|
325
324
|
}
|
|
@@ -394,8 +393,7 @@ export default {
|
|
|
394
393
|
:search-input-attributes="searchInputAttributes"
|
|
395
394
|
:view-only="viewOnly"
|
|
396
395
|
:is-last-token="isLastToken(idx)"
|
|
397
|
-
class="
|
|
398
|
-
:class="{ 'gl-filtered-search-last-item': isLastToken(idx) && !viewOnly }"
|
|
396
|
+
:class="getTokenClassList(idx)"
|
|
399
397
|
@activate="activate(idx)"
|
|
400
398
|
@deactivate="deactivate(token)"
|
|
401
399
|
@destroy="destroyToken(idx, $event)"
|
|
@@ -26,7 +26,7 @@ export default {
|
|
|
26
26
|
isActive(newValue) {
|
|
27
27
|
if (newValue) {
|
|
28
28
|
window.requestAnimationFrame(() => {
|
|
29
|
-
this.$refs.item
|
|
29
|
+
this.$refs.item?.$el?.scrollIntoView({ block: 'nearest', inline: 'end' });
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
},
|
|
@@ -79,6 +79,7 @@ describe('Filtered search suggestion list component', () => {
|
|
|
79
79
|
<div>
|
|
80
80
|
<gl-filtered-search-suggestion value="One">One</gl-filtered-search-suggestion>
|
|
81
81
|
<gl-filtered-search-suggestion value="Two">Two</gl-filtered-search-suggestion>
|
|
82
|
+
<gl-filtered-search-suggestion :value="false">Three</gl-filtered-search-suggestion>
|
|
82
83
|
</div>
|
|
83
84
|
`,
|
|
84
85
|
};
|
|
@@ -123,6 +124,7 @@ describe('Filtered search suggestion list component', () => {
|
|
|
123
124
|
wrapper.vm.nextItem();
|
|
124
125
|
wrapper.vm.nextItem();
|
|
125
126
|
wrapper.vm.nextItem();
|
|
127
|
+
wrapper.vm.nextItem();
|
|
126
128
|
return wrapper.vm.$nextTick().then(() => {
|
|
127
129
|
expect(wrapper.vm.getValue()).toBe(null);
|
|
128
130
|
});
|
|
@@ -141,7 +143,7 @@ describe('Filtered search suggestion list component', () => {
|
|
|
141
143
|
wrapper.vm.prevItem();
|
|
142
144
|
wrapper.vm.prevItem();
|
|
143
145
|
return wrapper.vm.$nextTick().then(() => {
|
|
144
|
-
expect(wrapper.vm.getValue()).toBe(
|
|
146
|
+
expect(wrapper.vm.getValue()).toBe(false);
|
|
145
147
|
});
|
|
146
148
|
});
|
|
147
149
|
|
|
@@ -150,6 +152,7 @@ describe('Filtered search suggestion list component', () => {
|
|
|
150
152
|
wrapper.vm.nextItem();
|
|
151
153
|
wrapper.vm.nextItem();
|
|
152
154
|
wrapper.vm.nextItem();
|
|
155
|
+
wrapper.vm.nextItem();
|
|
153
156
|
return wrapper.vm.$nextTick().then(() => {
|
|
154
157
|
expect(wrapper.vm.getValue()).toBe('One');
|
|
155
158
|
});
|
|
@@ -169,6 +172,13 @@ describe('Filtered search suggestion list component', () => {
|
|
|
169
172
|
});
|
|
170
173
|
});
|
|
171
174
|
|
|
175
|
+
it('highlights suggestion if initial-value is provided, regardless of falsiness', async () => {
|
|
176
|
+
wrapper.setProps({ initialValue: false });
|
|
177
|
+
return wrapper.vm.$nextTick().then(() => {
|
|
178
|
+
expect(wrapper.find('.gl-filtered-search-suggestion-active').text()).toBe('Three');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
172
182
|
it('does not highlight anything if initial-value matches nothing', () => {
|
|
173
183
|
wrapper.setProps({ initialValue: 'missing' });
|
|
174
184
|
return wrapper.vm.$nextTick().then(() => {
|
|
@@ -45,9 +45,9 @@ export default {
|
|
|
45
45
|
|
|
46
46
|
methods: {
|
|
47
47
|
valuesMatch(firstValue, secondValue) {
|
|
48
|
-
if (
|
|
48
|
+
if (firstValue == null || secondValue == null) return false;
|
|
49
49
|
|
|
50
|
-
return typeof firstValue === 'string'
|
|
50
|
+
return typeof firstValue === 'string' && typeof secondValue === 'string'
|
|
51
51
|
? firstValue.toLowerCase() === secondValue.toLowerCase()
|
|
52
52
|
: firstValue === secondValue;
|
|
53
53
|
},
|
|
@@ -68,7 +68,7 @@ describe('Filtered search term', () => {
|
|
|
68
68
|
|
|
69
69
|
it('renders input with placeholder if placeholder prop is provided', () => {
|
|
70
70
|
createComponent({ placeholder: 'placeholder-stub' });
|
|
71
|
-
expect(
|
|
71
|
+
expect(findSearchInput().attributes('placeholder')).toBe('placeholder-stub');
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
it('filters suggestions by input', async () => {
|
|
@@ -145,6 +145,21 @@ describe('Filtered search term', () => {
|
|
|
145
145
|
);
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
+
it('activates and deactivates when the input is focused/blurred', async () => {
|
|
149
|
+
createComponent({ placeholder: 'placeholder-stub' });
|
|
150
|
+
await nextTick();
|
|
151
|
+
|
|
152
|
+
expect(wrapper.emitted()).toEqual({});
|
|
153
|
+
|
|
154
|
+
await findSearchInput().trigger('focusin');
|
|
155
|
+
|
|
156
|
+
expect(wrapper.emitted()).toEqual({ activate: [[]] });
|
|
157
|
+
|
|
158
|
+
await findSearchInput().trigger('focusout');
|
|
159
|
+
|
|
160
|
+
expect(wrapper.emitted()).toEqual({ activate: [[]], deactivate: [[]] });
|
|
161
|
+
});
|
|
162
|
+
|
|
148
163
|
describe.each([true, false])('when `viewOnly` is %s', (viewOnly) => {
|
|
149
164
|
beforeEach(() => {
|
|
150
165
|
createComponent({ viewOnly, searchInputAttributes, placeholder: 'placeholder-stub' });
|
|
@@ -146,7 +146,6 @@ export default {
|
|
|
146
146
|
class="gl-filtered-search-term-token"
|
|
147
147
|
:active="active"
|
|
148
148
|
:cursor-position="cursorPosition"
|
|
149
|
-
:class="{ 'gl-w-full': placeholder }"
|
|
150
149
|
:search-input-attributes="searchInputAttributes"
|
|
151
150
|
:is-last-token="isLastToken"
|
|
152
151
|
:current-value="currentValue"
|
|
@@ -181,6 +180,8 @@ export default {
|
|
|
181
180
|
:aria-label="placeholder"
|
|
182
181
|
:readonly="viewOnly"
|
|
183
182
|
data-testid="filtered-search-term-input"
|
|
183
|
+
@focusin="$emit('activate')"
|
|
184
|
+
@focusout="$emit('deactivate')"
|
|
184
185
|
/>
|
|
185
186
|
|
|
186
187
|
<template v-else>{{ value.data }}</template>
|
|
@@ -170,10 +170,10 @@ export default {
|
|
|
170
170
|
return this.nonMultipleValue;
|
|
171
171
|
}
|
|
172
172
|
if (this.value) {
|
|
173
|
-
const
|
|
173
|
+
const option =
|
|
174
174
|
this.getMatchingOptionForInputValue(this.inputValue) ||
|
|
175
175
|
this.getMatchingOptionForInputValue(this.inputValue, { loose: true });
|
|
176
|
-
return
|
|
176
|
+
return option?.value;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
const defaultSuggestion = this.options.find((op) => op.default);
|