@gitlab/ui 128.11.0 → 128.12.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.
@@ -1,5 +1,6 @@
1
1
  import isString from 'lodash/isString';
2
2
  import isPlainObject from 'lodash/isPlainObject';
3
+ import { uniqueId } from 'lodash';
3
4
  import { BFormGroup } from '../../../../vendor/bootstrap-vue/src/components/form-group/form-group';
4
5
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
5
6
 
@@ -8,8 +9,11 @@ var script = {
8
9
  components: {
9
10
  BFormGroup
10
11
  },
11
- provide: {
12
- isInFormGroup: true
12
+ provide() {
13
+ return {
14
+ // Only for internal use by @gitlab/ui components
15
+ getFormGroupInstance: () => this
16
+ };
13
17
  },
14
18
  inheritAttrs: false,
15
19
  props: {
@@ -46,6 +50,11 @@ var script = {
46
50
  default: '(optional)'
47
51
  }
48
52
  },
53
+ data() {
54
+ return {
55
+ labelId: uniqueId('gl-form-group-label-')
56
+ };
57
+ },
49
58
  computed: {
50
59
  actualLabelClass() {
51
60
  const {
@@ -77,7 +86,7 @@ var script = {
77
86
  const __vue_script__ = script;
78
87
 
79
88
  /* template */
80
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('b-form-group',_vm._b({staticClass:"gl-form-group",attrs:{"label-class":_vm.actualLabelClass},scopedSlots:_vm._u([(_vm.$attrs.label || _vm.$scopedSlots.label)?{key:"label",fn:function(){return [_vm._t("label",function(){return [_vm._v("\n "+_vm._s(_vm.$attrs.label)+"\n "),(_vm.optional)?_c('span',{staticClass:"optional-label",attrs:{"data-testid":"optional-label"}},[_vm._v(_vm._s(_vm.optionalText))]):_vm._e()]}),_vm._v(" "),(_vm.hasLabelDescription)?_c('div',{staticClass:"label-description",attrs:{"data-testid":"label-description"}},[_vm._t("label-description",function(){return [_vm._v(_vm._s(_vm.labelDescription))]})],2):_vm._e()]},proxy:true}:null,_vm._l((Object.keys(_vm.$slots)),function(slot){return {key:slot,fn:function(){return [_vm._t(slot)]},proxy:true}})],null,true)},'b-form-group',_vm.$attrs,false))};
89
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('b-form-group',_vm._b({staticClass:"gl-form-group",attrs:{"label-class":_vm.actualLabelClass,"label-id":_vm.labelId},scopedSlots:_vm._u([(_vm.$attrs.label || _vm.$scopedSlots.label)?{key:"label",fn:function(){return [_vm._t("label",function(){return [_vm._v("\n "+_vm._s(_vm.$attrs.label)+"\n "),(_vm.optional)?_c('span',{staticClass:"optional-label",attrs:{"data-testid":"optional-label"}},[_vm._v(_vm._s(_vm.optionalText))]):_vm._e()]}),_vm._v(" "),(_vm.hasLabelDescription)?_c('div',{staticClass:"label-description",attrs:{"data-testid":"label-description"}},[_vm._t("label-description",function(){return [_vm._v(_vm._s(_vm.labelDescription))]})],2):_vm._e()]},proxy:true}:null,_vm._l((Object.keys(_vm.$slots)),function(slot){return {key:slot,fn:function(){return [_vm._t(slot)]},proxy:true}})],null,true)},'b-form-group',_vm.$attrs,false))};
81
90
  var __vue_staticRenderFns__ = [];
82
91
 
83
92
  /* style */
@@ -25,6 +25,11 @@ var script = {
25
25
  directives: {
26
26
  Outside: OutsideDirective
27
27
  },
28
+ inject: {
29
+ getFormGroupInstance: {
30
+ default: () => () => {}
31
+ }
32
+ },
28
33
  props: {
29
34
  toggleText: {
30
35
  type: String,
@@ -266,16 +271,41 @@ var script = {
266
271
  toggleButtonTextClasses() {
267
272
  return this.block ? 'gl-w-full' : '';
268
273
  },
274
+ // Set the aria-labelledby property with one or more ID strings
269
275
  toggleLabelledBy() {
276
+ var _this$getFormGroupIns;
277
+ const formGroupLabelId = (_this$getFormGroupIns = this.getFormGroupInstance()) === null || _this$getFormGroupIns === void 0 ? void 0 : _this$getFormGroupIns.labelId;
270
278
  if (this.isToggleCombobox) {
279
+ // Comboboxes announce label and self value when aria-labelledby is label ID.
280
+ // Tested with VoiceOver, NVDA, JAWS, Narrator and preferred browsers.
271
281
  if (this.ariaLabelledby) {
272
- return `${this.ariaLabelledby} ${this.toggleId}`;
282
+ return `${this.ariaLabelledby}`;
273
283
  }
284
+
285
+ // Combobox inside GlFormGroup
286
+ if (formGroupLabelId) {
287
+ return `${formGroupLabelId} ${this.toggleId}`;
288
+ }
289
+
290
+ // Fallback calculated toggleId value
274
291
  return this.toggleId;
275
292
  }
293
+ if (!this.isToggleCombobox) {
294
+ // Disclosures or buttons with listbox require both IDs to announce correctly.
295
+ // Tested with VoiceOver, NVDA, JAWS, Narrator and preferred browsers.
296
+ if (this.ariaLabelledby) {
297
+ return `${this.ariaLabelledby} ${this.toggleId}`;
298
+ }
299
+
300
+ // Disclosure or button with listbox inside GlFormGroup
301
+ if (formGroupLabelId) {
302
+ return `${formGroupLabelId} ${this.toggleId}`;
303
+ }
276
304
 
277
- // For non-combobox toggles, combine IDs or use the button's own text
278
- return this.ariaLabelledby ? `${this.ariaLabelledby} ${this.toggleId}` : undefined;
305
+ // Fallback calculated toggleId value
306
+ return this.toggleId;
307
+ }
308
+ return undefined;
279
309
  },
280
310
  toggleRole() {
281
311
  if (this.isToggleCombobox) {
@@ -39,8 +39,8 @@ var script = {
39
39
  GlIntersectionObserver
40
40
  },
41
41
  inject: {
42
- isInFormGroup: {
43
- default: false
42
+ getFormGroupInstance: {
43
+ default: () => () => {}
44
44
  }
45
45
  },
46
46
  model: {
@@ -385,6 +385,9 @@ var script = {
385
385
  // Fallback. Return a header ID or the toggle button ID.
386
386
  return this.listAriaLabelledBy || this.headerId || this.toggleIdComputed;
387
387
  },
388
+ isInFormGroup() {
389
+ return Boolean(this.getFormGroupInstance());
390
+ },
388
391
  toggleIdComputed() {
389
392
  return this.toggleId || uniqueId('dropdown-toggle-btn-');
390
393
  },
@@ -53,6 +53,7 @@ const generateProps = () => makePropsConfigurable(sortKeys({
53
53
  label: makeProp(PROP_TYPE_STRING),
54
54
  labelClass: makeProp(PROP_TYPE_ARRAY_OBJECT_STRING),
55
55
  labelFor: makeProp(PROP_TYPE_STRING),
56
+ labelId: makeProp(PROP_TYPE_STRING),
56
57
  labelSize: makeProp(PROP_TYPE_STRING),
57
58
  labelSrOnly: makeProp(PROP_TYPE_BOOLEAN, false),
58
59
  tooltip: makeProp(PROP_TYPE_BOOLEAN, false),
@@ -209,7 +210,7 @@ const BFormGroup = {
209
210
  const isFieldset = !labelFor;
210
211
  let $label = h();
211
212
  const labelContent = normalizeSlot(SLOT_NAME_LABEL) || this.label;
212
- const labelId = labelContent ? safeId('_BV_label_') : null;
213
+ const labelId = labelContent ? this.labelId || safeId('_BV_label_') : null;
213
214
  if (labelContent || isHorizontal) {
214
215
  const {
215
216
  labelSize,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "128.11.0",
3
+ "version": "128.12.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,7 @@
1
1
  <script>
2
2
  import isString from 'lodash/isString';
3
3
  import isPlainObject from 'lodash/isPlainObject';
4
+ import { uniqueId } from 'lodash';
4
5
  import { BFormGroup } from '../../../../vendor/bootstrap-vue/src/components/form-group/form-group';
5
6
 
6
7
  export default {
@@ -8,8 +9,11 @@ export default {
8
9
  components: {
9
10
  BFormGroup,
10
11
  },
11
- provide: {
12
- isInFormGroup: true,
12
+ provide() {
13
+ return {
14
+ // Only for internal use by @gitlab/ui components
15
+ getFormGroupInstance: () => this,
16
+ };
13
17
  },
14
18
  inheritAttrs: false,
15
19
  props: {
@@ -46,6 +50,11 @@ export default {
46
50
  default: '(optional)',
47
51
  },
48
52
  },
53
+ data() {
54
+ return {
55
+ labelId: uniqueId('gl-form-group-label-'),
56
+ };
57
+ },
49
58
  computed: {
50
59
  actualLabelClass() {
51
60
  const { labelClass } = this;
@@ -70,7 +79,12 @@ export default {
70
79
  };
71
80
  </script>
72
81
  <template>
73
- <b-form-group v-bind="$attrs" class="gl-form-group" :label-class="actualLabelClass">
82
+ <b-form-group
83
+ v-bind="$attrs"
84
+ class="gl-form-group"
85
+ :label-class="actualLabelClass"
86
+ :label-id="labelId"
87
+ >
74
88
  <template v-if="$attrs.label || $scopedSlots.label" #label>
75
89
  <slot name="label">
76
90
  {{ $attrs.label }}
@@ -55,6 +55,11 @@ export default {
55
55
  GlIcon,
56
56
  },
57
57
  directives: { Outside: OutsideDirective },
58
+ inject: {
59
+ getFormGroupInstance: {
60
+ default: () => () => {},
61
+ },
62
+ },
58
63
  props: {
59
64
  toggleText: {
60
65
  type: String,
@@ -298,16 +303,43 @@ export default {
298
303
  toggleButtonTextClasses() {
299
304
  return this.block ? 'gl-w-full' : '';
300
305
  },
306
+ // Set the aria-labelledby property with one or more ID strings
301
307
  toggleLabelledBy() {
308
+ const formGroupLabelId = this.getFormGroupInstance()?.labelId;
309
+
302
310
  if (this.isToggleCombobox) {
311
+ // Comboboxes announce label and self value when aria-labelledby is label ID.
312
+ // Tested with VoiceOver, NVDA, JAWS, Narrator and preferred browsers.
313
+ if (this.ariaLabelledby) {
314
+ return `${this.ariaLabelledby}`;
315
+ }
316
+
317
+ // Combobox inside GlFormGroup
318
+ if (formGroupLabelId) {
319
+ return `${formGroupLabelId} ${this.toggleId}`;
320
+ }
321
+
322
+ // Fallback calculated toggleId value
323
+ return this.toggleId;
324
+ }
325
+
326
+ if (!this.isToggleCombobox) {
327
+ // Disclosures or buttons with listbox require both IDs to announce correctly.
328
+ // Tested with VoiceOver, NVDA, JAWS, Narrator and preferred browsers.
303
329
  if (this.ariaLabelledby) {
304
330
  return `${this.ariaLabelledby} ${this.toggleId}`;
305
331
  }
332
+
333
+ // Disclosure or button with listbox inside GlFormGroup
334
+ if (formGroupLabelId) {
335
+ return `${formGroupLabelId} ${this.toggleId}`;
336
+ }
337
+
338
+ // Fallback calculated toggleId value
306
339
  return this.toggleId;
307
340
  }
308
341
 
309
- // For non-combobox toggles, combine IDs or use the button's own text
310
- return this.ariaLabelledby ? `${this.ariaLabelledby} ${this.toggleId}` : undefined;
342
+ return undefined;
311
343
  },
312
344
  toggleRole() {
313
345
  if (this.isToggleCombobox) {
@@ -67,8 +67,8 @@ export default {
67
67
  GlIntersectionObserver,
68
68
  },
69
69
  inject: {
70
- isInFormGroup: {
71
- default: false,
70
+ getFormGroupInstance: {
71
+ default: () => () => {},
72
72
  },
73
73
  },
74
74
  model: {
@@ -417,6 +417,9 @@ export default {
417
417
  // Fallback. Return a header ID or the toggle button ID.
418
418
  return this.listAriaLabelledBy || this.headerId || this.toggleIdComputed;
419
419
  },
420
+ isInFormGroup() {
421
+ return Boolean(this.getFormGroupInstance());
422
+ },
420
423
  toggleIdComputed() {
421
424
  return this.toggleId || uniqueId('dropdown-toggle-btn-');
422
425
  },
@@ -74,6 +74,7 @@ export const generateProps = () =>
74
74
  label: makeProp(PROP_TYPE_STRING),
75
75
  labelClass: makeProp(PROP_TYPE_ARRAY_OBJECT_STRING),
76
76
  labelFor: makeProp(PROP_TYPE_STRING),
77
+ labelId: makeProp(PROP_TYPE_STRING),
77
78
  labelSize: makeProp(PROP_TYPE_STRING),
78
79
  labelSrOnly: makeProp(PROP_TYPE_BOOLEAN, false),
79
80
  tooltip: makeProp(PROP_TYPE_BOOLEAN, false),
@@ -241,7 +242,7 @@ export const BFormGroup = {
241
242
 
242
243
  let $label = h()
243
244
  const labelContent = normalizeSlot(SLOT_NAME_LABEL) || this.label
244
- const labelId = labelContent ? safeId('_BV_label_') : null
245
+ const labelId = labelContent ? this.labelId || safeId('_BV_label_') : null
245
246
  if (labelContent || isHorizontal) {
246
247
  const { labelSize, labelColProps } = this
247
248
  const labelTag = isFieldset ? 'legend' : 'label'