@gitlab/ui 61.1.3 → 61.3.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.
Files changed (28) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/components/base/filtered_search/common_story_options.js +2 -1
  3. package/dist/components/base/filtered_search/filtered_search.js +28 -4
  4. package/dist/components/base/filtered_search/filtered_search_suggestion_list.js +13 -5
  5. package/dist/components/base/filtered_search/filtered_search_term.js +51 -4
  6. package/dist/components/base/filtered_search/filtered_search_token.js +1 -2
  7. package/dist/components/base/filtered_search/filtered_search_token_segment.js +18 -5
  8. package/dist/components/base/filtered_search/filtered_search_utils.js +18 -7
  9. package/dist/utility_classes.css +1 -1
  10. package/dist/utility_classes.css.map +1 -1
  11. package/package.json +1 -1
  12. package/src/components/base/filtered_search/__snapshots__/filtered_search_term.spec.js.snap +2 -5
  13. package/src/components/base/filtered_search/common_story_options.js +1 -0
  14. package/src/components/base/filtered_search/filtered_search.md +10 -1
  15. package/src/components/base/filtered_search/filtered_search.spec.js +14 -2
  16. package/src/components/base/filtered_search/filtered_search.stories.js +17 -0
  17. package/src/components/base/filtered_search/filtered_search.vue +29 -1
  18. package/src/components/base/filtered_search/filtered_search_suggestion_list.spec.js +222 -75
  19. package/src/components/base/filtered_search/filtered_search_suggestion_list.vue +15 -6
  20. package/src/components/base/filtered_search/filtered_search_term.spec.js +73 -14
  21. package/src/components/base/filtered_search/filtered_search_term.vue +64 -6
  22. package/src/components/base/filtered_search/filtered_search_token.spec.js +1 -0
  23. package/src/components/base/filtered_search/filtered_search_token.vue +2 -2
  24. package/src/components/base/filtered_search/filtered_search_token_segment.spec.js +46 -2
  25. package/src/components/base/filtered_search/filtered_search_token_segment.vue +22 -5
  26. package/src/components/base/filtered_search/filtered_search_utils.js +20 -7
  27. package/src/scss/utilities.scss +32 -16
  28. package/src/scss/utility-mixins/border.scss +16 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "61.1.3",
3
+ "version": "61.3.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -1,19 +1,16 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
- exports[`Filtered search term renders input with value in active mode 1`] = `
3
+ exports[`Filtered search term renders nothing with value in active mode (delegates to segment) 1`] = `
4
4
  <div
5
5
  class="gl-h-auto gl-filtered-search-term"
6
6
  data-testid="filtered-search-term"
7
7
  >
8
8
  <div
9
- active="true"
10
9
  class="gl-filtered-search-term-token"
11
10
  cursor-position="end"
12
11
  is-term=""
13
12
  value="test-value"
14
- >
15
- test-value
16
- </div>
13
+ />
17
14
  </div>
18
15
  `;
19
16
 
@@ -8,4 +8,5 @@ export const provide = () => ({
8
8
  portalName: 'portal',
9
9
  alignSuggestions: noop,
10
10
  suggestionsListClass: noop,
11
+ termsAsTokens: () => false,
11
12
  });
@@ -48,6 +48,15 @@ The token should emit the following events:
48
48
  - `split`: token requests adding string values after the current token.
49
49
  - `complete`: token indicates its editing is completed.
50
50
 
51
+ ### Improve space handling
52
+
53
+ Set the `terms-as-tokens` prop to `true` to enable new term rendering and
54
+ interaction behavior. This makes it easier to input/edit free text tokens, and
55
+ removes the need for quoting values with spaces and other workarounds.
56
+
57
+ In future, this prop will be enabled by default and eventually removed. Opt in
58
+ to this earlier rather than later to ease migration.
59
+
51
60
  ## Examples
52
61
 
53
62
  Define a list of available tokens:
@@ -65,5 +74,5 @@ realtime updates:
65
74
  <!-- Empty initial line is a workaround for https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2102 -->
66
75
  ```html
67
76
 
68
- <gl-filtered-search :available-tokens="tokens" v-model="value" />
77
+ <gl-filtered-search :available-tokens="tokens" v-model="value" terms-as-tokens />
69
78
  ```
@@ -95,7 +95,7 @@ describe('Filtered search', () => {
95
95
  });
96
96
  await nextTick();
97
97
 
98
- const inputEventArgs = wrapper.emitted().input[1][0];
98
+ const [inputEventArgs] = wrapper.emitted().input.at(-1);
99
99
  expect(inputEventArgs.every((t) => t.type === TERM_TOKEN_TYPE)).toBe(true);
100
100
  expect(inputEventArgs.map((t) => t.value.data)).toStrictEqual(['one', 'two', '']);
101
101
  });
@@ -106,10 +106,22 @@ describe('Filtered search', () => {
106
106
  });
107
107
  await nextTick();
108
108
 
109
- const inputEventArgs = wrapper.emitted().input[1][0];
109
+ const [inputEventArgs] = wrapper.emitted().input.at(-1);
110
110
  expect(inputEventArgs.every((t) => t.type === TERM_TOKEN_TYPE)).toBe(true);
111
111
  expect(inputEventArgs.map((t) => t.value.data)).toStrictEqual(['one', 'two', '']);
112
112
  });
113
+
114
+ it('does not split strings with termsAsTokens', async () => {
115
+ createComponent({
116
+ termsAsTokens: true,
117
+ value: ['one two'],
118
+ });
119
+ await nextTick();
120
+
121
+ const [inputEventArgs] = wrapper.emitted().input.at(-1);
122
+ expect(inputEventArgs.every((t) => t.type === TERM_TOKEN_TYPE)).toBe(true);
123
+ expect(inputEventArgs.map((t) => t.value.data)).toStrictEqual(['one two', '']);
124
+ });
113
125
  });
114
126
 
115
127
  describe('event handling', () => {
@@ -329,6 +329,23 @@ export const Default = () => ({
329
329
  template: `<gl-filtered-search :available-tokens="tokens" :value="value" />`,
330
330
  });
331
331
 
332
+ export const WithTermsAsTokens = () => ({
333
+ data() {
334
+ return {
335
+ tokens,
336
+ value: [
337
+ { type: 'author', value: { data: 'beta', operator: '=' } },
338
+ { type: 'label', value: { data: 'Bug', operator: '=' } },
339
+ 'raw text',
340
+ ],
341
+ };
342
+ },
343
+ components,
344
+ template: `
345
+ <gl-filtered-search :available-tokens="tokens" v-model="value" terms-as-tokens />
346
+ `,
347
+ });
348
+
332
349
  export const ViewOnly = () => ({
333
350
  data() {
334
351
  return {
@@ -15,6 +15,7 @@ import {
15
15
  normalizeTokens,
16
16
  denormalizeTokens,
17
17
  needDenormalization,
18
+ termTokenDefinition,
18
19
  } from './filtered_search_utils';
19
20
 
20
21
  Vue.use(PortalVue);
@@ -43,6 +44,7 @@ export default {
43
44
  // TODO: This can be reverted once https://github.com/vuejs/vue-apollo/pull/1153
44
45
  // has been merged and we consume it, or we upgrade to vue-apollo@4.
45
46
  suggestionsListClass: () => this.suggestionsListClass,
47
+ termsAsTokens: () => this.termsAsTokens,
46
48
  };
47
49
  },
48
50
  inheritAttrs: false,
@@ -137,6 +139,28 @@ export default {
137
139
  required: false,
138
140
  default: false,
139
141
  },
142
+ /**
143
+ * Render search terms as GlTokens. Ideally, this prop will be as
144
+ * short-lived as possible, and this behavior will become the default and
145
+ * only behavior.
146
+ *
147
+ * This prop is *not* reactive.
148
+ *
149
+ * See https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2159.
150
+ */
151
+ termsAsTokens: {
152
+ type: Boolean,
153
+ required: false,
154
+ default: false,
155
+ },
156
+ /**
157
+ * The title of the text search option. Ignored unless termsAsTokens is enabled.
158
+ */
159
+ searchTextOptionLabel: {
160
+ type: String,
161
+ required: false,
162
+ default: termTokenDefinition.title,
163
+ },
140
164
  },
141
165
  data() {
142
166
  return {
@@ -212,7 +236,9 @@ export default {
212
236
 
213
237
  methods: {
214
238
  applyNewValue(newValue) {
215
- this.tokens = needDenormalization(newValue) ? denormalizeTokens(newValue) : newValue;
239
+ this.tokens = needDenormalization(newValue)
240
+ ? denormalizeTokens(newValue, this.termsAsTokens)
241
+ : newValue;
216
242
  },
217
243
 
218
244
  isActiveToken(idx) {
@@ -317,6 +343,7 @@ export default {
317
343
  this.activeTokenIdx = idx;
318
344
  },
319
345
 
346
+ // This method can be deleted once termsAsTokens behavior is the default.
320
347
  createTokens(idx, newStrings = ['']) {
321
348
  if (!this.isLastTokenActive && newStrings.length === 1 && newStrings[0] === '') {
322
349
  this.activeTokenIdx = this.lastTokenIdx;
@@ -391,6 +418,7 @@ export default {
391
418
  :view-only="viewOnly"
392
419
  :is-last-token="isLastToken(idx)"
393
420
  :class="getTokenClassList(idx)"
421
+ :search-text-option-label="searchTextOptionLabel"
394
422
  @activate="activate(idx)"
395
423
  @deactivate="deactivate(token)"
396
424
  @destroy="destroyToken(idx, $event)"
@@ -7,13 +7,14 @@ describe('Filtered search suggestion list component', () => {
7
7
  let wrapper;
8
8
 
9
9
  describe('suggestions API', () => {
10
- beforeEach(() => {
10
+ const createComponent = ({ termsAsTokens = false } = {}) => {
11
11
  wrapper = shallowMount(FilteredSearchSuggestionList, {
12
- provide: { suggestionsListClass: () => 'custom-class' },
12
+ provide: { suggestionsListClass: () => 'custom-class', termsAsTokens: () => termsAsTokens },
13
13
  });
14
- });
14
+ };
15
15
 
16
16
  it('has expected public methods', () => {
17
+ createComponent();
17
18
  expect(wrapper.vm.register).toBeInstanceOf(Function);
18
19
  expect(wrapper.vm.unregister).toBeInstanceOf(Function);
19
20
  expect(wrapper.vm.getValue).toBeInstanceOf(Function);
@@ -24,6 +25,7 @@ describe('Filtered search suggestion list component', () => {
24
25
  describe('navigation', () => {
25
26
  const stubs = [{ value: 'stub1' }, { value: 'stub2' }, { value: 'stub3' }];
26
27
  beforeEach(() => {
28
+ createComponent();
27
29
  stubs.forEach((s) => {
28
30
  wrapper.vm.register(s);
29
31
  });
@@ -71,6 +73,58 @@ describe('Filtered search suggestion list component', () => {
71
73
  expect(wrapper.vm.getValue()).toBe('stub2');
72
74
  });
73
75
  });
76
+
77
+ describe('navigation with termsAsTokens', () => {
78
+ const stubs = [{ value: 'stub1' }, { value: 'stub2' }, { value: 'stub3' }];
79
+ beforeEach(() => {
80
+ createComponent({ termsAsTokens: true });
81
+ stubs.forEach((s) => {
82
+ wrapper.vm.register(s);
83
+ });
84
+ });
85
+
86
+ it('does not select item by default', () => {
87
+ expect(wrapper.vm.getValue()).toBe(null);
88
+ });
89
+
90
+ it('selects first item on nextItem call', async () => {
91
+ wrapper.vm.nextItem();
92
+ await nextTick();
93
+ expect(wrapper.vm.getValue()).toBe(stubs[0].value);
94
+ });
95
+
96
+ it('wraps to last item on prevItem call', async () => {
97
+ wrapper.vm.nextItem();
98
+ wrapper.vm.prevItem();
99
+ await nextTick();
100
+ expect(wrapper.vm.getValue()).toBe(stubs[2].value);
101
+ });
102
+
103
+ it('wraps to first item on nextItem call', async () => {
104
+ stubs.forEach(() => wrapper.vm.nextItem());
105
+ wrapper.vm.nextItem();
106
+ await nextTick();
107
+ expect(wrapper.vm.getValue()).toBe(stubs[0].value);
108
+ });
109
+
110
+ it('remove selection if suggestion is unregistered', async () => {
111
+ wrapper.vm.nextItem();
112
+ await nextTick();
113
+ wrapper.vm.unregister(stubs[0]);
114
+ await nextTick();
115
+ expect(wrapper.vm.getValue()).toBe(null);
116
+ });
117
+
118
+ it('selects correct suggestion when item (un)registration is late', async () => {
119
+ // Initially stub2 is at index 1.
120
+ await wrapper.setProps({ initialValue: 'stub2' });
121
+ // Remove item at index 0, so stub2 moves to index 0
122
+ wrapper.vm.unregister(stubs[0]);
123
+ await nextTick();
124
+ // stub2 should still be selected
125
+ expect(wrapper.vm.getValue()).toBe('stub2');
126
+ });
127
+ });
74
128
  });
75
129
 
76
130
  describe('integration tests', () => {
@@ -87,6 +141,17 @@ describe('Filtered search suggestion list component', () => {
87
141
  `,
88
142
  };
89
143
 
144
+ const createComponent = ({ termsAsTokens = false } = {}) => {
145
+ wrapper = mount(FilteredSearchSuggestionList, {
146
+ provide: { suggestionsListClass: () => 'custom-class', termsAsTokens: () => termsAsTokens },
147
+ slots: {
148
+ default: list,
149
+ },
150
+ });
151
+ };
152
+
153
+ const findActiveSuggestion = () => wrapper.find('.gl-filtered-search-suggestion-active');
154
+
90
155
  beforeAll(() => {
91
156
  if (!HTMLElement.prototype.scrollIntoView) {
92
157
  HTMLElement.prototype.scrollIntoView = jest.fn();
@@ -99,92 +164,174 @@ describe('Filtered search suggestion list component', () => {
99
164
  }
100
165
  });
101
166
 
102
- beforeEach(() => {
103
- wrapper = mount(FilteredSearchSuggestionList, {
104
- provide: { suggestionsListClass: () => 'custom-class' },
105
- slots: {
106
- default: list,
107
- },
167
+ describe('with termsAsTokens = false', () => {
168
+ beforeEach(() => {
169
+ createComponent();
108
170
  });
109
- });
110
171
 
111
- it('selects first suggestion', async () => {
112
- wrapper.vm.nextItem();
113
- await nextTick();
114
- expect(wrapper.vm.getValue()).toBe('One');
115
- });
172
+ it('selects first suggestion', async () => {
173
+ wrapper.vm.nextItem();
174
+ await nextTick();
175
+ expect(wrapper.vm.getValue()).toBe('One');
176
+ });
116
177
 
117
- it('selects second suggestion', async () => {
118
- wrapper.vm.nextItem();
119
- wrapper.vm.nextItem();
120
- await nextTick();
121
- expect(wrapper.vm.getValue()).toBe('Two');
122
- });
178
+ it('selects second suggestion', async () => {
179
+ wrapper.vm.nextItem();
180
+ wrapper.vm.nextItem();
181
+ await nextTick();
182
+ expect(wrapper.vm.getValue()).toBe('Two');
183
+ });
123
184
 
124
- it('deselects first suggestion after list end', async () => {
125
- wrapper.vm.nextItem();
126
- wrapper.vm.nextItem();
127
- wrapper.vm.nextItem();
128
- wrapper.vm.nextItem();
129
- await nextTick();
130
- expect(wrapper.vm.getValue()).toBe(null);
131
- });
185
+ it('deselects first suggestion after list end', async () => {
186
+ wrapper.vm.nextItem();
187
+ wrapper.vm.nextItem();
188
+ wrapper.vm.nextItem();
189
+ wrapper.vm.nextItem();
190
+ await nextTick();
191
+ expect(wrapper.vm.getValue()).toBe(null);
192
+ });
132
193
 
133
- it('deselects first suggestion after list start', async () => {
134
- wrapper.vm.nextItem();
135
- wrapper.vm.prevItem();
136
- await nextTick();
137
- expect(wrapper.vm.getValue()).toBe(null);
138
- });
194
+ it('deselects first suggestion after list start', async () => {
195
+ wrapper.vm.nextItem();
196
+ wrapper.vm.prevItem();
197
+ await nextTick();
198
+ expect(wrapper.vm.getValue()).toBe(null);
199
+ });
139
200
 
140
- it('selects last suggestion in circle when selecting previous item', async () => {
141
- wrapper.vm.nextItem();
142
- wrapper.vm.prevItem();
143
- wrapper.vm.prevItem();
144
- await nextTick();
145
- expect(wrapper.vm.getValue()).toBe(false);
146
- });
201
+ it('selects last suggestion in circle when selecting previous item', async () => {
202
+ wrapper.vm.nextItem();
203
+ wrapper.vm.prevItem();
204
+ wrapper.vm.prevItem();
205
+ await nextTick();
206
+ expect(wrapper.vm.getValue()).toBe(false);
207
+ });
147
208
 
148
- it('selects first suggestion in circle when selecting next item', async () => {
149
- wrapper.vm.nextItem();
150
- wrapper.vm.nextItem();
151
- wrapper.vm.nextItem();
152
- wrapper.vm.nextItem();
153
- wrapper.vm.nextItem();
154
- await nextTick();
155
- expect(wrapper.vm.getValue()).toBe('One');
156
- });
209
+ it('selects first suggestion in circle when selecting next item', async () => {
210
+ wrapper.vm.nextItem();
211
+ wrapper.vm.nextItem();
212
+ wrapper.vm.nextItem();
213
+ wrapper.vm.nextItem();
214
+ wrapper.vm.nextItem();
215
+ await nextTick();
216
+ expect(wrapper.vm.getValue()).toBe('One');
217
+ });
157
218
 
158
- it('highlights suggestion if initial-value is provided', async () => {
159
- await wrapper.setProps({ initialValue: 'Two' });
160
- expect(wrapper.find('.gl-filtered-search-suggestion-active').text()).toBe('Two');
161
- });
219
+ it('highlights suggestion if initial-value is provided', async () => {
220
+ await wrapper.setProps({ initialValue: 'Two' });
221
+ expect(findActiveSuggestion().text()).toBe('Two');
222
+ });
162
223
 
163
- it('highlights suggestion if initial-value is provided, regardless of case sensitivity', async () => {
164
- await wrapper.setProps({ initialValue: 'two' });
165
- expect(wrapper.find('.gl-filtered-search-suggestion-active').text()).toBe('Two');
166
- });
224
+ it('highlights suggestion if initial-value is provided, regardless of case sensitivity', async () => {
225
+ await wrapper.setProps({ initialValue: 'two' });
226
+ expect(findActiveSuggestion().text()).toBe('Two');
227
+ });
167
228
 
168
- it('highlights suggestion if initial-value is provided, regardless of falsiness', async () => {
169
- await wrapper.setProps({ initialValue: false });
170
- expect(wrapper.find('.gl-filtered-search-suggestion-active').text()).toBe('Three');
171
- });
229
+ it('highlights suggestion if initial-value is provided, regardless of falsiness', async () => {
230
+ await wrapper.setProps({ initialValue: false });
231
+ expect(findActiveSuggestion().text()).toBe('Three');
232
+ });
172
233
 
173
- it('highlights first suggestion if initial-value is provided, deselected then selected', async () => {
174
- await wrapper.setProps({ initialValue: 'One' });
175
- wrapper.vm.prevItem();
176
- wrapper.vm.nextItem();
177
- await nextTick();
178
- expect(wrapper.find('.gl-filtered-search-suggestion-active').text()).toBe('One');
179
- });
234
+ it('highlights first suggestion if initial-value is provided, deselected then selected', async () => {
235
+ await wrapper.setProps({ initialValue: 'One' });
236
+ wrapper.vm.prevItem();
237
+ wrapper.vm.nextItem();
238
+ await nextTick();
239
+ expect(findActiveSuggestion().text()).toBe('One');
240
+ });
241
+
242
+ it('does not highlight anything if initial-value matches nothing', async () => {
243
+ await wrapper.setProps({ initialValue: 'missing' });
244
+ expect(findActiveSuggestion().exists()).toBe(false);
245
+ });
180
246
 
181
- it('does not highlight anything if initial-value matches nothing', async () => {
182
- await wrapper.setProps({ initialValue: 'missing' });
183
- expect(wrapper.find('.gl-filtered-search-suggestion-active').exists()).toBe(false);
247
+ it('applies the injected suggestion-list-class to the dropdown', () => {
248
+ expect(wrapper.classes()).toContain('custom-class');
249
+ });
184
250
  });
185
251
 
186
- it('applies the injected suggestion-list-class to the dropdown', () => {
187
- expect(wrapper.classes()).toContain('custom-class');
252
+ describe('with termsAsTokens = true', () => {
253
+ beforeEach(() => {
254
+ createComponent({ termsAsTokens: true });
255
+ });
256
+
257
+ it('selects first suggestion', async () => {
258
+ wrapper.vm.nextItem();
259
+ await nextTick();
260
+ expect(wrapper.vm.getValue()).toBe('One');
261
+ });
262
+
263
+ it('selects second suggestion', async () => {
264
+ wrapper.vm.nextItem();
265
+ wrapper.vm.nextItem();
266
+ await nextTick();
267
+ expect(wrapper.vm.getValue()).toBe('Two');
268
+ });
269
+
270
+ it('wraps to first suggestion after list end', async () => {
271
+ wrapper.vm.nextItem();
272
+ wrapper.vm.nextItem();
273
+ wrapper.vm.nextItem();
274
+ wrapper.vm.nextItem();
275
+ await nextTick();
276
+ expect(wrapper.vm.getValue()).toBe('One');
277
+ });
278
+
279
+ it('wraps to last suggestion before list start', async () => {
280
+ wrapper.vm.nextItem();
281
+ wrapper.vm.prevItem();
282
+ await nextTick();
283
+ expect(wrapper.vm.getValue()).toBe(false);
284
+ });
285
+
286
+ it('selects second-to-last suggestion in circle when selecting previous item', async () => {
287
+ wrapper.vm.nextItem();
288
+ wrapper.vm.prevItem();
289
+ wrapper.vm.prevItem();
290
+ await nextTick();
291
+ expect(wrapper.vm.getValue()).toBe('Two');
292
+ });
293
+
294
+ it('selects second suggestion in circle when selecting next item', async () => {
295
+ wrapper.vm.nextItem();
296
+ wrapper.vm.nextItem();
297
+ wrapper.vm.nextItem();
298
+ wrapper.vm.nextItem();
299
+ wrapper.vm.nextItem();
300
+ await nextTick();
301
+ expect(wrapper.vm.getValue()).toBe('Two');
302
+ });
303
+
304
+ it('highlights suggestion if initial-value is provided', async () => {
305
+ await wrapper.setProps({ initialValue: 'Two' });
306
+ expect(findActiveSuggestion().text()).toBe('Two');
307
+ });
308
+
309
+ it('highlights suggestion if initial-value is provided, regardless of case sensitivity', async () => {
310
+ await wrapper.setProps({ initialValue: 'two' });
311
+ expect(findActiveSuggestion().text()).toBe('Two');
312
+ });
313
+
314
+ it('highlights suggestion if initial-value is provided, regardless of falsiness', async () => {
315
+ await wrapper.setProps({ initialValue: false });
316
+ expect(findActiveSuggestion().text()).toBe('Three');
317
+ });
318
+
319
+ it('highlights first suggestion if initial-value is provided, deselected then selected', async () => {
320
+ await wrapper.setProps({ initialValue: 'One' });
321
+ wrapper.vm.prevItem();
322
+ wrapper.vm.nextItem();
323
+ await nextTick();
324
+ expect(findActiveSuggestion().text()).toBe('One');
325
+ });
326
+
327
+ it('does not highlight anything if initial-value matches nothing', async () => {
328
+ await wrapper.setProps({ initialValue: 'missing' });
329
+ expect(findActiveSuggestion().exists()).toBe(false);
330
+ });
331
+
332
+ it('applies the injected suggestion-list-class to the dropdown', () => {
333
+ expect(wrapper.classes()).toContain('custom-class');
334
+ });
188
335
  });
189
336
  });
190
337
  });
@@ -6,7 +6,7 @@ const NO_ACTIVE_ITEM = -2;
6
6
 
7
7
  export default {
8
8
  name: 'GlFilteredSearchSuggestionList',
9
- inject: ['suggestionsListClass'],
9
+ inject: ['suggestionsListClass', 'termsAsTokens'],
10
10
  provide() {
11
11
  return {
12
12
  filteredSearchSuggestionListInstance: this,
@@ -40,7 +40,7 @@ export default {
40
40
  return this.registeredItems[this.initialActiveIdx];
41
41
  },
42
42
  activeItem() {
43
- if (this.activeIdx === NO_ACTIVE_ITEM) return null;
43
+ if (!this.termsAsTokens() && this.activeIdx === NO_ACTIVE_ITEM) return null;
44
44
  if (this.activeIdx === DEFER_TO_INITIAL_VALUE) return this.initialActiveItem;
45
45
  return this.registeredItems[this.activeIdx];
46
46
  },
@@ -76,15 +76,24 @@ export default {
76
76
  }
77
77
  },
78
78
  nextItem() {
79
- this.stepItem(1, this.registeredItems.length - 1);
79
+ if (this.termsAsTokens()) {
80
+ this.stepItem(1);
81
+ } else {
82
+ this.stepItem(1, this.registeredItems.length - 1);
83
+ }
80
84
  },
81
85
  prevItem() {
82
- this.stepItem(-1, 0);
86
+ if (this.termsAsTokens()) {
87
+ this.stepItem(-1);
88
+ } else {
89
+ this.stepItem(-1, 0);
90
+ }
83
91
  },
84
92
  stepItem(direction, endIdx) {
85
93
  if (
86
- this.activeIdx === endIdx ||
87
- (this.activeIdx === DEFER_TO_INITIAL_VALUE && this.initialActiveIdx === endIdx)
94
+ !this.termsAsTokens() &&
95
+ (this.activeIdx === endIdx ||
96
+ (this.activeIdx === DEFER_TO_INITIAL_VALUE && this.initialActiveIdx === endIdx))
88
97
  ) {
89
98
  // The user wants to move past the end of the list, so ensure nothing is selected.
90
99
  this.activeIdx = NO_ACTIVE_ITEM;