@gitlab/ui 123.9.2 → 123.10.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.
@@ -16,11 +16,6 @@ var script = {
16
16
  validator: tokensValidator,
17
17
  required: true
18
18
  },
19
- state: {
20
- type: Boolean,
21
- required: false,
22
- default: null
23
- },
24
19
  registerFocusOnToken: {
25
20
  type: Function,
26
21
  required: true
@@ -130,7 +125,7 @@ var script = {
130
125
  const __vue_script__ = script;
131
126
 
132
127
  /* template */
133
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-flex gl-w-full gl-flex-nowrap gl-items-start"},[_c('div',{staticClass:"gl-flex gl-grow gl-flex-wrap gl-items-start"},[_c('div',{ref:"tokenContainer",staticClass:"-gl-mx-1 -gl-my-1 gl-flex gl-w-auto gl-list-none gl-flex-wrap gl-items-center gl-p-0",attrs:{"role":"listbox","aria-multiselectable":"false","aria-orientation":"horizontal","aria-invalid":_vm.state === false && 'true',"aria-label":"token list"},on:{"keydown":[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.handleLeftArrow.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"right",39,$event.key,["Right","ArrowRight"])){ return null; }if('button' in $event && $event.button !== 2){ return null; }return _vm.handleRightArrow.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"home",undefined,$event.key,undefined)){ return null; }return _vm.handleHome.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"end",undefined,$event.key,undefined)){ return null; }return _vm.handleEnd.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.handleDelete.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,"tab",9,$event.key,"Tab")){ return null; }if($event.ctrlKey||$event.shiftKey||$event.altKey||$event.metaKey){ return null; }$event.preventDefault();return _vm.handleTab.apply(null, arguments)}]}},_vm._l((_vm.tokens),function(token,index){return _c('div',{key:token.id,ref:"tokens",refInFor:true,staticClass:"gl-token-selector-token-container gl-px-1 gl-py-2 gl-outline-none",attrs:{"data-token-id":token.id,"role":"option","tabindex":"-1"},on:{"focus":function($event){_vm.bindFocusEvent ? _vm.handleTokenFocus(index) : null;}}},[_c('gl-token',{staticClass:"gl-cursor-default",class:token.class,style:(token.style),attrs:{"view-only":_vm.viewOnly},on:{"close":function($event){return _vm.handleClose(token)}}},[_vm._t("token-content",function(){return [_c('span',[_vm._v("\n "+_vm._s(token.name)+"\n ")])]},{"token":token})],2)],1)}),0),_vm._v(" "),_vm._t("text-input")],2),_vm._v(" "),(_vm.showClearAllButton)?_c('div',{staticClass:"gl-ml-3 gl-p-1"},[_c('gl-button',{attrs:{"size":"small","aria-label":"Clear all","icon":"clear","category":"tertiary","data-testid":"clear-all-button"},on:{"click":function($event){$event.stopPropagation();return _vm.$emit('clear-all')}}})],1):_vm._e()])};
128
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-flex gl-w-full gl-flex-nowrap gl-items-start"},[_c('div',{staticClass:"gl-flex gl-grow gl-flex-wrap gl-items-start"},[_c('div',{ref:"tokenContainer",staticClass:"-gl-mx-1 -gl-my-1 gl-flex gl-w-auto gl-list-none gl-flex-wrap gl-items-center gl-p-0",on:{"keydown":[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.handleLeftArrow.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"right",39,$event.key,["Right","ArrowRight"])){ return null; }if('button' in $event && $event.button !== 2){ return null; }return _vm.handleRightArrow.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"home",undefined,$event.key,undefined)){ return null; }return _vm.handleHome.apply(null, arguments)},function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"end",undefined,$event.key,undefined)){ return null; }return _vm.handleEnd.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.handleDelete.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,"tab",9,$event.key,"Tab")){ return null; }if($event.ctrlKey||$event.shiftKey||$event.altKey||$event.metaKey){ return null; }$event.preventDefault();return _vm.handleTab.apply(null, arguments)}]}},_vm._l((_vm.tokens),function(token,index){return _c('div',{key:token.id,ref:"tokens",refInFor:true,staticClass:"gl-token-selector-token-container gl-px-1 gl-py-2 gl-outline-none",attrs:{"data-token-id":token.id,"data-testid":"gl-token-selector-tokens","tabindex":"-1"},on:{"focus":function($event){_vm.bindFocusEvent ? _vm.handleTokenFocus(index) : null;}}},[_c('gl-token',{staticClass:"gl-cursor-default",class:token.class,style:(token.style),attrs:{"view-only":_vm.viewOnly},on:{"close":function($event){return _vm.handleClose(token)}}},[_vm._t("token-content",function(){return [_c('span',[_vm._v("\n "+_vm._s(token.name)+"\n ")])]},{"token":token})],2)],1)}),0),_vm._v(" "),_vm._t("text-input")],2),_vm._v(" "),(_vm.showClearAllButton)?_c('div',{staticClass:"gl-ml-3 gl-p-1"},[_c('gl-button',{attrs:{"size":"small","aria-label":"Clear all","icon":"clear","category":"tertiary","data-testid":"clear-all-button"},on:{"click":function($event){$event.stopPropagation();return _vm.$emit('clear-all')}}})],1):_vm._e()])};
134
129
  var __vue_staticRenderFns__ = [];
135
130
 
136
131
  /* style */
@@ -1,4 +1,4 @@
1
- import uniqueId from 'lodash/uniqueId';
1
+ import { GlUniqueId } from '../../../utils/unique_id';
2
2
  import { tokensValidator } from './helpers';
3
3
  import GlTokenContainer from './token_container';
4
4
  import GlTokenSelectorDropdown from './token_selector_dropdown';
@@ -6,7 +6,6 @@ import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
6
6
 
7
7
  var script = {
8
8
  name: 'GlTokenSelector',
9
- componentId: uniqueId('token-selector'),
10
9
  components: {
11
10
  GlTokenContainer,
12
11
  GlTokenSelectorDropdown
@@ -75,7 +74,7 @@ var script = {
75
74
  default: ''
76
75
  },
77
76
  /**
78
- * The autocomplete attribute value for the underlying `input` element
77
+ * The HTML5 autocomplete attribute value for the underlying `input` element.
79
78
  */
80
79
  autocomplete: {
81
80
  type: String,
@@ -83,7 +82,21 @@ var script = {
83
82
  default: 'off'
84
83
  },
85
84
  /**
86
- * The `aria-labelledby` attribute value for the underlying `input` element
85
+ * The `aria-label` attribute value for the underlying `input` element.
86
+ * Input must have an aria-label or aria-labelledby prop or it will be inaccessible.
87
+ *
88
+ * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-label
89
+ */
90
+ ariaLabel: {
91
+ type: String,
92
+ required: false,
93
+ default: null
94
+ },
95
+ /**
96
+ * The `aria-labelledby` attribute value for the underlying `input` element.
97
+ * String must match the unique ID on a text element to create an accessible label.
98
+ *
99
+ * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-labelledby
87
100
  */
88
101
  ariaLabelledby: {
89
102
  type: String,
@@ -229,6 +242,10 @@ var script = {
229
242
  }
230
243
  }
231
244
  },
245
+ created() {
246
+ // Each instance must have a unique ID for proper ARIA relationships
247
+ this.uniqueId = `token-selector-${GlUniqueId()}`;
248
+ },
232
249
  methods: {
233
250
  handleFocus(event) {
234
251
  /**
@@ -364,6 +381,15 @@ var script = {
364
381
  clearAll() {
365
382
  this.$emit('input', []);
366
383
  this.focusTextInput();
384
+ },
385
+ handleAriaInvalid() {
386
+ const {
387
+ state
388
+ } = this;
389
+ return state === false ? 'true' : null;
390
+ },
391
+ handleAriaActiveDescendent(value) {
392
+ return value ? `${this.uniqueId}-dropdown-item-${value.id}` : null;
367
393
  }
368
394
  }
369
395
  };
@@ -380,9 +406,9 @@ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=
380
406
  _vm.containerClass,
381
407
  _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){
382
408
  var token = ref.token;
383
- 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-default 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){
409
+ 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-default gl-outline-none",attrs:{"type":"text","aria-activedescendant":_vm.handleAriaActiveDescendent(_vm.focusedDropdownItem),"autocomplete":_vm.autocomplete,"aria-controls":_vm.uniqueId,"aria-expanded":_vm.dropdownIsOpen.toString(),"aria-invalid":_vm.handleAriaInvalid(),"aria-label":_vm.ariaLabelledby ? null : _vm.ariaLabel,"aria-labelledby":_vm.ariaLabelledby,"placeholder":_vm.placeholder,"disabled":_vm.viewOnly,"aria-autocomplete":"list","role":"combobox"},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,"input-text":_vm.inputText,"user-defined-token-can-be-added":_vm.userDefinedTokenCanBeAdded,"component-id":_vm.uniqueId,"register-dropdown-event-handlers":_vm.registerDropdownEventHandlers,"register-reset-focused-dropdown-item":_vm.registerResetFocusedDropdownItem},on:{"dropdown-item-click":_vm.addToken,"input":function($event){_vm.focusedDropdownItem = $event;},"aria-active-descendant":_vm.handleAriaActiveDescendent,"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){
384
410
  var dropdownItem = ref.dropdownItem;
385
- 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)};
411
+ return [_vm._t("dropdown-item-content",null,{"dropdownItem":dropdownItem})]}},{key:"dropdown-footer",fn:function(){return [_vm._t("dropdown-footer")]},proxy:true}],null,true)})],1)};
386
412
  var __vue_staticRenderFns__ = [];
387
413
 
388
414
  /* style */
@@ -188,7 +188,7 @@ var script = {
188
188
  const __vue_script__ = script;
189
189
 
190
190
  /* template */
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)])};
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:{"id":_vm.componentId,"role":"listbox"}},[(_vm.loading)?_c('gl-dropdown-item',{attrs:{"role":"option","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),"aria-selected":_vm.dropdownItemIsFocused(dropdownItem).toString(),"active-class":"is-focused","role":"option","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","role":"option","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:{"role":"option","disabled":""}},[_vm._t("no-results-content",function(){return [_vm._v("No matches found")]})],2):_vm._e(),_vm._v(" "),_vm._t("dropdown-footer")],2)])};
192
192
  var __vue_staticRenderFns__ = [];
193
193
 
194
194
  /* style */
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Function uses browser native crypto to return a sixteen
3
+ * character base16 encoded string. It is often used to
4
+ * generate collision-resistant IDs for aria markup such
5
+ * as aria-activedescendant and aria-controls.
6
+ *
7
+ * @returns String
8
+ */
9
+ const GlUniqueId = () => {
10
+ if (typeof window !== 'undefined' && window.crypto) {
11
+ const arr32 = new Uint32Array(2);
12
+ window.crypto.getRandomValues(arr32);
13
+ return Array.from(arr32, id => id.toString(16)).join('');
14
+ }
15
+ throw new Error('Cannot generate a unique ID');
16
+ };
17
+
18
+ export { GlUniqueId };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "123.9.2",
3
+ "version": "123.10.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -13,11 +13,6 @@ export default {
13
13
  validator: tokensValidator,
14
14
  required: true,
15
15
  },
16
- state: {
17
- type: Boolean,
18
- required: false,
19
- default: null,
20
- },
21
16
  registerFocusOnToken: {
22
17
  type: Function,
23
18
  required: true,
@@ -133,11 +128,6 @@ export default {
133
128
  <div
134
129
  ref="tokenContainer"
135
130
  class="-gl-mx-1 -gl-my-1 gl-flex gl-w-auto gl-list-none gl-flex-wrap gl-items-center gl-p-0"
136
- role="listbox"
137
- aria-multiselectable="false"
138
- aria-orientation="horizontal"
139
- :aria-invalid="state === false && 'true'"
140
- aria-label="token list"
141
131
  @keydown.left="handleLeftArrow"
142
132
  @keydown.right="handleRightArrow"
143
133
  @keydown.home="handleHome"
@@ -152,7 +142,7 @@ export default {
152
142
  :key="token.id"
153
143
  :data-token-id="token.id"
154
144
  class="gl-token-selector-token-container gl-px-1 gl-py-2 gl-outline-none"
155
- role="option"
145
+ data-testid="gl-token-selector-tokens"
156
146
  tabindex="-1"
157
147
  @focus="bindFocusEvent ? handleTokenFocus(index) : null"
158
148
  >
@@ -1,12 +1,11 @@
1
1
  <script>
2
- import uniqueId from 'lodash/uniqueId';
2
+ import { GlUniqueId } from '../../../utils/unique_id';
3
3
  import { tokensValidator } from './helpers';
4
4
  import GlTokenContainer from './token_container.vue';
5
5
  import GlTokenSelectorDropdown from './token_selector_dropdown.vue';
6
6
 
7
7
  export default {
8
8
  name: 'GlTokenSelector',
9
- componentId: uniqueId('token-selector'),
10
9
  components: {
11
10
  GlTokenContainer,
12
11
  GlTokenSelectorDropdown,
@@ -75,7 +74,7 @@ export default {
75
74
  default: '',
76
75
  },
77
76
  /**
78
- * The autocomplete attribute value for the underlying `input` element
77
+ * The HTML5 autocomplete attribute value for the underlying `input` element.
79
78
  */
80
79
  autocomplete: {
81
80
  type: String,
@@ -83,7 +82,21 @@ export default {
83
82
  default: 'off',
84
83
  },
85
84
  /**
86
- * The `aria-labelledby` attribute value for the underlying `input` element
85
+ * The `aria-label` attribute value for the underlying `input` element.
86
+ * Input must have an aria-label or aria-labelledby prop or it will be inaccessible.
87
+ *
88
+ * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-label
89
+ */
90
+ ariaLabel: {
91
+ type: String,
92
+ required: false,
93
+ default: null,
94
+ },
95
+ /**
96
+ * The `aria-labelledby` attribute value for the underlying `input` element.
97
+ * String must match the unique ID on a text element to create an accessible label.
98
+ *
99
+ * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-labelledby
87
100
  */
88
101
  ariaLabelledby: {
89
102
  type: String,
@@ -237,6 +250,10 @@ export default {
237
250
  }
238
251
  },
239
252
  },
253
+ created() {
254
+ // Each instance must have a unique ID for proper ARIA relationships
255
+ this.uniqueId = `token-selector-${GlUniqueId()}`;
256
+ },
240
257
  methods: {
241
258
  handleFocus(event) {
242
259
  /**
@@ -382,6 +399,13 @@ export default {
382
399
  this.$emit('input', []);
383
400
  this.focusTextInput();
384
401
  },
402
+ handleAriaInvalid() {
403
+ const { state } = this;
404
+ return state === false ? 'true' : null;
405
+ },
406
+ handleAriaActiveDescendent(value) {
407
+ return value ? `${this.uniqueId}-dropdown-item-${value.id}` : null;
408
+ },
385
409
  },
386
410
  };
387
411
  </script>
@@ -428,10 +452,17 @@ export default {
428
452
  type="text"
429
453
  class="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-default gl-outline-none"
430
454
  :value="inputText"
455
+ :aria-activedescendant="handleAriaActiveDescendent(focusedDropdownItem)"
431
456
  :autocomplete="autocomplete"
457
+ :aria-controls="uniqueId"
458
+ :aria-expanded="dropdownIsOpen.toString()"
459
+ :aria-invalid="handleAriaInvalid()"
460
+ :aria-label="ariaLabelledby ? null : ariaLabel"
432
461
  :aria-labelledby="ariaLabelledby"
433
462
  :placeholder="placeholder"
434
463
  :disabled="viewOnly"
464
+ aria-autocomplete="list"
465
+ role="combobox"
435
466
  v-bind="textInputAttrs"
436
467
  @input="inputText = $event.target.value"
437
468
  @focus="handleFocus"
@@ -450,18 +481,18 @@ export default {
450
481
  </gl-token-container>
451
482
  </div>
452
483
  <gl-token-selector-dropdown
453
- v-model="focusedDropdownItem"
454
484
  :menu-class="menuClass"
455
485
  :show="dropdownIsOpen"
456
486
  :loading="loading"
457
487
  :dropdown-items="filteredDropdownItems"
458
- :selected-tokens="selectedTokens"
459
488
  :input-text="inputText"
460
489
  :user-defined-token-can-be-added="userDefinedTokenCanBeAdded"
461
- :component-id="$options.componentId"
490
+ :component-id="uniqueId"
462
491
  :register-dropdown-event-handlers="registerDropdownEventHandlers"
463
492
  :register-reset-focused-dropdown-item="registerResetFocusedDropdownItem"
464
493
  @dropdown-item-click="addToken"
494
+ @input="focusedDropdownItem = $event"
495
+ @aria-active-descendant="handleAriaActiveDescendent"
465
496
  @show="openDropdown"
466
497
  >
467
498
  <template #loading-content
@@ -106,7 +106,6 @@ export default {
106
106
  });
107
107
 
108
108
  this.registerResetFocusedDropdownItem(this.resetFocusedDropdownItem);
109
-
110
109
  this.$emit('input', this.focusedDropdownItem);
111
110
  },
112
111
  methods: {
@@ -199,13 +198,13 @@ export default {
199
198
  <template>
200
199
  <div class="dropdown b-dropdown gl-dropdown gl-relative" :class="{ show }">
201
200
  <ul
201
+ :id="componentId"
202
202
  ref="dropdownMenu"
203
- role="menu"
203
+ role="listbox"
204
204
  class="dropdown-menu gl-absolute"
205
- :aria-activedescendant="dropdownItemIdAttribute(focusedDropdownItem)"
206
205
  :class="[{ show }, menuClass]"
207
206
  >
208
- <gl-dropdown-item v-if="loading" disabled>
207
+ <gl-dropdown-item v-if="loading" role="option" disabled>
209
208
  <slot name="loading-content">Searching...</slot>
210
209
  </gl-dropdown-item>
211
210
 
@@ -216,7 +215,9 @@ export default {
216
215
  :key="dropdownItem.id"
217
216
  :data-dropdown-item-id="dropdownItem.id"
218
217
  :active="dropdownItemIsFocused(dropdownItem)"
218
+ :aria-selected="dropdownItemIsFocused(dropdownItem).toString()"
219
219
  active-class="is-focused"
220
+ role="option"
220
221
  tabindex="-1"
221
222
  @click="handleDropdownItemClick(dropdownItem)"
222
223
  >
@@ -234,6 +235,7 @@ export default {
234
235
  :data-dropdown-item-id="userDefinedToken.id"
235
236
  :active="dropdownItemIsFocused(userDefinedToken)"
236
237
  active-class="is-focused"
238
+ role="option"
237
239
  tabindex="-1"
238
240
  @click="handleDropdownItemClick(userDefinedToken)"
239
241
  >
@@ -247,7 +249,7 @@ export default {
247
249
  </div>
248
250
  </gl-dropdown-item>
249
251
 
250
- <gl-dropdown-item v-else-if="!dropdownItems.length" disabled>
252
+ <gl-dropdown-item v-else-if="!dropdownItems.length" role="option" disabled>
251
253
  <slot name="no-results-content">No matches found</slot>
252
254
  </gl-dropdown-item>
253
255
 
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Function uses browser native crypto to return a sixteen
3
+ * character base16 encoded string. It is often used to
4
+ * generate collision-resistant IDs for aria markup such
5
+ * as aria-activedescendant and aria-controls.
6
+ *
7
+ * @returns String
8
+ */
9
+ export const GlUniqueId = () => {
10
+ if (typeof window !== 'undefined' && window.crypto) {
11
+ const arr32 = new Uint32Array(2);
12
+ window.crypto.getRandomValues(arr32);
13
+ return Array.from(arr32, (id) => id.toString(16)).join('');
14
+ }
15
+
16
+ throw new Error('Cannot generate a unique ID');
17
+ };