@gitlab/ui 87.2.1 → 87.3.1

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,17 @@
1
+ ## [87.3.1](https://gitlab.com/gitlab-org/gitlab-ui/compare/v87.3.0...v87.3.1) (2024-07-30)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add missing Tailwind equivalent for `gl-flex-shrink-0` ([5e20414](https://gitlab.com/gitlab-org/gitlab-ui/commit/5e20414a18ddd14247862baaf3eedc5390683e82))
7
+
8
+ # [87.3.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v87.2.1...v87.3.0) (2024-07-30)
9
+
10
+
11
+ ### Features
12
+
13
+ * **GlTokenSelector:** Add `showAddNewAlways` prop ([89ae31c](https://gitlab.com/gitlab-org/gitlab-ui/commit/89ae31c903054f2b1866a692772799d94299e76c))
14
+
1
15
  ## [87.2.1](https://gitlab.com/gitlab-org/gitlab-ui/compare/v87.2.0...v87.2.1) (2024-07-28)
2
16
 
3
17
 
@@ -183900,6 +183900,7 @@ var tailwindEquivalents = {
183900
183900
  "gl-md-flex-direction-row-reverse": "md:gl-flex-row-reverse",
183901
183901
  "gl-flex-grow-0!": "!gl-grow-0",
183902
183902
  "gl-flex-grow-1": "gl-grow",
183903
+ "gl-flex-shrink-0": "gl-shrink-0",
183903
183904
  "gl-md-flex-grow-0": "md:gl-grow-0",
183904
183905
  "gl-flex-basis-0": "gl-basis-0",
183905
183906
  "gl-flex-basis-quarter": "gl-basis-1/4",
@@ -34,6 +34,14 @@ var script = {
34
34
  required: false,
35
35
  default: false
36
36
  },
37
+ /**
38
+ * Shows 'Add new token option' in dropdown even if results are present, requires allowUserDefinedTokens to be true
39
+ */
40
+ showAddNewAlways: {
41
+ type: Boolean,
42
+ required: false,
43
+ default: false
44
+ },
37
45
  /**
38
46
  * Dropdown items are loading, can be used when requesting new dropdown items
39
47
  */
@@ -158,7 +166,10 @@ var script = {
158
166
  return !this.filteredDropdownItems.length;
159
167
  },
160
168
  userDefinedTokenCanBeAdded() {
161
- return this.allowUserDefinedTokens && this.dropdownHasNoItems && this.inputText !== '';
169
+ if (!this.allowUserDefinedTokens || !this.inputText) {
170
+ return false;
171
+ }
172
+ return this.showAddNewAlways || this.dropdownHasNoItems;
162
173
  },
163
174
  hideDropdown() {
164
175
  if (this.userDefinedTokenCanBeAdded) {
@@ -250,9 +261,7 @@ var script = {
250
261
  });
251
262
  },
252
263
  handleEnter() {
253
- if (this.userDefinedTokenCanBeAdded) {
254
- this.addToken();
255
- } else if (this.focusedDropdownItem && this.dropdownIsOpen) {
264
+ if (this.focusedDropdownItem && this.dropdownIsOpen) {
256
265
  this.addToken(this.focusedDropdownItem);
257
266
  }
258
267
  },
@@ -290,19 +299,13 @@ var script = {
290
299
  }
291
300
  this.focusTextInput();
292
301
  },
293
- addToken() {
294
- let dropdownItem = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
295
- const token = dropdownItem !== null ? dropdownItem : {
296
- id: uniqueId('user-defined-token'),
297
- name: this.inputText
298
- };
299
-
302
+ addToken(dropdownItem) {
300
303
  /**
301
304
  * Fired when a token is added or removed
302
305
  *
303
306
  * @property {array} selectedTokens
304
307
  */
305
- this.$emit('input', [...this.selectedTokens, token]);
308
+ this.$emit('input', [...this.selectedTokens, dropdownItem]);
306
309
  this.inputText = '';
307
310
  this.closeDropdown();
308
311
 
@@ -311,13 +314,13 @@ var script = {
311
314
  *
312
315
  * @property {object} token
313
316
  */
314
- this.$emit('token-add', token);
317
+ this.$emit('token-add', dropdownItem);
315
318
  },
316
319
  removeToken(token) {
317
320
  /**
318
- * Fired when user types in the token selector
321
+ * Fired when a token is added or removed
319
322
  *
320
- * @property {string} inputText
323
+ * @property {array} selectedTokens
321
324
  */
322
325
  this.$emit('input', this.selectedTokens.filter(selectedToken => selectedToken.id !== token.id));
323
326
  /**
@@ -366,7 +369,7 @@ const __vue_script__ = script;
366
369
  /* template */
367
370
  var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',[_c('div',{ref:"container",staticClass:"gl-token-selector gl-form-input form-control form-control-plaintext gl-flex !gl-cursor-text gl-items-center !gl-px-3 !gl-py-2",class:[_vm.inputFocused ? 'gl-token-selector-focus-glow' : '', _vm.containerClass, _vm.stateClass],on:{"click":_vm.handleContainerClick}},[(_vm.showEmptyPlaceholder)?_vm._t("empty-placeholder"):_vm._e(),_vm._v(" "),_c('gl-token-container',{attrs:{"tokens":_vm.selectedTokens,"state":_vm.state,"register-focus-on-token":_vm.registerFocusOnToken,"view-only":_vm.viewOnly,"show-clear-all-button":_vm.showClearAllButton},on:{"token-remove":_vm.removeToken,"cancel-focus":_vm.cancelTokenFocus,"clear-all":_vm.clearAll},scopedSlots:_vm._u([{key:"token-content",fn:function(ref){
368
371
  var token = ref.token;
369
- return [_vm._t("token-content",null,{"token":token})]}},{key:"text-input",fn:function(){return [_c('input',_vm._b({ref:"textInput",staticClass:"gl-token-selector-input gl-h-auto gl-w-4/10 gl-grow gl-border-none gl-bg-transparent gl-px-1 gl-font-regular gl-text-base gl-leading-normal gl-text-gray-900 gl-outline-none",attrs:{"type":"text","autocomplete":_vm.autocomplete,"aria-labelledby":_vm.ariaLabelledby,"placeholder":_vm.placeholder,"disabled":_vm.viewOnly},domProps:{"value":_vm.inputText},on:{"input":function($event){_vm.inputText = $event.target.value;},"focus":_vm.handleFocus,"blur":_vm.handleBlur,"keydown":[function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"enter",13,$event.key,"Enter")){ return null; }return _vm.handleEnter.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"esc",27,$event.key,["Esc","Escape"])){ return null; }return _vm.handleEscape.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"delete",[8,46],$event.key,["Backspace","Delete","Del"])){ return null; }return _vm.handleBackspace.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"up",38,$event.key,["Up","ArrowUp"])){ return null; }$event.preventDefault();return _vm.dropdownEventHandlers.handleUpArrow.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"down",40,$event.key,["Down","ArrowDown"])){ return null; }$event.preventDefault();return _vm.dropdownEventHandlers.handleDownArrow.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"home",undefined,$event.key,undefined)){ return null; }return _vm.dropdownEventHandlers.handleHomeKey.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"end",undefined,$event.key,undefined)){ return null; }return _vm.dropdownEventHandlers.handleEndKey.apply(null, arguments)},function($event){$event.stopPropagation();return _vm.$emit('keydown', $event)}],"click":_vm.handleInputClick}},'input',_vm.textInputAttrs,false))]},proxy:true}],null,true)})],2),_vm._v(" "),_c('gl-token-selector-dropdown',{attrs:{"menu-class":_vm.menuClass,"show":_vm.dropdownIsOpen,"loading":_vm.loading,"dropdown-items":_vm.filteredDropdownItems,"selected-tokens":_vm.selectedTokens,"input-text":_vm.inputText,"allow-user-defined-tokens":_vm.allowUserDefinedTokens,"component-id":_vm.$options.componentId,"register-dropdown-event-handlers":_vm.registerDropdownEventHandlers,"register-reset-focused-dropdown-item":_vm.registerResetFocusedDropdownItem},on:{"dropdown-item-click":_vm.addToken,"show":_vm.openDropdown},scopedSlots:_vm._u([{key:"loading-content",fn:function(){return [_vm._t("loading-content")]},proxy:true},{key:"user-defined-token-content",fn:function(){return [_vm._t("user-defined-token-content",null,{"inputText":_vm.inputText})]},proxy:true},{key:"no-results-content",fn:function(){return [_vm._t("no-results-content")]},proxy:true},{key:"dropdown-item-content",fn:function(ref){
372
+ return [_vm._t("token-content",null,{"token":token})]}},{key:"text-input",fn:function(){return [_c('input',_vm._b({ref:"textInput",staticClass:"gl-token-selector-input gl-h-auto gl-w-4/10 gl-grow gl-border-none gl-bg-transparent gl-px-1 gl-font-regular gl-text-base gl-leading-normal gl-text-gray-900 gl-outline-none",attrs:{"type":"text","autocomplete":_vm.autocomplete,"aria-labelledby":_vm.ariaLabelledby,"placeholder":_vm.placeholder,"disabled":_vm.viewOnly},domProps:{"value":_vm.inputText},on:{"input":function($event){_vm.inputText = $event.target.value;},"focus":_vm.handleFocus,"blur":_vm.handleBlur,"keydown":[function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"enter",13,$event.key,"Enter")){ return null; }return _vm.handleEnter.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"esc",27,$event.key,["Esc","Escape"])){ return null; }return _vm.handleEscape.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"delete",[8,46],$event.key,["Backspace","Delete","Del"])){ return null; }return _vm.handleBackspace.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"up",38,$event.key,["Up","ArrowUp"])){ return null; }$event.preventDefault();return _vm.dropdownEventHandlers.handleUpArrow.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"down",40,$event.key,["Down","ArrowDown"])){ return null; }$event.preventDefault();return _vm.dropdownEventHandlers.handleDownArrow.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"home",undefined,$event.key,undefined)){ return null; }return _vm.dropdownEventHandlers.handleHomeKey.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"end",undefined,$event.key,undefined)){ return null; }return _vm.dropdownEventHandlers.handleEndKey.apply(null, arguments)},function($event){$event.stopPropagation();return _vm.$emit('keydown', $event)}],"click":_vm.handleInputClick}},'input',_vm.textInputAttrs,false))]},proxy:true}],null,true)})],2),_vm._v(" "),_c('gl-token-selector-dropdown',{attrs:{"menu-class":_vm.menuClass,"show":_vm.dropdownIsOpen,"loading":_vm.loading,"dropdown-items":_vm.filteredDropdownItems,"selected-tokens":_vm.selectedTokens,"input-text":_vm.inputText,"user-defined-token-can-be-added":_vm.userDefinedTokenCanBeAdded,"component-id":_vm.$options.componentId,"register-dropdown-event-handlers":_vm.registerDropdownEventHandlers,"register-reset-focused-dropdown-item":_vm.registerResetFocusedDropdownItem},on:{"dropdown-item-click":_vm.addToken,"show":_vm.openDropdown},scopedSlots:_vm._u([{key:"loading-content",fn:function(){return [_vm._t("loading-content")]},proxy:true},{key:"user-defined-token-content",fn:function(){return [_vm._t("user-defined-token-content",null,{"inputText":_vm.inputText})]},proxy:true},{key:"no-results-content",fn:function(){return [_vm._t("no-results-content")]},proxy:true},{key:"dropdown-item-content",fn:function(ref){
370
373
  var dropdownItem = ref.dropdownItem;
371
374
  return [_vm._t("dropdown-item-content",null,{"dropdownItem":dropdownItem})]}},{key:"dropdown-footer",fn:function(){return [_vm._t("dropdown-footer")]},proxy:true}],null,true),model:{value:(_vm.focusedDropdownItem),callback:function ($$v) {_vm.focusedDropdownItem=$$v;},expression:"focusedDropdownItem"}})],1)};
372
375
  var __vue_staticRenderFns__ = [];
@@ -1,3 +1,4 @@
1
+ import uniqueId from 'lodash/uniqueId';
1
2
  import GlDropdownItem from '../dropdown/dropdown_item';
2
3
  import { tokensValidator } from './helpers';
3
4
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
@@ -32,7 +33,7 @@ var script = {
32
33
  type: String,
33
34
  required: true
34
35
  },
35
- allowUserDefinedTokens: {
36
+ userDefinedTokenCanBeAdded: {
36
37
  type: Boolean,
37
38
  required: true
38
39
  },
@@ -55,7 +56,27 @@ var script = {
55
56
  };
56
57
  },
57
58
  computed: {
59
+ userDefinedToken() {
60
+ return {
61
+ id: uniqueId('user-defined-token'),
62
+ name: this.inputText
63
+ };
64
+ },
65
+ dropdownLength() {
66
+ // Adds an additional dropdown item for the 'Add ... dropdown' item
67
+ return this.userDefinedTokenCanBeAdded ? this.dropdownItems.length : this.dropdownItems.length - 1;
68
+ },
69
+ focusedLastDropdownItem() {
70
+ return this.focusedDropdownItemIndex === this.dropdownLength;
71
+ },
72
+ focusedUserDefinedToken() {
73
+ // User defined tokens are always the last in the list
74
+ return this.userDefinedTokenCanBeAdded && this.focusedLastDropdownItem;
75
+ },
58
76
  focusedDropdownItem() {
77
+ if (this.focusedUserDefinedToken) {
78
+ return this.userDefinedToken;
79
+ }
59
80
  return this.dropdownItems[this.focusedDropdownItemIndex];
60
81
  }
61
82
  },
@@ -93,7 +114,7 @@ var script = {
93
114
  handleMousedown(dropdownItem) {
94
115
  // `event.relatedTarget` returns `null` on Safari because buttons are not focused on click (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus)
95
116
  // Because of this we need to manually focus on the button. We do this in `mousedown` because it is fired before the `blur` event
96
- const dropdownItemRef = dropdownItem === null ? this.$refs.userDefinedTokenDropdownItem : this.getDropdownItemRef(dropdownItem);
117
+ const dropdownItemRef = this.getDropdownItemRef(dropdownItem);
97
118
  if (dropdownItemRef !== null && dropdownItemRef !== void 0 && dropdownItemRef.$el) {
98
119
  dropdownItemRef.$el.querySelector('button').focus();
99
120
  }
@@ -116,7 +137,7 @@ var script = {
116
137
  }
117
138
 
118
139
  // Last dropdown item has been reached
119
- if (this.focusedDropdownItemIndex === this.dropdownItems.length - 1) {
140
+ if (this.focusedLastDropdownItem) {
120
141
  return;
121
142
  }
122
143
  this.focusNextDropdownItem();
@@ -130,7 +151,7 @@ var script = {
130
151
  this.focusLastDropdownItem();
131
152
  },
132
153
  focusLastDropdownItem() {
133
- this.focusedDropdownItemIndex = this.dropdownItems.length - 1;
154
+ this.focusedDropdownItemIndex = this.dropdownLength;
134
155
  },
135
156
  focusFirstDropdownItem() {
136
157
  this.focusedDropdownItemIndex = 0;
@@ -152,6 +173,9 @@ var script = {
152
173
  },
153
174
  getDropdownItemRef(dropdownItem) {
154
175
  var _this$$refs$dropdownI;
176
+ if (this.focusedUserDefinedToken) {
177
+ return this.$refs[this.userDefinedToken.id];
178
+ }
155
179
  return (_this$$refs$dropdownI = this.$refs.dropdownItems) === null || _this$$refs$dropdownI === void 0 ? void 0 : _this$$refs$dropdownI.find(ref => ref.$attrs['data-dropdown-item-id'] === dropdownItem.id);
156
180
  },
157
181
  dropdownItemIdAttribute(dropdownItem) {
@@ -164,7 +188,7 @@ var script = {
164
188
  const __vue_script__ = script;
165
189
 
166
190
  /* template */
167
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"dropdown b-dropdown gl-dropdown gl-relative",class:{ show: _vm.show }},[_c('ul',{ref:"dropdownMenu",staticClass:"dropdown-menu gl-absolute",class:[{ show: _vm.show }, _vm.menuClass],attrs:{"role":"menu","aria-activedescendant":_vm.dropdownItemIdAttribute(_vm.focusedDropdownItem)}},[(_vm.loading)?_c('gl-dropdown-item',{attrs:{"disabled":""}},[_vm._t("loading-content",function(){return [_vm._v("Searching...")]})],2):(_vm.dropdownItems.length)?_vm._l((_vm.dropdownItems),function(dropdownItem){return _c('gl-dropdown-item',{key:dropdownItem.id,ref:"dropdownItems",refInFor:true,attrs:{"id":_vm.dropdownItemIdAttribute(dropdownItem),"data-dropdown-item-id":dropdownItem.id,"active":_vm.dropdownItemIsFocused(dropdownItem),"active-class":"is-focused","tabindex":"-1"},on:{"click":function($event){return _vm.handleDropdownItemClick(dropdownItem)}}},[_c('div',{staticClass:"-gl-mx-4 -gl-my-3 gl-px-4 gl-py-3",on:{"mousedown":function($event){return _vm.handleMousedown(dropdownItem)}}},[_vm._t("dropdown-item-content",function(){return [_vm._v("\n "+_vm._s(dropdownItem.name)+"\n ")]},{"dropdownItem":dropdownItem})],2)])}):[(_vm.allowUserDefinedTokens && _vm.inputText !== '')?_c('gl-dropdown-item',{ref:"userDefinedTokenDropdownItem",attrs:{"active":"","active-class":"is-focused"},on:{"click":function($event){return _vm.handleDropdownItemClick(null)}}},[_c('div',{staticClass:"-gl-mx-4 -gl-my-3 gl-px-4 gl-py-3",on:{"mousedown":function($event){return _vm.handleMousedown(null)}}},[_vm._t("user-defined-token-content",function(){return [_vm._v("\n Add \""+_vm._s(_vm.inputText)+"\"\n ")]},{"inputText":_vm.inputText})],2)]):_c('gl-dropdown-item',{attrs:{"disabled":""}},[_vm._t("no-results-content",function(){return [_vm._v("No matches found")]})],2)],_vm._v(" "),_vm._t("dropdown-footer")],2)])};
191
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"dropdown b-dropdown gl-dropdown gl-relative",class:{ show: _vm.show }},[_c('ul',{ref:"dropdownMenu",staticClass:"dropdown-menu gl-absolute",class:[{ show: _vm.show }, _vm.menuClass],attrs:{"role":"menu","aria-activedescendant":_vm.dropdownItemIdAttribute(_vm.focusedDropdownItem)}},[(_vm.loading)?_c('gl-dropdown-item',{attrs:{"disabled":""}},[_vm._t("loading-content",function(){return [_vm._v("Searching...")]})],2):_vm._e(),_vm._v(" "),_vm._l((_vm.dropdownItems),function(dropdownItem){return _c('gl-dropdown-item',{key:dropdownItem.id,ref:"dropdownItems",refInFor:true,attrs:{"id":_vm.dropdownItemIdAttribute(dropdownItem),"data-dropdown-item-id":dropdownItem.id,"active":_vm.dropdownItemIsFocused(dropdownItem),"active-class":"is-focused","tabindex":"-1"},on:{"click":function($event){return _vm.handleDropdownItemClick(dropdownItem)}}},[_c('div',{staticClass:"-gl-mx-4 -gl-my-3 gl-px-4 gl-py-3",on:{"mousedown":function($event){return _vm.handleMousedown(dropdownItem)}}},[_vm._t("dropdown-item-content",function(){return [_vm._v("\n "+_vm._s(dropdownItem.name)+"\n ")]},{"dropdownItem":dropdownItem})],2)])}),_vm._v(" "),(_vm.userDefinedTokenCanBeAdded)?_c('gl-dropdown-item',{ref:_vm.userDefinedToken.id,attrs:{"id":_vm.dropdownItemIdAttribute(_vm.userDefinedToken),"data-dropdown-item-id":_vm.userDefinedToken.id,"active":_vm.dropdownItemIsFocused(_vm.userDefinedToken),"active-class":"is-focused","tabindex":"-1"},on:{"click":function($event){return _vm.handleDropdownItemClick(_vm.userDefinedToken)}}},[_c('div',{staticClass:"-gl-mx-4 -gl-my-3 gl-px-4 gl-py-3",on:{"mousedown":function($event){return _vm.handleMousedown(_vm.userDefinedToken)}}},[_vm._t("user-defined-token-content",function(){return [_vm._v("\n Add \""+_vm._s(_vm.inputText)+"\"\n ")]},{"inputText":_vm.inputText})],2)]):(!_vm.dropdownItems.length)?_c('gl-dropdown-item',{attrs:{"disabled":""}},[_vm._t("no-results-content",function(){return [_vm._v("No matches found")]})],2):_vm._e(),_vm._v(" "),_vm._t("dropdown-footer")],2)])};
168
192
  var __vue_staticRenderFns__ = [];
169
193
 
170
194
  /* style */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "87.2.1",
3
+ "version": "87.3.1",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -108,13 +108,13 @@
108
108
  "@babel/core": "^7.24.9",
109
109
  "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
110
110
  "@babel/plugin-proposal-optional-chaining": "^7.21.0",
111
- "@babel/preset-env": "^7.24.8",
111
+ "@babel/preset-env": "^7.25.0",
112
112
  "@babel/preset-react": "^7.24.7",
113
113
  "@cypress/grep": "^4.0.1",
114
114
  "@gitlab/eslint-plugin": "19.6.0",
115
115
  "@gitlab/fonts": "^1.3.0",
116
116
  "@gitlab/stylelint-config": "6.1.0",
117
- "@gitlab/svgs": "3.109.0",
117
+ "@gitlab/svgs": "3.110.0",
118
118
  "@jest/test-sequencer": "^29.7.0",
119
119
  "@rollup/plugin-commonjs": "^11.1.0",
120
120
  "@rollup/plugin-node-resolve": "^7.1.3",
@@ -156,7 +156,7 @@
156
156
  "esbuild": "^0.18.0",
157
157
  "eslint": "8.57.0",
158
158
  "eslint-import-resolver-jest": "3.0.2",
159
- "eslint-plugin-cypress": "3.3.0",
159
+ "eslint-plugin-cypress": "3.4.0",
160
160
  "eslint-plugin-storybook": "0.8.0",
161
161
  "gitlab-api-async-iterator": "^1.3.1",
162
162
  "glob": "10.3.3",
@@ -43,3 +43,36 @@ export default {
43
43
  </div>
44
44
  </template>
45
45
  ```
46
+
47
+ ## User created tokens
48
+
49
+ This component allows for users to create their own tokens when configured to do so.
50
+ There are two props that support this functionality: `allowUserDefinedTokens` and `showAddNewAlways`.
51
+
52
+ `allowUserDefinedTokens` is required to enable the functionality
53
+
54
+ When set to `true` and a user's search text returns nothing,
55
+ they will be presented with an additional dropdown item `Add ...`
56
+ that takes their current search input and emits `@input`.
57
+ The parent component can then handle the event accordingly.
58
+
59
+ Additionally, there are scenarios where the user may want the ability to add a new token
60
+ even if some search results are returned. This functionality can be enabled by additionally
61
+ setting `showAddNewAlways` to `true`.
62
+ This will allow for the `Add ...` dropdown item to appear at all times
63
+ whenever a user has inputted text, regardless if results are found.
64
+
65
+ ```html
66
+ <template>
67
+ <div>
68
+ <gl-token-selector
69
+ v-model="selectedTokens"
70
+ :dropdown-items="dropdownItems"
71
+ allow-user-defined-items
72
+ show-ad-new-always
73
+ @input="onTokenUpdate"
74
+ />
75
+ {{ selectedTokens }}
76
+ </div>
77
+ </template>
78
+ ```
@@ -34,6 +34,14 @@ export default {
34
34
  required: false,
35
35
  default: false,
36
36
  },
37
+ /**
38
+ * Shows 'Add new token option' in dropdown even if results are present, requires allowUserDefinedTokens to be true
39
+ */
40
+ showAddNewAlways: {
41
+ type: Boolean,
42
+ required: false,
43
+ default: false,
44
+ },
37
45
  /**
38
46
  * Dropdown items are loading, can be used when requesting new dropdown items
39
47
  */
@@ -161,7 +169,11 @@ export default {
161
169
  return !this.filteredDropdownItems.length;
162
170
  },
163
171
  userDefinedTokenCanBeAdded() {
164
- return this.allowUserDefinedTokens && this.dropdownHasNoItems && this.inputText !== '';
172
+ if (!this.allowUserDefinedTokens || !this.inputText) {
173
+ return false;
174
+ }
175
+
176
+ return this.showAddNewAlways || this.dropdownHasNoItems;
165
177
  },
166
178
  hideDropdown() {
167
179
  if (this.userDefinedTokenCanBeAdded) {
@@ -262,9 +274,7 @@ export default {
262
274
  });
263
275
  },
264
276
  handleEnter() {
265
- if (this.userDefinedTokenCanBeAdded) {
266
- this.addToken();
267
- } else if (this.focusedDropdownItem && this.dropdownIsOpen) {
277
+ if (this.focusedDropdownItem && this.dropdownIsOpen) {
268
278
  this.addToken(this.focusedDropdownItem);
269
279
  }
270
280
  },
@@ -302,21 +312,13 @@ export default {
302
312
 
303
313
  this.focusTextInput();
304
314
  },
305
- addToken(dropdownItem = null) {
306
- const token =
307
- dropdownItem !== null
308
- ? dropdownItem
309
- : {
310
- id: uniqueId('user-defined-token'),
311
- name: this.inputText,
312
- };
313
-
315
+ addToken(dropdownItem) {
314
316
  /**
315
317
  * Fired when a token is added or removed
316
318
  *
317
319
  * @property {array} selectedTokens
318
320
  */
319
- this.$emit('input', [...this.selectedTokens, token]);
321
+ this.$emit('input', [...this.selectedTokens, dropdownItem]);
320
322
 
321
323
  this.inputText = '';
322
324
  this.closeDropdown();
@@ -326,13 +328,13 @@ export default {
326
328
  *
327
329
  * @property {object} token
328
330
  */
329
- this.$emit('token-add', token);
331
+ this.$emit('token-add', dropdownItem);
330
332
  },
331
333
  removeToken(token) {
332
334
  /**
333
- * Fired when user types in the token selector
335
+ * Fired when a token is added or removed
334
336
  *
335
- * @property {string} inputText
337
+ * @property {array} selectedTokens
336
338
  */
337
339
  this.$emit(
338
340
  'input',
@@ -446,7 +448,7 @@ export default {
446
448
  :dropdown-items="filteredDropdownItems"
447
449
  :selected-tokens="selectedTokens"
448
450
  :input-text="inputText"
449
- :allow-user-defined-tokens="allowUserDefinedTokens"
451
+ :user-defined-token-can-be-added="userDefinedTokenCanBeAdded"
450
452
  :component-id="$options.componentId"
451
453
  :register-dropdown-event-handlers="registerDropdownEventHandlers"
452
454
  :register-reset-focused-dropdown-item="registerResetFocusedDropdownItem"
@@ -466,7 +468,7 @@ export default {
466
468
  </template>
467
469
  <template #no-results-content>
468
470
  <!-- @slot Content to display when `dropdown-items` is empty and
469
- `allow-user-defined-tokens` is `false`. Default content is "No matches found". -->
471
+ both `allow-user-defined-tokens` and `show-add-new-always` is `false`. Default content is "No matches found". -->
470
472
  <slot name="no-results-content"></slot>
471
473
  </template>
472
474
  <template #dropdown-item-content="{ dropdownItem }">
@@ -1,4 +1,5 @@
1
1
  <script>
2
+ import uniqueId from 'lodash/uniqueId';
2
3
  import GlDropdownItem from '../dropdown/dropdown_item.vue';
3
4
  import { tokensValidator } from './helpers';
4
5
 
@@ -30,7 +31,7 @@ export default {
30
31
  type: String,
31
32
  required: true,
32
33
  },
33
- allowUserDefinedTokens: {
34
+ userDefinedTokenCanBeAdded: {
34
35
  type: Boolean,
35
36
  required: true,
36
37
  },
@@ -53,7 +54,30 @@ export default {
53
54
  };
54
55
  },
55
56
  computed: {
57
+ userDefinedToken() {
58
+ return {
59
+ id: uniqueId('user-defined-token'),
60
+ name: this.inputText,
61
+ };
62
+ },
63
+ dropdownLength() {
64
+ // Adds an additional dropdown item for the 'Add ... dropdown' item
65
+ return this.userDefinedTokenCanBeAdded
66
+ ? this.dropdownItems.length
67
+ : this.dropdownItems.length - 1;
68
+ },
69
+ focusedLastDropdownItem() {
70
+ return this.focusedDropdownItemIndex === this.dropdownLength;
71
+ },
72
+ focusedUserDefinedToken() {
73
+ // User defined tokens are always the last in the list
74
+ return this.userDefinedTokenCanBeAdded && this.focusedLastDropdownItem;
75
+ },
56
76
  focusedDropdownItem() {
77
+ if (this.focusedUserDefinedToken) {
78
+ return this.userDefinedToken;
79
+ }
80
+
57
81
  return this.dropdownItems[this.focusedDropdownItemIndex];
58
82
  },
59
83
  },
@@ -92,10 +116,7 @@ export default {
92
116
  handleMousedown(dropdownItem) {
93
117
  // `event.relatedTarget` returns `null` on Safari because buttons are not focused on click (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus)
94
118
  // Because of this we need to manually focus on the button. We do this in `mousedown` because it is fired before the `blur` event
95
- const dropdownItemRef =
96
- dropdownItem === null
97
- ? this.$refs.userDefinedTokenDropdownItem
98
- : this.getDropdownItemRef(dropdownItem);
119
+ const dropdownItemRef = this.getDropdownItemRef(dropdownItem);
99
120
 
100
121
  if (dropdownItemRef?.$el) {
101
122
  dropdownItemRef.$el.querySelector('button').focus();
@@ -121,7 +142,7 @@ export default {
121
142
  }
122
143
 
123
144
  // Last dropdown item has been reached
124
- if (this.focusedDropdownItemIndex === this.dropdownItems.length - 1) {
145
+ if (this.focusedLastDropdownItem) {
125
146
  return;
126
147
  }
127
148
 
@@ -138,7 +159,7 @@ export default {
138
159
  this.focusLastDropdownItem();
139
160
  },
140
161
  focusLastDropdownItem() {
141
- this.focusedDropdownItemIndex = this.dropdownItems.length - 1;
162
+ this.focusedDropdownItemIndex = this.dropdownLength;
142
163
  },
143
164
  focusFirstDropdownItem() {
144
165
  this.focusedDropdownItemIndex = 0;
@@ -160,6 +181,10 @@ export default {
160
181
  return dropdownItem.id === this.focusedDropdownItem.id;
161
182
  },
162
183
  getDropdownItemRef(dropdownItem) {
184
+ if (this.focusedUserDefinedToken) {
185
+ return this.$refs[this.userDefinedToken.id];
186
+ }
187
+
163
188
  return this.$refs.dropdownItems?.find(
164
189
  (ref) => ref.$attrs['data-dropdown-item-id'] === dropdownItem.id
165
190
  );
@@ -183,43 +208,49 @@ export default {
183
208
  <gl-dropdown-item v-if="loading" disabled>
184
209
  <slot name="loading-content">Searching...</slot>
185
210
  </gl-dropdown-item>
186
- <template v-else-if="dropdownItems.length">
187
- <gl-dropdown-item
188
- v-for="dropdownItem in dropdownItems"
189
- :id="dropdownItemIdAttribute(dropdownItem)"
190
- ref="dropdownItems"
191
- :key="dropdownItem.id"
192
- :data-dropdown-item-id="dropdownItem.id"
193
- :active="dropdownItemIsFocused(dropdownItem)"
194
- active-class="is-focused"
195
- tabindex="-1"
196
- @click="handleDropdownItemClick(dropdownItem)"
197
- >
198
- <div class="-gl-mx-4 -gl-my-3 gl-px-4 gl-py-3" @mousedown="handleMousedown(dropdownItem)">
199
- <slot name="dropdown-item-content" :dropdown-item="dropdownItem">
200
- {{ dropdownItem.name }}
201
- </slot>
202
- </div>
203
- </gl-dropdown-item>
204
- </template>
205
- <template v-else>
206
- <gl-dropdown-item
207
- v-if="allowUserDefinedTokens && inputText !== ''"
208
- ref="userDefinedTokenDropdownItem"
209
- active
210
- active-class="is-focused"
211
- @click="handleDropdownItemClick(null)"
211
+
212
+ <gl-dropdown-item
213
+ v-for="dropdownItem in dropdownItems"
214
+ :id="dropdownItemIdAttribute(dropdownItem)"
215
+ ref="dropdownItems"
216
+ :key="dropdownItem.id"
217
+ :data-dropdown-item-id="dropdownItem.id"
218
+ :active="dropdownItemIsFocused(dropdownItem)"
219
+ active-class="is-focused"
220
+ tabindex="-1"
221
+ @click="handleDropdownItemClick(dropdownItem)"
222
+ >
223
+ <div class="-gl-mx-4 -gl-my-3 gl-px-4 gl-py-3" @mousedown="handleMousedown(dropdownItem)">
224
+ <slot name="dropdown-item-content" :dropdown-item="dropdownItem">
225
+ {{ dropdownItem.name }}
226
+ </slot>
227
+ </div>
228
+ </gl-dropdown-item>
229
+
230
+ <gl-dropdown-item
231
+ v-if="userDefinedTokenCanBeAdded"
232
+ :id="dropdownItemIdAttribute(userDefinedToken)"
233
+ :ref="userDefinedToken.id"
234
+ :data-dropdown-item-id="userDefinedToken.id"
235
+ :active="dropdownItemIsFocused(userDefinedToken)"
236
+ active-class="is-focused"
237
+ tabindex="-1"
238
+ @click="handleDropdownItemClick(userDefinedToken)"
239
+ >
240
+ <div
241
+ class="-gl-mx-4 -gl-my-3 gl-px-4 gl-py-3"
242
+ @mousedown="handleMousedown(userDefinedToken)"
212
243
  >
213
- <div class="-gl-mx-4 -gl-my-3 gl-px-4 gl-py-3" @mousedown="handleMousedown(null)">
214
- <slot name="user-defined-token-content" :input-text="inputText">
215
- Add "{{ inputText }}"
216
- </slot>
217
- </div>
218
- </gl-dropdown-item>
219
- <gl-dropdown-item v-else disabled>
220
- <slot name="no-results-content">No matches found</slot>
221
- </gl-dropdown-item>
222
- </template>
244
+ <slot name="user-defined-token-content" :input-text="inputText">
245
+ Add "{{ inputText }}"
246
+ </slot>
247
+ </div>
248
+ </gl-dropdown-item>
249
+
250
+ <gl-dropdown-item v-else-if="!dropdownItems.length" disabled>
251
+ <slot name="no-results-content">No matches found</slot>
252
+ </gl-dropdown-item>
253
+
223
254
  <slot name="dropdown-footer"></slot>
224
255
  </ul>
225
256
  </div>