@gitlab/ui 38.10.2 → 38.11.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 CHANGED
@@ -1,3 +1,12 @@
1
+ # [38.11.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v38.10.2...v38.11.0) (2022-04-18)
2
+
3
+
4
+ ### Features
5
+
6
+ * **GlFilteredSearch:** Allow token navigation with arrows ([80844aa](https://gitlab.com/gitlab-org/gitlab-ui/commit/80844aac908452e626d1c3e4fb0c9aef9c8a5e25))
7
+ * **GlFilteredSearch:** Allow token navigation with arrows ([b7d1519](https://gitlab.com/gitlab-org/gitlab-ui/commit/b7d15196145f53bb5d7be0361e7d7a692c2b5dc4))
8
+ * **GlFilteredSearch:** Allow token navigation with arrows ([9f2a449](https://gitlab.com/gitlab-org/gitlab-ui/commit/9f2a44951657a4aeaaa162f1bd31d60231eb8be1))
9
+
1
10
  ## [38.10.2](https://gitlab.com/gitlab-org/gitlab-ui/compare/v38.10.1...v38.10.2) (2022-04-14)
2
11
 
3
12
 
@@ -120,7 +120,8 @@ var script = {
120
120
  return {
121
121
  tokens: initialState(),
122
122
  activeTokenIdx: null,
123
- suggestionsStyle: {}
123
+ suggestionsStyle: {},
124
+ intendedCursorPosition: 'end'
124
125
  };
125
126
  },
126
127
 
@@ -224,6 +225,20 @@ var script = {
224
225
  this.activeTokenIdx = idx;
225
226
  },
226
227
 
228
+ activatePreviousToken() {
229
+ if (this.activeTokenIdx > 0) {
230
+ this.activeTokenIdx -= 1;
231
+ this.intendedCursorPosition = 'end';
232
+ }
233
+ },
234
+
235
+ activateNextToken() {
236
+ if (this.activeTokenIdx < this.value.length) {
237
+ this.activeTokenIdx += 1;
238
+ this.intendedCursorPosition = 'start';
239
+ }
240
+ },
241
+
227
242
  alignSuggestions(ref) {
228
243
  const offsetRef = ref.getBoundingClientRect().left;
229
244
  const offsetMenu = this.$el.getBoundingClientRect().left;
@@ -234,6 +249,7 @@ var script = {
234
249
  },
235
250
 
236
251
  deactivate(token) {
252
+ this.intendedCursorPosition = 'end';
237
253
  const tokenIdx = this.tokens.indexOf(token);
238
254
 
239
255
  if (tokenIdx === -1 || this.activeTokenIdx !== tokenIdx) {
@@ -327,7 +343,7 @@ const __vue_script__ = script;
327
343
  /* template */
328
344
  var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-search-box-by-click',_vm._b({attrs:{"value":_vm.tokens,"history-items":_vm.historyItems,"clearable":_vm.hasValue,"search-button-attributes":_vm.searchButtonAttributes,"data-testid":"filtered-search-input"},on:{"submit":_vm.submit,"input":_vm.applyNewValue,"history-item-selected":function($event){return _vm.$emit('history-item-selected', $event)},"clear":function($event){return _vm.$emit('clear')},"clear-history":function($event){return _vm.$emit('clear-history')}},scopedSlots:_vm._u([{key:"history-item",fn:function(slotScope){return [_vm._t("history-item",null,null,slotScope)]}},{key:"input",fn:function(){return [_c('div',{staticClass:"gl-filtered-search-scrollable"},[_vm._l((_vm.tokens),function(token,idx){return [_c(_vm.getTokenComponent(token.type),{key:token.id,ref:"tokens",refInFor:true,tag:"component",staticClass:"gl-filtered-search-item",class:{
329
345
  'gl-filtered-search-last-item': _vm.isLastToken(idx),
330
- },attrs:{"config":_vm.getTokenEntry(token.type),"active":_vm.activeTokenIdx === idx,"available-tokens":_vm.currentAvailableTokens,"current-value":_vm.tokens,"index":idx,"placeholder":_vm.termPlaceholder,"show-friendly-text":_vm.showFriendlyText,"search-input-attributes":_vm.searchInputAttributes,"is-last-token":_vm.isLastToken(idx)},on:{"activate":function($event){return _vm.activate(idx)},"deactivate":function($event){return _vm.deactivate(token)},"destroy":function($event){return _vm.destroyToken(idx, $event)},"replace":function($event){return _vm.replaceToken(idx, $event)},"complete":_vm.completeToken,"submit":_vm.submit,"split":function($event){return _vm.createTokens(idx, $event)}},model:{value:(token.value),callback:function ($$v) {_vm.$set(token, "value", $$v);},expression:"token.value"}})]})],2),_vm._v(" "),_c('portal-target',{key:_vm.activeTokenIdx,ref:"menu",style:(_vm.suggestionsStyle),attrs:{"name":_vm.portalName,"slim":""}})]},proxy:true}],null,true)},'gl-search-box-by-click',_vm.$attrs,false))};
346
+ },attrs:{"config":_vm.getTokenEntry(token.type),"active":_vm.activeTokenIdx === idx,"cursor-position":_vm.intendedCursorPosition,"available-tokens":_vm.currentAvailableTokens,"current-value":_vm.tokens,"index":idx,"placeholder":_vm.termPlaceholder,"show-friendly-text":_vm.showFriendlyText,"search-input-attributes":_vm.searchInputAttributes,"is-last-token":_vm.isLastToken(idx)},on:{"activate":function($event){return _vm.activate(idx)},"deactivate":function($event){return _vm.deactivate(token)},"destroy":function($event){return _vm.destroyToken(idx, $event)},"replace":function($event){return _vm.replaceToken(idx, $event)},"complete":_vm.completeToken,"submit":_vm.submit,"split":function($event){return _vm.createTokens(idx, $event)},"previous":_vm.activatePreviousToken,"next":_vm.activateNextToken},model:{value:(token.value),callback:function ($$v) {_vm.$set(token, "value", $$v);},expression:"token.value"}})]})],2),_vm._v(" "),_c('portal-target',{key:_vm.activeTokenIdx,ref:"menu",style:(_vm.suggestionsStyle),attrs:{"name":_vm.portalName,"slim":""}})]},proxy:true}],null,true)},'gl-search-box-by-click',_vm.$attrs,false))};
331
347
  var __vue_staticRenderFns__ = [];
332
348
 
333
349
  /* style */
@@ -69,6 +69,11 @@ var script = {
69
69
  type: Array,
70
70
  required: false,
71
71
  default: () => []
72
+ },
73
+ cursorPosition: {
74
+ type: String,
75
+ required: true,
76
+ validator: value => ['start', 'end'].includes(value)
72
77
  }
73
78
  },
74
79
  computed: {
@@ -116,7 +121,7 @@ var script = {
116
121
  const __vue_script__ = script;
117
122
 
118
123
  /* template */
119
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-h-auto gl-filtered-search-term"},[_c('gl-filtered-search-token-segment',{staticClass:"gl-filtered-search-term-token",class:{ 'gl-w-full': _vm.placeholder },attrs:{"active":_vm.active,"search-input-attributes":_vm.searchInputAttributes,"is-last-token":_vm.isLastToken,"current-value":_vm.currentValue},on:{"activate":function($event){return _vm.$emit('activate')},"deactivate":function($event){return _vm.$emit('deactivate')},"complete":function($event){return _vm.$emit('replace', { type: $event })},"backspace":_vm.onBackspace,"submit":function($event){return _vm.$emit('submit')},"split":function($event){return _vm.$emit('split', $event)}},scopedSlots:_vm._u([{key:"suggestions",fn:function(){return _vm._l((_vm.suggestedTokens),function(item,idx){return _c('gl-filtered-search-suggestion',{key:idx,attrs:{"value":item.type,"icon-name":item.icon}},[_vm._v("\n "+_vm._s(item.title)+"\n ")])})},proxy:true},{key:"view",fn:function(){return [(_vm.placeholder)?_c('input',_vm._b({staticClass:"gl-filtered-search-term-input",attrs:{"placeholder":_vm.placeholder,"aria-label":_vm.placeholder}},'input',_vm.searchInputAttributes,false)):[_vm._v(_vm._s(_vm.value.data))]]},proxy:true}]),model:{value:(_vm.internalValue),callback:function ($$v) {_vm.internalValue=$$v;},expression:"internalValue"}})],1)};
124
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-h-auto gl-filtered-search-term",attrs:{"data-testid":"filtered-search-term"}},[_c('gl-filtered-search-token-segment',{ref:"segment",staticClass:"gl-filtered-search-term-token",class:{ 'gl-w-full': _vm.placeholder },attrs:{"active":_vm.active,"cursor-position":_vm.cursorPosition,"search-input-attributes":_vm.searchInputAttributes,"is-last-token":_vm.isLastToken,"current-value":_vm.currentValue},on:{"activate":function($event){return _vm.$emit('activate')},"deactivate":function($event){return _vm.$emit('deactivate')},"complete":function($event){return _vm.$emit('replace', { type: $event })},"backspace":_vm.onBackspace,"submit":function($event){return _vm.$emit('submit')},"split":function($event){return _vm.$emit('split', $event)},"previous":function($event){return _vm.$emit('previous')},"next":function($event){return _vm.$emit('next')}},scopedSlots:_vm._u([{key:"suggestions",fn:function(){return _vm._l((_vm.suggestedTokens),function(item,idx){return _c('gl-filtered-search-suggestion',{key:idx,attrs:{"value":item.type,"icon-name":item.icon}},[_vm._v("\n "+_vm._s(item.title)+"\n ")])})},proxy:true},{key:"view",fn:function(){return [(_vm.placeholder)?_c('input',_vm._b({staticClass:"gl-filtered-search-term-input",attrs:{"placeholder":_vm.placeholder,"aria-label":_vm.placeholder,"data-testid":"filtered-search-term-input"}},'input',_vm.searchInputAttributes,false)):[_vm._v(_vm._s(_vm.value.data))]]},proxy:true}]),model:{value:(_vm.internalValue),callback:function ($$v) {_vm.internalValue=$$v;},expression:"internalValue"}})],1)};
120
125
  var __vue_staticRenderFns__ = [];
121
126
 
122
127
  /* style */
@@ -73,13 +73,19 @@ var script = {
73
73
  type: Boolean,
74
74
  required: false,
75
75
  default: false
76
+ },
77
+ cursorPosition: {
78
+ type: String,
79
+ required: true,
80
+ validator: value => ['start', 'end'].includes(value)
76
81
  }
77
82
  },
78
83
 
79
84
  data() {
80
85
  return {
81
86
  activeSegment: null,
82
- tokenValue: _cloneDeep(this.value)
87
+ tokenValue: _cloneDeep(this.value),
88
+ intendedCursorPosition: this.cursorPosition
83
89
  };
84
90
  },
85
91
 
@@ -139,6 +145,8 @@ var script = {
139
145
 
140
146
  handler(newValue) {
141
147
  if (newValue) {
148
+ this.intendedCursorPosition = this.cursorPosition;
149
+
142
150
  if (!this.activeSegment) {
143
151
  this.activateSegment(this.tokenValue.data !== '' ? SEGMENT_DATA : SEGMENT_OPERATOR);
144
152
  }
@@ -274,6 +282,26 @@ var script = {
274
282
  this.activateSegment(this.$options.segments.SEGMENT_DATA);
275
283
  },
276
284
 
285
+ activatePreviousOperatorSegment() {
286
+ this.activateSegment(this.$options.segments.SEGMENT_OPERATOR);
287
+ this.intendedCursorPosition = 'end';
288
+ },
289
+
290
+ activatePreviousTitleSegment() {
291
+ this.activateSegment(this.$options.segments.SEGMENT_TITLE);
292
+ this.intendedCursorPosition = 'end';
293
+ },
294
+
295
+ activateNextDataSegment() {
296
+ this.activateDataSegment();
297
+ this.intendedCursorPosition = 'start';
298
+ },
299
+
300
+ activateNextOperatorSegment() {
301
+ this.activateSegment(this.$options.segments.SEGMENT_OPERATOR);
302
+ this.intendedCursorPosition = 'start';
303
+ },
304
+
277
305
  handleComplete() {
278
306
  if (this.config.multiSelect) {
279
307
  this.$emit('input', { ...this.tokenValue,
@@ -305,11 +333,11 @@ var script = {
305
333
  const __vue_script__ = script;
306
334
 
307
335
  /* template */
308
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-filtered-search-token",class:{ 'gl-filtered-search-token-active': _vm.active }},[_c('gl-filtered-search-token-segment',{key:"title-segment",attrs:{"value":_vm.config.title,"active":_vm.isSegmentActive(_vm.$options.segments.SEGMENT_TITLE),"options":_vm.availableTokensWithSelf},on:{"activate":function($event){return _vm.activateSegment(_vm.$options.segments.SEGMENT_TITLE)},"deactivate":function($event){return _vm.$emit('deactivate')},"complete":_vm.replaceToken,"backspace":function($event){return _vm.$emit('destroy')},"submit":function($event){return _vm.$emit('submit')}},scopedSlots:_vm._u([{key:"view",fn:function(ref){
336
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-filtered-search-token",class:{ 'gl-filtered-search-token-active': _vm.active },attrs:{"data-testid":"filtered-search-token"}},[_c('gl-filtered-search-token-segment',{key:"title-segment",attrs:{"value":_vm.config.title,"active":_vm.isSegmentActive(_vm.$options.segments.SEGMENT_TITLE),"cursor-position":_vm.intendedCursorPosition,"options":_vm.availableTokensWithSelf},on:{"activate":function($event){return _vm.activateSegment(_vm.$options.segments.SEGMENT_TITLE)},"deactivate":function($event){return _vm.$emit('deactivate')},"complete":_vm.replaceToken,"backspace":function($event){return _vm.$emit('destroy')},"submit":function($event){return _vm.$emit('submit')},"previous":function($event){return _vm.$emit('previous')},"next":_vm.activateNextOperatorSegment},scopedSlots:_vm._u([{key:"view",fn:function(ref){
309
337
  var inputValue = ref.inputValue;
310
- return [_c('gl-token',{staticClass:"gl-filtered-search-token-type",class:_vm.getAdditionalSegmentClasses(_vm.$options.segments.SEGMENT_TITLE),attrs:{"view-only":""}},[_vm._v(_vm._s(inputValue))])]}}])}),_vm._v(" "),_c('gl-filtered-search-token-segment',{key:"operator-segment",attrs:{"active":_vm.isSegmentActive(_vm.$options.segments.SEGMENT_OPERATOR),"options":_vm.operators,"custom-input-keydown-handler":_vm.handleOperatorKeydown,"view-only":""},on:{"activate":function($event){return _vm.activateSegment(_vm.$options.segments.SEGMENT_OPERATOR)},"backspace":_vm.replaceWithTermIfEmpty,"complete":function($event){return _vm.activateSegment(_vm.$options.segments.SEGMENT_DATA)},"deactivate":function($event){return _vm.$emit('deactivate')}},scopedSlots:_vm._u([{key:"view",fn:function(){return [_c('gl-token',{staticClass:"gl-filtered-search-token-operator",class:_vm.getAdditionalSegmentClasses(_vm.$options.segments.SEGMENT_OPERATOR),attrs:{"variant":"search-value","view-only":""}},[_vm._v(_vm._s(_vm.operatorDescription))])]},proxy:true},{key:"option",fn:function(ref){
338
+ return [_c('gl-token',{staticClass:"gl-filtered-search-token-type",class:_vm.getAdditionalSegmentClasses(_vm.$options.segments.SEGMENT_TITLE),attrs:{"view-only":""}},[_vm._v(_vm._s(inputValue))])]}}])}),_vm._v(" "),_c('gl-filtered-search-token-segment',{key:"operator-segment",attrs:{"active":_vm.isSegmentActive(_vm.$options.segments.SEGMENT_OPERATOR),"cursor-position":_vm.intendedCursorPosition,"options":_vm.operators,"custom-input-keydown-handler":_vm.handleOperatorKeydown,"view-only":""},on:{"activate":function($event){return _vm.activateSegment(_vm.$options.segments.SEGMENT_OPERATOR)},"backspace":_vm.replaceWithTermIfEmpty,"complete":function($event){return _vm.activateSegment(_vm.$options.segments.SEGMENT_DATA)},"deactivate":function($event){return _vm.$emit('deactivate')},"previous":_vm.activatePreviousTitleSegment,"next":_vm.activateNextDataSegment},scopedSlots:_vm._u([{key:"view",fn:function(){return [_c('gl-token',{staticClass:"gl-filtered-search-token-operator",class:_vm.getAdditionalSegmentClasses(_vm.$options.segments.SEGMENT_OPERATOR),attrs:{"variant":"search-value","view-only":""}},[_vm._v(_vm._s(_vm.operatorDescription))])]},proxy:true},{key:"option",fn:function(ref){
311
339
  var option = ref.option;
312
- return [_c('div',{staticClass:"gl-display-flex"},[_vm._v("\n "+_vm._s(option.value)+"\n "),(option.description)?_c('span',{staticClass:"gl-filtered-search-token-operator-description"},[_vm._v("\n "+_vm._s(option.description)+"\n ")]):_vm._e()])]}}]),model:{value:(_vm.tokenValue.operator),callback:function ($$v) {_vm.$set(_vm.tokenValue, "operator", $$v);},expression:"tokenValue.operator"}}),_vm._v(" "),(_vm.hasDataOrDataSegmentIsCurrentlyActive)?_c('gl-filtered-search-token-segment',{key:"data-segment",attrs:{"active":_vm.isSegmentActive(_vm.$options.segments.SEGMENT_DATA),"multi-select":_vm.config.multiSelect,"options":_vm.config.options,"option-text-field":"title"},on:{"activate":_vm.activateDataSegment,"backspace":function($event){return _vm.activateSegment(_vm.$options.segments.SEGMENT_OPERATOR)},"complete":_vm.handleComplete,"select":function($event){return _vm.$emit('select', $event)},"submit":function($event){return _vm.$emit('submit')},"deactivate":function($event){return _vm.$emit('deactivate')},"split":function($event){return _vm.$emit('split', $event)}},scopedSlots:_vm._u([{key:"suggestions",fn:function(){return [_vm._t("suggestions")]},proxy:true},{key:"view",fn:function(ref){
340
+ return [_c('div',{staticClass:"gl-display-flex"},[_vm._v("\n "+_vm._s(option.value)+"\n "),(option.description)?_c('span',{staticClass:"gl-filtered-search-token-operator-description"},[_vm._v("\n "+_vm._s(option.description)+"\n ")]):_vm._e()])]}}]),model:{value:(_vm.tokenValue.operator),callback:function ($$v) {_vm.$set(_vm.tokenValue, "operator", $$v);},expression:"tokenValue.operator"}}),_vm._v(" "),(_vm.hasDataOrDataSegmentIsCurrentlyActive)?_c('gl-filtered-search-token-segment',{key:"data-segment",attrs:{"active":_vm.isSegmentActive(_vm.$options.segments.SEGMENT_DATA),"cursor-position":_vm.intendedCursorPosition,"multi-select":_vm.config.multiSelect,"options":_vm.config.options,"option-text-field":"title"},on:{"activate":_vm.activateDataSegment,"backspace":function($event){return _vm.activateSegment(_vm.$options.segments.SEGMENT_OPERATOR)},"complete":_vm.handleComplete,"select":function($event){return _vm.$emit('select', $event)},"submit":function($event){return _vm.$emit('submit')},"deactivate":function($event){return _vm.$emit('deactivate')},"split":function($event){return _vm.$emit('split', $event)},"previous":_vm.activatePreviousOperatorSegment,"next":function($event){return _vm.$emit('next')}},scopedSlots:_vm._u([{key:"suggestions",fn:function(){return [_vm._t("suggestions")]},proxy:true},{key:"view",fn:function(ref){
313
341
  var inputValue = ref.inputValue;
314
342
  return [_vm._t("view-token",[_c('gl-token',{staticClass:"gl-filtered-search-token-data",class:_vm.getAdditionalSegmentClasses(_vm.$options.segments.SEGMENT_DATA),attrs:{"variant":"search-value"},on:{"mousedown":_vm.destroyByClose}},[_c('span',{staticClass:"gl-filtered-search-token-data-content"},[_vm._t("view",[_vm._v(_vm._s(inputValue))],null,{ inputValue: inputValue })],2)])],null,{
315
343
  inputValue: inputValue,
@@ -79,6 +79,11 @@ var script = {
79
79
  type: Array,
80
80
  required: false,
81
81
  default: () => []
82
+ },
83
+ cursorPosition: {
84
+ type: String,
85
+ required: true,
86
+ validator: value => ['start', 'end'].includes(value)
82
87
  }
83
88
  },
84
89
 
@@ -218,6 +223,10 @@ var script = {
218
223
  inline: 'end'
219
224
  });
220
225
  this.alignSuggestions(input);
226
+
227
+ if (this.cursorPosition === 'start') {
228
+ input === null || input === void 0 ? void 0 : input.setSelectionRange(0, 0);
229
+ }
221
230
  }
222
231
  });
223
232
  },
@@ -255,24 +264,33 @@ var script = {
255
264
  key
256
265
  } = e;
257
266
  const {
258
- suggestions
267
+ suggestions,
268
+ input
259
269
  } = this.$refs;
260
270
  const suggestedValue = suggestions === null || suggestions === void 0 ? void 0 : suggestions.getValue();
261
-
262
- if (key === 'Backspace') {
263
- if (this.inputValue === '') {
264
- e.preventDefault();
265
- /**
266
- * Emitted when Backspace is pressed and the value is empty
267
- */
268
-
269
- this.$emit('backspace');
270
- }
271
-
272
- return;
273
- }
274
-
275
271
  const handlers = {
272
+ ArrowLeft: () => {
273
+ if (input.selectionStart === 0) {
274
+ e.preventDefault();
275
+ this.$emit('previous');
276
+ }
277
+ },
278
+ ArrowRight: () => {
279
+ if (input.selectionEnd === this.inputValue.length) {
280
+ e.preventDefault();
281
+ this.$emit('next');
282
+ }
283
+ },
284
+ Backspace: () => {
285
+ if (this.inputValue === '') {
286
+ e.preventDefault();
287
+ /**
288
+ * Emitted when Backspace is pressed and the value is empty
289
+ */
290
+
291
+ this.$emit('backspace');
292
+ }
293
+ },
276
294
  Enter: () => {
277
295
  e.preventDefault();
278
296
 
@@ -341,7 +359,9 @@ var script = {
341
359
  const __vue_script__ = script;
342
360
 
343
361
  /* template */
344
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',_vm._b({staticClass:"gl-filtered-search-token-segment",class:{ 'gl-filtered-search-token-segment-active': _vm.active },attrs:{"data-testid":"filtered-search-token-segment"},on:{"mousedown":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"left",37,$event.key,["Left","ArrowLeft"])){ return null; }if('button' in $event && $event.button !== 0){ return null; }return _vm.emitIfInactive($event)}}},'div',_vm.containerAttributes,false),[(_vm.active)?[(((_vm.searchInputAttributes).type)==='checkbox')?_c('input',_vm._b({directives:[{name:"model",rawName:"v-model",value:(_vm.inputValue),expression:"inputValue"}],ref:"input",staticClass:"gl-filtered-search-token-segment-input",attrs:{"aria-label":_vm.label,"type":"checkbox"},domProps:{"checked":Array.isArray(_vm.inputValue)?_vm._i(_vm.inputValue,null)>-1:(_vm.inputValue)},on:{"keydown":_vm.handleInputKeydown,"blur":_vm.handleBlur,"change":function($event){var $$a=_vm.inputValue,$$el=$event.target,$$c=$$el.checked?(true):(false);if(Array.isArray($$a)){var $$v=null,$$i=_vm._i($$a,$$v);if($$el.checked){$$i<0&&(_vm.inputValue=$$a.concat([$$v]));}else {$$i>-1&&(_vm.inputValue=$$a.slice(0,$$i).concat($$a.slice($$i+1)));}}else {_vm.inputValue=$$c;}}}},'input',_vm.searchInputAttributes,false)):(((_vm.searchInputAttributes).type)==='radio')?_c('input',_vm._b({directives:[{name:"model",rawName:"v-model",value:(_vm.inputValue),expression:"inputValue"}],ref:"input",staticClass:"gl-filtered-search-token-segment-input",attrs:{"aria-label":_vm.label,"type":"radio"},domProps:{"checked":_vm._q(_vm.inputValue,null)},on:{"keydown":_vm.handleInputKeydown,"blur":_vm.handleBlur,"change":function($event){_vm.inputValue=null;}}},'input',_vm.searchInputAttributes,false)):_c('input',_vm._b({directives:[{name:"model",rawName:"v-model",value:(_vm.inputValue),expression:"inputValue"}],ref:"input",staticClass:"gl-filtered-search-token-segment-input",attrs:{"aria-label":_vm.label,"type":(_vm.searchInputAttributes).type},domProps:{"value":(_vm.inputValue)},on:{"keydown":_vm.handleInputKeydown,"blur":_vm.handleBlur,"input":function($event){if($event.target.composing){ return; }_vm.inputValue=$event.target.value;}}},'input',_vm.searchInputAttributes,false)),_vm._v(" "),_c('portal',{key:("operator-" + _vm._uid),attrs:{"to":_vm.portalName}},[(_vm.hasOptionsOrSuggestions)?_c('gl-filtered-search-suggestion-list',{key:("operator-" + _vm._uid),ref:"suggestions",attrs:{"initial-value":_vm.defaultSuggestedValue},on:{"suggestion":_vm.applySuggestion}},[(_vm.options)?_vm._l((_vm.options),function(option,idx){return _c('gl-filtered-search-suggestion',{key:((option.value) + "-" + idx),attrs:{"value":option.value,"icon-name":option.icon}},[_vm._t("option",[_vm._v("\n "+_vm._s(option[_vm.optionTextField])+"\n ")],null,{ option: option })],2)}):_vm._t("suggestions")],2):_vm._e()],1)]:_vm._t("view",[_vm._v(_vm._s(_vm.inputValue))],null,{ inputValue: _vm.inputValue })],2)};
362
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',_vm._b({staticClass:"gl-filtered-search-token-segment",class:{
363
+ 'gl-filtered-search-token-segment-active': _vm.active,
364
+ },attrs:{"data-testid":"filtered-search-token-segment"},on:{"mousedown":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"left",37,$event.key,["Left","ArrowLeft"])){ return null; }if('button' in $event && $event.button !== 0){ return null; }return _vm.emitIfInactive($event)}}},'div',_vm.containerAttributes,false),[(_vm.active)?[(((_vm.searchInputAttributes).type)==='checkbox')?_c('input',_vm._b({directives:[{name:"model",rawName:"v-model",value:(_vm.inputValue),expression:"inputValue"}],ref:"input",staticClass:"gl-filtered-search-token-segment-input",attrs:{"aria-label":_vm.label,"type":"checkbox"},domProps:{"checked":Array.isArray(_vm.inputValue)?_vm._i(_vm.inputValue,null)>-1:(_vm.inputValue)},on:{"keydown":_vm.handleInputKeydown,"blur":_vm.handleBlur,"change":function($event){var $$a=_vm.inputValue,$$el=$event.target,$$c=$$el.checked?(true):(false);if(Array.isArray($$a)){var $$v=null,$$i=_vm._i($$a,$$v);if($$el.checked){$$i<0&&(_vm.inputValue=$$a.concat([$$v]));}else {$$i>-1&&(_vm.inputValue=$$a.slice(0,$$i).concat($$a.slice($$i+1)));}}else {_vm.inputValue=$$c;}}}},'input',_vm.searchInputAttributes,false)):(((_vm.searchInputAttributes).type)==='radio')?_c('input',_vm._b({directives:[{name:"model",rawName:"v-model",value:(_vm.inputValue),expression:"inputValue"}],ref:"input",staticClass:"gl-filtered-search-token-segment-input",attrs:{"aria-label":_vm.label,"type":"radio"},domProps:{"checked":_vm._q(_vm.inputValue,null)},on:{"keydown":_vm.handleInputKeydown,"blur":_vm.handleBlur,"change":function($event){_vm.inputValue=null;}}},'input',_vm.searchInputAttributes,false)):_c('input',_vm._b({directives:[{name:"model",rawName:"v-model",value:(_vm.inputValue),expression:"inputValue"}],ref:"input",staticClass:"gl-filtered-search-token-segment-input",attrs:{"aria-label":_vm.label,"type":(_vm.searchInputAttributes).type},domProps:{"value":(_vm.inputValue)},on:{"keydown":_vm.handleInputKeydown,"blur":_vm.handleBlur,"input":function($event){if($event.target.composing){ return; }_vm.inputValue=$event.target.value;}}},'input',_vm.searchInputAttributes,false)),_vm._v(" "),_c('portal',{key:("operator-" + _vm._uid),attrs:{"to":_vm.portalName}},[(_vm.hasOptionsOrSuggestions)?_c('gl-filtered-search-suggestion-list',{key:("operator-" + _vm._uid),ref:"suggestions",attrs:{"initial-value":_vm.defaultSuggestedValue},on:{"suggestion":_vm.applySuggestion}},[(_vm.options)?_vm._l((_vm.options),function(option,idx){return _c('gl-filtered-search-suggestion',{key:((option.value) + "-" + idx),attrs:{"value":option.value,"icon-name":option.icon}},[_vm._t("option",[_vm._v("\n "+_vm._s(option[_vm.optionTextField])+"\n ")],null,{ option: option })],2)}):_vm._t("suggestions")],2):_vm._e()],1)]:_vm._t("view",[_vm._v(_vm._s(_vm.inputValue))],null,{ inputValue: _vm.inputValue })],2)};
345
365
  var __vue_staticRenderFns__ = [];
346
366
 
347
367
  /* style */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "38.10.2",
3
+ "version": "38.11.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -92,15 +92,15 @@
92
92
  "@rollup/plugin-commonjs": "^11.1.0",
93
93
  "@rollup/plugin-node-resolve": "^7.1.3",
94
94
  "@rollup/plugin-replace": "^2.3.2",
95
- "@storybook/addon-a11y": "6.4.20",
96
- "@storybook/addon-docs": "6.4.20",
97
- "@storybook/addon-essentials": "6.4.20",
95
+ "@storybook/addon-a11y": "6.4.22",
96
+ "@storybook/addon-docs": "6.4.22",
97
+ "@storybook/addon-essentials": "6.4.22",
98
98
  "@storybook/addon-knobs": "6.4.0",
99
- "@storybook/addon-storyshots": "6.4.20",
100
- "@storybook/addon-storyshots-puppeteer": "6.4.20",
101
- "@storybook/addon-viewport": "6.4.20",
102
- "@storybook/theming": "6.4.20",
103
- "@storybook/vue": "6.4.20",
99
+ "@storybook/addon-storyshots": "6.4.22",
100
+ "@storybook/addon-storyshots-puppeteer": "6.4.22",
101
+ "@storybook/addon-viewport": "6.4.22",
102
+ "@storybook/theming": "6.4.22",
103
+ "@storybook/vue": "6.4.22",
104
104
  "@vue/test-utils": "1.3.0",
105
105
  "autoprefixer": "^9.7.6",
106
106
  "babel-jest": "^26.6.3",
@@ -1,13 +1,13 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`Filtered search term renders input with value in active mode 1`] = `
4
- <div class="gl-h-auto gl-filtered-search-term">
5
- <div active="true" value="test-value" class="gl-filtered-search-term-token">test-value</div>
4
+ <div data-testid="filtered-search-term" class="gl-h-auto gl-filtered-search-term">
5
+ <div active="true" cursor-position="end" value="test-value" class="gl-filtered-search-term-token">test-value</div>
6
6
  </div>
7
7
  `;
8
8
 
9
9
  exports[`Filtered search term renders value in inactive mode 1`] = `
10
- <div class="gl-h-auto gl-filtered-search-term">
11
- <div value="test-value" class="gl-filtered-search-term-token">test-value</div>
10
+ <div data-testid="filtered-search-term" class="gl-h-auto gl-filtered-search-term">
11
+ <div cursor-position="end" value="test-value" class="gl-filtered-search-term-token">test-value</div>
12
12
  </div>
13
13
  `;
@@ -9,6 +9,7 @@ import {
9
9
  GlIcon,
10
10
  GlToken,
11
11
  GlAvatar,
12
+ GlDropdownDivider,
12
13
  } from '../../../index';
13
14
  import { setStoryTimeout } from '../../../utils/test_utils';
14
15
  import { makeContainer } from '../../../utils/story_decorators/container';
@@ -178,7 +179,13 @@ const MilestoneToken = {
178
179
 
179
180
  const LabelToken = {
180
181
  name: 'LabelToken',
181
- components: { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon, GlToken },
182
+ components: {
183
+ GlFilteredSearchToken,
184
+ GlFilteredSearchSuggestion,
185
+ GlLoadingIcon,
186
+ GlToken,
187
+ GlDropdownDivider,
188
+ },
182
189
  props: ['value', 'active'],
183
190
  inheritAttrs: false,
184
191
  data() {
@@ -119,6 +119,7 @@ export default {
119
119
  tokens: initialState(),
120
120
  activeTokenIdx: null,
121
121
  suggestionsStyle: {},
122
+ intendedCursorPosition: 'end',
122
123
  };
123
124
  },
124
125
  computed: {
@@ -210,6 +211,20 @@ export default {
210
211
  this.activeTokenIdx = idx;
211
212
  },
212
213
 
214
+ activatePreviousToken() {
215
+ if (this.activeTokenIdx > 0) {
216
+ this.activeTokenIdx -= 1;
217
+ this.intendedCursorPosition = 'end';
218
+ }
219
+ },
220
+
221
+ activateNextToken() {
222
+ if (this.activeTokenIdx < this.value.length) {
223
+ this.activeTokenIdx += 1;
224
+ this.intendedCursorPosition = 'start';
225
+ }
226
+ },
227
+
213
228
  alignSuggestions(ref) {
214
229
  const offsetRef = ref.getBoundingClientRect().left;
215
230
  const offsetMenu = this.$el.getBoundingClientRect().left;
@@ -218,6 +233,7 @@ export default {
218
233
  },
219
234
 
220
235
  deactivate(token) {
236
+ this.intendedCursorPosition = 'end';
221
237
  const tokenIdx = this.tokens.indexOf(token);
222
238
  if (tokenIdx === -1 || this.activeTokenIdx !== tokenIdx) {
223
239
  return;
@@ -341,6 +357,7 @@ export default {
341
357
  v-model="token.value"
342
358
  :config="getTokenEntry(token.type)"
343
359
  :active="activeTokenIdx === idx"
360
+ :cursor-position="intendedCursorPosition"
344
361
  :available-tokens="currentAvailableTokens"
345
362
  :current-value="tokens"
346
363
  :index="idx"
@@ -359,6 +376,8 @@ export default {
359
376
  @complete="completeToken"
360
377
  @submit="submit"
361
378
  @split="createTokens(idx, $event)"
379
+ @previous="activatePreviousToken"
380
+ @next="activateNextToken"
362
381
  />
363
382
  </template>
364
383
  </div>
@@ -17,6 +17,7 @@ describe('Filtered search term', () => {
17
17
 
18
18
  const defaultProps = {
19
19
  availableTokens: [],
20
+ cursorPosition: 'end',
20
21
  };
21
22
 
22
23
  const segmentStub = {
@@ -63,6 +63,11 @@ export default {
63
63
  required: false,
64
64
  default: () => [],
65
65
  },
66
+ cursorPosition: {
67
+ type: String,
68
+ required: true,
69
+ validator: (value) => ['start', 'end'].includes(value),
70
+ },
66
71
  },
67
72
  computed: {
68
73
  suggestedTokens() {
@@ -101,7 +106,7 @@ export default {
101
106
  </script>
102
107
 
103
108
  <template>
104
- <div class="gl-h-auto gl-filtered-search-term">
109
+ <div class="gl-h-auto gl-filtered-search-term" data-testid="filtered-search-term">
105
110
  <!--
106
111
  Emitted when this term token is clicked.
107
112
  @event activate
@@ -125,9 +130,11 @@ export default {
125
130
  @property {array} newTokens Token configurations
126
131
  -->
127
132
  <gl-filtered-search-token-segment
133
+ ref="segment"
128
134
  v-model="internalValue"
129
135
  class="gl-filtered-search-term-token"
130
136
  :active="active"
137
+ :cursor-position="cursorPosition"
131
138
  :class="{ 'gl-w-full': placeholder }"
132
139
  :search-input-attributes="searchInputAttributes"
133
140
  :is-last-token="isLastToken"
@@ -138,6 +145,8 @@ export default {
138
145
  @backspace="onBackspace"
139
146
  @submit="$emit('submit')"
140
147
  @split="$emit('split', $event)"
148
+ @previous="$emit('previous')"
149
+ @next="$emit('next')"
141
150
  >
142
151
  <template #suggestions>
143
152
  <gl-filtered-search-suggestion
@@ -156,6 +165,7 @@ export default {
156
165
  class="gl-filtered-search-term-input"
157
166
  :placeholder="placeholder"
158
167
  :aria-label="placeholder"
168
+ data-testid="filtered-search-term-input"
159
169
  />
160
170
  <template v-else>{{ value.data }}</template>
161
171
  </template>
@@ -30,6 +30,7 @@ describe('Filtered search token', () => {
30
30
  const defaultProps = {
31
31
  config: { title: 'testTitle' },
32
32
  availableTokens,
33
+ cursorPosition: 'end',
33
34
  };
34
35
 
35
36
  const createComponent = (props) => {
@@ -65,11 +65,17 @@ export default {
65
65
  required: false,
66
66
  default: false,
67
67
  },
68
+ cursorPosition: {
69
+ type: String,
70
+ required: true,
71
+ validator: (value) => ['start', 'end'].includes(value),
72
+ },
68
73
  },
69
74
  data() {
70
75
  return {
71
76
  activeSegment: null,
72
77
  tokenValue: cloneDeep(this.value),
78
+ intendedCursorPosition: this.cursorPosition,
73
79
  };
74
80
  },
75
81
 
@@ -127,6 +133,7 @@ export default {
127
133
  immediate: true,
128
134
  handler(newValue) {
129
135
  if (newValue) {
136
+ this.intendedCursorPosition = this.cursorPosition;
130
137
  if (!this.activeSegment) {
131
138
  this.activateSegment(this.tokenValue.data !== '' ? SEGMENT_DATA : SEGMENT_OPERATOR);
132
139
  }
@@ -239,6 +246,26 @@ export default {
239
246
  this.activateSegment(this.$options.segments.SEGMENT_DATA);
240
247
  },
241
248
 
249
+ activatePreviousOperatorSegment() {
250
+ this.activateSegment(this.$options.segments.SEGMENT_OPERATOR);
251
+ this.intendedCursorPosition = 'end';
252
+ },
253
+
254
+ activatePreviousTitleSegment() {
255
+ this.activateSegment(this.$options.segments.SEGMENT_TITLE);
256
+ this.intendedCursorPosition = 'end';
257
+ },
258
+
259
+ activateNextDataSegment() {
260
+ this.activateDataSegment();
261
+ this.intendedCursorPosition = 'start';
262
+ },
263
+
264
+ activateNextOperatorSegment() {
265
+ this.activateSegment(this.$options.segments.SEGMENT_OPERATOR);
266
+ this.intendedCursorPosition = 'start';
267
+ },
268
+
242
269
  handleComplete() {
243
270
  if (this.config.multiSelect) {
244
271
  this.$emit('input', { ...this.tokenValue, data: this.multiSelectValues.join(COMMA) });
@@ -263,7 +290,11 @@ export default {
263
290
  </script>
264
291
 
265
292
  <template>
266
- <div class="gl-filtered-search-token" :class="{ 'gl-filtered-search-token-active': active }">
293
+ <div
294
+ class="gl-filtered-search-token"
295
+ :class="{ 'gl-filtered-search-token-active': active }"
296
+ data-testid="filtered-search-token"
297
+ >
267
298
  <!--
268
299
  Emitted when the token is submitted.
269
300
  @event submit
@@ -272,12 +303,15 @@ export default {
272
303
  key="title-segment"
273
304
  :value="config.title"
274
305
  :active="isSegmentActive($options.segments.SEGMENT_TITLE)"
306
+ :cursor-position="intendedCursorPosition"
275
307
  :options="availableTokensWithSelf"
276
308
  @activate="activateSegment($options.segments.SEGMENT_TITLE)"
277
309
  @deactivate="$emit('deactivate')"
278
310
  @complete="replaceToken"
279
311
  @backspace="$emit('destroy')"
280
312
  @submit="$emit('submit')"
313
+ @previous="$emit('previous')"
314
+ @next="activateNextOperatorSegment"
281
315
  >
282
316
  <template #view="{ inputValue }">
283
317
  <gl-token
@@ -292,6 +326,7 @@ export default {
292
326
  key="operator-segment"
293
327
  v-model="tokenValue.operator"
294
328
  :active="isSegmentActive($options.segments.SEGMENT_OPERATOR)"
329
+ :cursor-position="intendedCursorPosition"
295
330
  :options="operators"
296
331
  :custom-input-keydown-handler="handleOperatorKeydown"
297
332
  view-only
@@ -299,6 +334,8 @@ export default {
299
334
  @backspace="replaceWithTermIfEmpty"
300
335
  @complete="activateSegment($options.segments.SEGMENT_DATA)"
301
336
  @deactivate="$emit('deactivate')"
337
+ @previous="activatePreviousTitleSegment"
338
+ @next="activateNextDataSegment"
302
339
  >
303
340
  <template #view>
304
341
  <gl-token
@@ -333,6 +370,7 @@ export default {
333
370
  key="data-segment"
334
371
  v-model="tokenValue.data"
335
372
  :active="isSegmentActive($options.segments.SEGMENT_DATA)"
373
+ :cursor-position="intendedCursorPosition"
336
374
  :multi-select="config.multiSelect"
337
375
  :options="config.options"
338
376
  option-text-field="title"
@@ -343,6 +381,8 @@ export default {
343
381
  @submit="$emit('submit')"
344
382
  @deactivate="$emit('deactivate')"
345
383
  @split="$emit('split', $event)"
384
+ @previous="activatePreviousOperatorSegment"
385
+ @next="$emit('next')"
346
386
  >
347
387
  <template #suggestions>
348
388
  <!-- @slot The suggestions (implemented with GlFilteredSearchSuggestion). -->
@@ -35,7 +35,7 @@ describe('Filtered search token segment', () => {
35
35
  };
36
36
 
37
37
  wrapper = shallowMount(GlFilteredSearchTokenSegment, {
38
- propsData: props,
38
+ propsData: { ...props, cursorPosition: 'end' },
39
39
  provide: {
40
40
  portalName: 'fakePortal',
41
41
  alignSuggestions: alignSuggestionsMock,
@@ -64,7 +64,7 @@ describe('Filtered search token segment', () => {
64
64
  };
65
65
 
66
66
  wrapper = shallowMount(fakeParent, {
67
- propsData: otherProps,
67
+ propsData: { ...otherProps, cursorPosition: 'end' },
68
68
  stubs: { GlFilteredSearchTokenSegment },
69
69
  });
70
70
  };
@@ -77,6 +77,11 @@ export default {
77
77
  required: false,
78
78
  default: () => [],
79
79
  },
80
+ cursorPosition: {
81
+ type: String,
82
+ required: true,
83
+ validator: (value) => ['start', 'end'].includes(value),
84
+ },
80
85
  },
81
86
 
82
87
  data() {
@@ -198,6 +203,9 @@ export default {
198
203
  input.focus();
199
204
  input.scrollIntoView({ block: 'nearest', inline: 'end' });
200
205
  this.alignSuggestions(input);
206
+ if (this.cursorPosition === 'start') {
207
+ input?.setSelectionRange(0, 0);
208
+ }
201
209
  }
202
210
  });
203
211
  },
@@ -230,20 +238,31 @@ export default {
230
238
 
231
239
  handleInputKeydown(e) {
232
240
  const { key } = e;
233
- const { suggestions } = this.$refs;
241
+ const { suggestions, input } = this.$refs;
234
242
  const suggestedValue = suggestions?.getValue();
235
- if (key === 'Backspace') {
236
- if (this.inputValue === '') {
237
- e.preventDefault();
238
- /**
239
- * Emitted when Backspace is pressed and the value is empty
240
- */
241
- this.$emit('backspace');
242
- }
243
- return;
244
- }
245
243
 
246
244
  const handlers = {
245
+ ArrowLeft: () => {
246
+ if (input.selectionStart === 0) {
247
+ e.preventDefault();
248
+ this.$emit('previous');
249
+ }
250
+ },
251
+ ArrowRight: () => {
252
+ if (input.selectionEnd === this.inputValue.length) {
253
+ e.preventDefault();
254
+ this.$emit('next');
255
+ }
256
+ },
257
+ Backspace: () => {
258
+ if (this.inputValue === '') {
259
+ e.preventDefault();
260
+ /**
261
+ * Emitted when Backspace is pressed and the value is empty
262
+ */
263
+ this.$emit('backspace');
264
+ }
265
+ },
247
266
  Enter: () => {
248
267
  e.preventDefault();
249
268
  if (suggestedValue != null) {
@@ -311,7 +330,9 @@ export default {
311
330
  <div
312
331
  v-bind="containerAttributes"
313
332
  class="gl-filtered-search-token-segment"
314
- :class="{ 'gl-filtered-search-token-segment-active': active }"
333
+ :class="{
334
+ 'gl-filtered-search-token-segment-active': active,
335
+ }"
315
336
  data-testid="filtered-search-token-segment"
316
337
  @mousedown.left="emitIfInactive"
317
338
  >