@gitlab/ui 137.3.1 → 138.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +3 -12
  2. package/dist/components/base/alert/alert.js +0 -1
  3. package/dist/components/base/drawer/drawer.js +0 -1
  4. package/dist/components/base/filtered_search/filtered_search_suggestion_list.js +1 -1
  5. package/dist/components/base/filtered_search/filtered_search_token_segment.js +0 -1
  6. package/dist/components/base/form/form_group/form_group.js +0 -1
  7. package/dist/components/base/form/form_select/form_select.js +322 -6
  8. package/dist/components/base/modal/modal.js +1 -5
  9. package/dist/components/base/new_dropdowns/base_dropdown/dropdown_container.js +1 -1
  10. package/dist/components/base/new_dropdowns/disclosure/disclosure_dropdown.js +1 -3
  11. package/dist/components/base/toast/index.js +1 -1
  12. package/dist/components/base/toggle/toggle.js +0 -1
  13. package/dist/components/mixins/tooltip_mixin.js +1 -1
  14. package/dist/index.css +2 -2
  15. package/dist/index.css.map +1 -1
  16. package/dist/tailwind.css +1 -1
  17. package/dist/tailwind.css.map +1 -1
  18. package/dist/tokens/build/js/tokens.dark.js +299 -299
  19. package/dist/tokens/build/js/tokens.js +302 -302
  20. package/dist/utils/confetti/confetti.js +189 -0
  21. package/dist/utils/confetti/constants.js +36 -0
  22. package/dist/utils/confetti/field.js +114 -0
  23. package/dist/utils/confetti/palette.js +6 -0
  24. package/dist/utils/confetti/particles.js +92 -0
  25. package/dist/utils/matrix/columns.js +54 -0
  26. package/dist/utils/matrix/constants.js +19 -0
  27. package/dist/utils/matrix/matrix.js +157 -0
  28. package/dist/utils/matrix/palette.js +9 -0
  29. package/dist/utils.js +2 -0
  30. package/dist/vendor/bootstrap-vue/src/components/popover/helpers/bv-popover-template.js +4 -2
  31. package/dist/vendor/bootstrap-vue/src/components/tooltip/helpers/bv-tooltip-template.js +3 -1
  32. package/dist/vendor/bootstrap-vue/src/constants/components.js +1 -4
  33. package/package.json +4 -11
  34. package/src/components/base/alert/alert.vue +0 -1
  35. package/src/components/base/drawer/drawer.vue +0 -1
  36. package/src/components/base/filtered_search/filtered_search_suggestion_list.vue +2 -1
  37. package/src/components/base/filtered_search/filtered_search_token_segment.vue +0 -1
  38. package/src/components/base/form/form_group/form_group.vue +0 -2
  39. package/src/components/base/form/form_select/form_select.vue +389 -10
  40. package/src/components/base/modal/modal.vue +0 -3
  41. package/src/components/base/new_dropdowns/base_dropdown/dropdown_container.js +1 -1
  42. package/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.vue +0 -1
  43. package/src/components/base/tabs/tabs/scrollable_tabs.vue +0 -1
  44. package/src/components/base/tabs/tabs/tabs.vue +0 -1
  45. package/src/components/base/toast/index.js +1 -1
  46. package/src/components/base/toggle/toggle.vue +0 -1
  47. package/src/components/charts/heatmap/index.js +1 -1
  48. package/src/components/mixins/tooltip_mixin.js +1 -1
  49. package/src/tokens/build/css/tokens.css +74 -74
  50. package/src/tokens/build/css/tokens.dark.css +70 -70
  51. package/src/tokens/build/docs/tokens-tailwind-docs.dark.json +303 -303
  52. package/src/tokens/build/docs/tokens-tailwind-docs.json +323 -323
  53. package/src/tokens/build/figma/constants.dark.json +207 -207
  54. package/src/tokens/build/figma/constants.json +207 -207
  55. package/src/tokens/build/figma/mode.dark.json +37 -37
  56. package/src/tokens/build/figma/mode.json +35 -35
  57. package/src/tokens/build/js/tokens.dark.js +299 -300
  58. package/src/tokens/build/js/tokens.js +302 -303
  59. package/src/tokens/build/json/tokens.dark.json +369 -369
  60. package/src/tokens/build/json/tokens.json +376 -376
  61. package/src/tokens/build/scss/_tokens.dark.scss +70 -70
  62. package/src/tokens/build/scss/_tokens.scss +74 -74
  63. package/src/tokens/constant/color.blue.tokens.json +39 -39
  64. package/src/tokens/constant/color.green.tokens.json +41 -41
  65. package/src/tokens/constant/color.orange.tokens.json +43 -43
  66. package/src/tokens/constant/color.purple.tokens.json +40 -40
  67. package/src/tokens/constant/color.red.tokens.json +44 -44
  68. package/src/tokens/contextual/avatar.tokens.json +13 -13
  69. package/src/tokens/contextual/button.tokens.json +22 -22
  70. package/src/tokens/contextual/chart.tokens.json +3 -3
  71. package/src/tokens/semantic/action.tokens.json +18 -18
  72. package/src/utils/confetti/confetti.js +191 -0
  73. package/src/utils/confetti/constants.js +42 -0
  74. package/src/utils/confetti/field.js +134 -0
  75. package/src/utils/confetti/palette.js +15 -0
  76. package/src/utils/confetti/particles.js +98 -0
  77. package/src/utils/matrix/columns.js +53 -0
  78. package/src/utils/matrix/constants.js +19 -0
  79. package/src/utils/matrix/matrix.js +185 -0
  80. package/src/utils/matrix/palette.js +18 -0
  81. package/src/utils.js +2 -0
  82. package/src/vendor/bootstrap-vue/src/components/popover/helpers/bv-popover-template.js +7 -2
  83. package/src/vendor/bootstrap-vue/src/components/tooltip/helpers/bv-tooltip-template.js +2 -1
  84. package/src/vendor/bootstrap-vue/src/constants/components.js +0 -3
  85. package/translations.js +1 -1
  86. package/dist/vendor/bootstrap-vue/src/components/form-select/form-select-option-group.js +0 -51
  87. package/dist/vendor/bootstrap-vue/src/components/form-select/form-select-option.js +0 -41
  88. package/dist/vendor/bootstrap-vue/src/components/form-select/form-select.js +0 -153
  89. package/dist/vendor/bootstrap-vue/src/components/form-select/helpers/mixin-options.js +0 -63
  90. package/dist/vendor/bootstrap-vue/src/components/form-select/index.js +0 -3
  91. package/src/vendor/bootstrap-vue/src/components/form-select/form-select-option-group.js +0 -46
  92. package/src/vendor/bootstrap-vue/src/components/form-select/form-select-option.js +0 -35
  93. package/src/vendor/bootstrap-vue/src/components/form-select/form-select.js +0 -156
  94. package/src/vendor/bootstrap-vue/src/components/form-select/helpers/mixin-options.js +0 -60
  95. package/src/vendor/bootstrap-vue/src/components/form-select/index.js +0 -5
@@ -42,10 +42,12 @@ const BVPopoverTemplate = /*#__PURE__*/extend({
42
42
  attrs: {
43
43
  role: 'presentation'
44
44
  }
45
- }, [$title]), isUndefinedOrNull($content) || $content === '' ? /* istanbul ignore next */h() : h('div', {
45
+ },
46
+ // `innerHTML` overrides children, so avoid passing both — Vue 3 warns otherwise
47
+ titleDomProps.innerHTML ? [] : [$title]), isUndefinedOrNull($content) || $content === '' ? /* istanbul ignore next */h() : h('div', {
46
48
  staticClass: 'popover-body',
47
49
  domProps: contentDomProps
48
- }, [$content])]);
50
+ }, contentDomProps.innerHTML ? [] : [$content])]);
49
51
  }
50
52
  }
51
53
  });
@@ -113,7 +113,9 @@ const BVTooltipTemplate = /*#__PURE__*/extend({
113
113
  }), h('div', {
114
114
  staticClass: 'tooltip-inner',
115
115
  domProps
116
- }, [$title])]);
116
+ },
117
+ // `innerHTML` overrides children, so avoid passing both — Vue 3 warns otherwise
118
+ domProps.innerHTML ? [] : [$title])]);
117
119
  }
118
120
  }
119
121
  });
@@ -14,9 +14,6 @@ const NAME_FORM = 'BForm';
14
14
  const NAME_FORM_GROUP = 'BFormGroup';
15
15
  const NAME_FORM_INVALID_FEEDBACK = 'BFormInvalidFeedback';
16
16
  const NAME_FORM_ROW = 'BFormRow';
17
- const NAME_FORM_SELECT = 'BFormSelect';
18
- const NAME_FORM_SELECT_OPTION = 'BFormSelectOption';
19
- const NAME_FORM_SELECT_OPTION_GROUP = 'BFormSelectOptionGroup';
20
17
  const NAME_FORM_TEXT = 'BFormText';
21
18
  const NAME_FORM_VALID_FEEDBACK = 'BFormValidFeedback';
22
19
  const NAME_LINK = 'BLink';
@@ -51,4 +48,4 @@ const NAME_TRANSITION = 'BVTransition';
51
48
  const NAME_TRANSPORTER = 'BVTransporter';
52
49
  const NAME_TRANSPORTER_TARGET = 'BVTransporterTarget';
53
50
 
54
- export { NAME_BUTTON, NAME_BUTTON_CLOSE, NAME_COL, NAME_DROPDOWN, NAME_DROPDOWN_DIVIDER, NAME_DROPDOWN_FORM, NAME_DROPDOWN_GROUP, NAME_DROPDOWN_HEADER, NAME_DROPDOWN_ITEM, NAME_DROPDOWN_ITEM_BUTTON, NAME_DROPDOWN_TEXT, NAME_FORM, NAME_FORM_BUTTON_LABEL_CONTROL, NAME_FORM_GROUP, NAME_FORM_INVALID_FEEDBACK, NAME_FORM_RATING_STAR, NAME_FORM_ROW, NAME_FORM_SELECT, NAME_FORM_SELECT_OPTION, NAME_FORM_SELECT_OPTION_GROUP, NAME_FORM_TEXT, NAME_FORM_VALID_FEEDBACK, NAME_LINK, NAME_MODAL, NAME_POPOVER, NAME_POPOVER_HELPER, NAME_POPOVER_TEMPLATE, NAME_POPPER, NAME_TAB, NAME_TABLE, NAME_TABLE_CELL, NAME_TABLE_LITE, NAME_TABLE_SIMPLE, NAME_TABS, NAME_TAB_BUTTON_HELPER, NAME_TBODY, NAME_TFOOT, NAME_TH, NAME_THEAD, NAME_TOAST, NAME_TOASTER, NAME_TOAST_POP, NAME_TOOLTIP, NAME_TOOLTIP_HELPER, NAME_TOOLTIP_TEMPLATE, NAME_TR, NAME_TRANSITION, NAME_TRANSPORTER, NAME_TRANSPORTER_TARGET };
51
+ export { NAME_BUTTON, NAME_BUTTON_CLOSE, NAME_COL, NAME_DROPDOWN, NAME_DROPDOWN_DIVIDER, NAME_DROPDOWN_FORM, NAME_DROPDOWN_GROUP, NAME_DROPDOWN_HEADER, NAME_DROPDOWN_ITEM, NAME_DROPDOWN_ITEM_BUTTON, NAME_DROPDOWN_TEXT, NAME_FORM, NAME_FORM_BUTTON_LABEL_CONTROL, NAME_FORM_GROUP, NAME_FORM_INVALID_FEEDBACK, NAME_FORM_RATING_STAR, NAME_FORM_ROW, NAME_FORM_TEXT, NAME_FORM_VALID_FEEDBACK, NAME_LINK, NAME_MODAL, NAME_POPOVER, NAME_POPOVER_HELPER, NAME_POPOVER_TEMPLATE, NAME_POPPER, NAME_TAB, NAME_TABLE, NAME_TABLE_CELL, NAME_TABLE_LITE, NAME_TABLE_SIMPLE, NAME_TABS, NAME_TAB_BUTTON_HELPER, NAME_TBODY, NAME_TFOOT, NAME_TH, NAME_THEAD, NAME_TOAST, NAME_TOASTER, NAME_TOAST_POP, NAME_TOOLTIP, NAME_TOOLTIP_HELPER, NAME_TOOLTIP_TEMPLATE, NAME_TR, NAME_TRANSITION, NAME_TRANSPORTER, NAME_TRANSPORTER_TARGET };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "137.3.1",
3
+ "version": "138.1.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -89,14 +89,6 @@
89
89
  "tailwindcss": "^3",
90
90
  "vue": "^2.7.16"
91
91
  },
92
- "resolutions": {
93
- "chokidar": "^4.0.3",
94
- "sane": "^5.0.1",
95
- "jackspeak": "2.3.6",
96
- "postcss": "8.5.28",
97
- "json5": "2.2.3",
98
- "rollup-plugin-vue/@vue/component-compiler/postcss-modules-sync/generic-names/loader-utils": "3.3.1"
99
- },
100
92
  "devDependencies": {
101
93
  "@adobe/leonardo-contrast-colors": "^1.1.0",
102
94
  "@babel/core": "^7.29.7",
@@ -107,8 +99,9 @@
107
99
  "@cypress/grep": "^4.1.1",
108
100
  "@figma/code-connect": "^1.5.3",
109
101
  "@gitlab/fonts": "^1.3.1",
110
- "@gitlab/hybrid-vue": "npm:@vue/compat@3.5.34",
102
+ "@gitlab/hybrid-vue": "npm:@vue/compat@3.5.42",
111
103
  "@gitlab/svgs": "*",
104
+ "@gitlab/vue-router-vue3": "npm:vue-router@^4.6.4",
112
105
  "@jest/test-sequencer": "30.5.2",
113
106
  "@rollup/plugin-commonjs": "^28.0.9",
114
107
  "@rollup/plugin-node-resolve": "^16.0.3",
@@ -169,7 +162,7 @@
169
162
  "rollup-plugin-string": "^3.0.0",
170
163
  "rollup-plugin-svg": "^2.0.0",
171
164
  "rollup-plugin-vue": "^5.1.9",
172
- "sass": "^1.104.1",
165
+ "sass": "^1.105.1",
173
166
  "sass-loader": "^10.5.2",
174
167
  "sass-true": "^9",
175
168
  "start-server-and-test": "^2.1.5",
@@ -147,7 +147,6 @@ export default {
147
147
  return alertVariantIconMap[this.variant];
148
148
  },
149
149
  shouldRenderActions() {
150
- // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
151
150
  return Boolean(this.$slots.actions || this.actionButtons.length);
152
151
  },
153
152
  actionButtons() {
@@ -72,7 +72,6 @@ export default {
72
72
  };
73
73
  },
74
74
  shouldRenderFooter() {
75
- // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
76
75
  return Boolean(this.$slots.footer);
77
76
  },
78
77
  variantClass() {
@@ -117,7 +117,8 @@ export default {
117
117
  </script>
118
118
  <template>
119
119
  <ul :class="listClasses">
120
- <li ref="top"></li>
120
+ <!-- Sentinel used by stepItem() to reset scroll position; not a real suggestion. -->
121
+ <li ref="top" aria-hidden="true" role="presentation"></li>
121
122
  <!-- @slot The suggestions (implemented with GlFilteredSearchSuggestion). -->
122
123
  <slot></slot>
123
124
  </ul>
@@ -212,7 +212,6 @@ export default {
212
212
  },
213
213
 
214
214
  hasOptionsOrSuggestions() {
215
- // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
216
215
  return this.options?.length || isSlotNotEmpty(this.$slots.suggestions);
217
216
  },
218
217
 
@@ -70,7 +70,6 @@ export default {
70
70
  return defaultClass;
71
71
  },
72
72
  hasLabelDescription() {
73
- // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
74
73
  return Boolean(this.labelDescription || this.$slots['label-description']);
75
74
  },
76
75
  },
@@ -95,7 +94,6 @@ export default {
95
94
  </div>
96
95
  </template>
97
96
 
98
- <!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
99
97
  <template v-for="slot in Object.keys($slots)" #[slot]>
100
98
  <slot :name="slot"></slot>
101
99
  </template>
@@ -1,17 +1,185 @@
1
1
  <script>
2
- import { isObject } from 'lodash-es';
3
- import { BFormSelect } from '../../../../vendor/bootstrap-vue/src/components/form-select/form-select';
2
+ import { isObject, uniqueId, isPlainObject, isBoolean, isUndefined, isNull, get } from 'lodash-es';
4
3
  import { formInputWidths } from '../../../../utils/constants';
4
+ import { isVisible } from '../../../../utils/utils';
5
+ import { SafeHtmlDirective as SafeHtml } from '../../../../directives/safe_html/safe_html';
5
6
 
6
7
  export default {
7
8
  name: 'GlFormSelect',
8
- components: {
9
- BFormSelect,
9
+ directives: {
10
+ SafeHtml,
10
11
  },
11
12
  inheritAttrs: false,
12
13
  props: {
13
14
  /**
14
- * Width of the select element.
15
+ * Current value of the select. Should be set to an array when the 'multiple' prop is set
16
+ */
17
+ value: {
18
+ // Value can be any type!
19
+ type: undefined,
20
+ required: false,
21
+ default: undefined,
22
+ },
23
+ /**
24
+ * Array of items to render in the component
25
+ */
26
+ options: {
27
+ type: [Array, Object],
28
+ required: false,
29
+ default: undefined,
30
+ },
31
+ /**
32
+ * When set to a number larger than 0, will set the number of display option rows. Note not all browser will respect this setting
33
+ * @deprecated Only useful with `multiple`, which is also deprecated.
34
+ */
35
+ selectSize: {
36
+ type: Number,
37
+ required: false,
38
+ default: 0,
39
+ },
40
+ /**
41
+ * Used to set the `id` attribute on the rendered content, and used as the base to generate any additional element IDs as needed
42
+ */
43
+ id: {
44
+ type: String,
45
+ required: false,
46
+ default: undefined,
47
+ },
48
+ /**
49
+ * Render the form control in plain mode, rather than custom styled mode
50
+ * @deprecated Use a native <select> element instead.
51
+ */
52
+ plain: {
53
+ type: Boolean,
54
+ required: false,
55
+ default: false,
56
+ },
57
+ /**
58
+ * Set the size of the component's appearance. 'sm', 'md' (default), or 'lg'
59
+ * @deprecated Has no effect. Use the `width` prop instead.
60
+ */
61
+ size: {
62
+ type: String,
63
+ required: false,
64
+ default: undefined,
65
+ },
66
+ /**
67
+ * Optional value to set for the 'aria-invalid' attribute. Supported values are 'true' and 'false'. If not set, the 'state' prop will dictate the value
68
+ */
69
+ ariaInvalid: {
70
+ type: [Boolean, String],
71
+ required: false,
72
+ default: false,
73
+ },
74
+ /**
75
+ * Controls the validation state appearance of the component. `true` for valid, `false` for invalid, or `null` for no validation state
76
+ */
77
+ state: {
78
+ // Tri-state prop: true, false, null (or undefined)
79
+ type: Boolean,
80
+ required: false,
81
+ default: null,
82
+ },
83
+ /**
84
+ * Field name in the `options` array that should be used for the value
85
+ */
86
+ valueField: {
87
+ type: String,
88
+ required: false,
89
+ default: 'value',
90
+ },
91
+ /**
92
+ * The key to use from the option object to get the label
93
+ */
94
+ labelField: {
95
+ type: String,
96
+ required: false,
97
+ default: 'label',
98
+ },
99
+ /**
100
+ * The key to use from the option object to get the options
101
+ */
102
+ optionsField: {
103
+ type: String,
104
+ required: false,
105
+ default: 'options',
106
+ },
107
+ /**
108
+ * Field name in the `options` array that should be used for the text label
109
+ */
110
+ textField: {
111
+ type: String,
112
+ required: false,
113
+ default: 'text',
114
+ },
115
+ /**
116
+ * Field name in the `options` array that should be used for the disabled state
117
+ */
118
+ disabledField: {
119
+ type: String,
120
+ required: false,
121
+ default: 'disabled',
122
+ },
123
+ /**
124
+ * Field name in the `options` array that should be used for the html label instead of text field
125
+ * @deprecated Using the `.html` field (whatever its name) is potentially unsafe.
126
+ */
127
+ htmlField: {
128
+ type: String,
129
+ required: false,
130
+ default: 'html',
131
+ },
132
+ /**
133
+ * When set, allows multiple options to be selected (multi-select)
134
+ * @deprecated Use GlCollapsibleListbox instead.
135
+ */
136
+ multiple: {
137
+ type: Boolean,
138
+ required: false,
139
+ default: false,
140
+ },
141
+ /**
142
+ * When set to `true`, attempts to auto-focus the control when it is mounted, or re-activated when in a keep-alive. Does not set the `autofocus` attribute on the control
143
+ */
144
+ autofocus: {
145
+ type: Boolean,
146
+ required: false,
147
+ default: false,
148
+ },
149
+ /**
150
+ * When set to `true`, disables the component's functionality and places it in a disabled state
151
+ */
152
+ disabled: {
153
+ type: Boolean,
154
+ required: false,
155
+ default: false,
156
+ },
157
+ /**
158
+ * ID of the form that the form control belongs to. Sets the `form` attribute on the control
159
+ */
160
+ form: {
161
+ type: String,
162
+ required: false,
163
+ default: undefined,
164
+ },
165
+ /**
166
+ * Sets the value of the `name` attribute on the form control
167
+ */
168
+ name: {
169
+ type: String,
170
+ required: false,
171
+ default: undefined,
172
+ },
173
+ /**
174
+ * Adds the `required` attribute to the form control
175
+ */
176
+ required: {
177
+ type: Boolean,
178
+ required: false,
179
+ default: false,
180
+ },
181
+ /**
182
+ * Maximum width of the Select
15
183
  */
16
184
  width: {
17
185
  type: [String, Object],
@@ -32,7 +200,32 @@ export default {
32
200
  default: '',
33
201
  },
34
202
  },
203
+ data() {
204
+ return {
205
+ localValue: this.value,
206
+ localId: null,
207
+ };
208
+ },
35
209
  computed: {
210
+ computedId() {
211
+ return this.id || this.localId;
212
+ },
213
+ computedSelectSize() {
214
+ // Custom selects with a size of zero causes the arrows to be hidden,
215
+ // so dont render the size attribute in this case
216
+ return !this.plain && this.selectSize === 0 ? null : this.selectSize;
217
+ },
218
+ computedAriaInvalid() {
219
+ const { ariaInvalid } = this;
220
+ if (ariaInvalid === true || ariaInvalid === 'true' || ariaInvalid === '') {
221
+ return 'true';
222
+ }
223
+ return this.computedState === false ? 'true' : ariaInvalid;
224
+ },
225
+ computedState() {
226
+ // If not a boolean, ensure that value is null
227
+ return isBoolean(this.state) ? this.state : null;
228
+ },
36
229
  cssClasses() {
37
230
  if (this.width === null) {
38
231
  return [];
@@ -54,16 +247,202 @@ export default {
54
247
  // eslint-disable-next-line @gitlab/tailwind-no-interpolation -- Not a CSS utility
55
248
  return [`gl-form-select-${this.width}`];
56
249
  },
250
+ stateClass() {
251
+ if (this.computedState === true) return 'is-valid';
252
+ if (this.computedState === false) return 'is-invalid';
253
+ return null;
254
+ },
255
+ inputClass() {
256
+ return [
257
+ 'gl-form-select',
258
+ this.plain ? 'form-control' : 'custom-select',
259
+ this.size && this.plain ? `form-control-${this.size}` : null,
260
+ this.size && !this.plain ? `custom-select-${this.size}` : null,
261
+ this.stateClass,
262
+ this.selectClass,
263
+ ];
264
+ },
265
+ normalizedOptions() {
266
+ return this.normalizeOptions(this.options);
267
+ },
268
+ },
269
+ watch: {
270
+ value(newValue) {
271
+ this.localValue = newValue;
272
+ },
273
+ localValue(newValue) {
274
+ /**
275
+ * Emitted when the select value changes
276
+ *
277
+ * @event input
278
+ * @type {any}
279
+ */
280
+ this.$emit('input', newValue);
281
+ },
282
+ },
283
+ activated() {
284
+ this.handleAutofocus();
285
+ },
286
+ async mounted() {
287
+ this.handleAutofocus();
288
+ await this.$nextTick();
289
+ // Update DOM with auto-generated ID after mount
290
+ // to prevent SSR hydration errors
291
+ this.localId = uniqueId('gl-form-select-');
292
+ },
293
+ methods: {
294
+ focus() {
295
+ this.$refs.select?.focus();
296
+ },
297
+ blur() {
298
+ this.$refs.select?.blur();
299
+ },
300
+ normalizeOptions(options) {
301
+ // Normalize the given options array
302
+ if (Array.isArray(options)) return options.map((option) => this.normalizeOption(option));
303
+
304
+ if (isPlainObject(options)) {
305
+ if (process.env.NODE_ENV !== 'production') {
306
+ // Deprecate the object options format
307
+ // eslint-disable-next-line no-console
308
+ console.warn(
309
+ '[@gitlab/ui] GlFormSelect - Setting prop "options" to an object is deprecated. Use the array format instead.',
310
+ );
311
+ }
312
+
313
+ // Normalize a `options` object to an array of options
314
+ return Object.keys(options).map((key) => this.normalizeOption(options[key] || {}, key));
315
+ }
316
+
317
+ // If not an array or object, return an empty array
318
+ return [];
319
+ },
320
+ normalizeOption(option, key = null) {
321
+ // When the option is an object, normalize it
322
+ if (isPlainObject(option)) {
323
+ const value = get(option, this.valueField);
324
+ const text = get(option, this.textField);
325
+ const options = get(option, this.optionsField, null);
326
+ // When it has options, create an <optgroup> object
327
+ if (!isNull(options)) {
328
+ return {
329
+ label: String(get(option, this.labelField) || text),
330
+ options: this.normalizeOptions(options),
331
+ };
332
+ }
333
+ // Otherwise create an <option> object
334
+ return {
335
+ value: isUndefined(value) ? key || text : value,
336
+ text: String(isUndefined(text) ? key : text),
337
+ html: get(option, this.htmlField),
338
+ disabled: Boolean(get(option, this.disabledField)),
339
+ };
340
+ }
341
+ // Otherwise create an <option> object from the given value
342
+ return {
343
+ value: key || option,
344
+ text: String(option),
345
+ disabled: false,
346
+ };
347
+ },
348
+ isOptionGroup(option) {
349
+ return Array.isArray(option.options);
350
+ },
351
+ async onChange(event) {
352
+ const { target } = event;
353
+ const selectedValue = Array.from(target.options)
354
+ .filter((option) => option.selected)
355
+ // NOTE: the _value property is set up by both Vue 2 and 3 explicitly for the
356
+ // purpose of handling non-string values on form elements.
357
+ //
358
+ // See:
359
+ // - https://github.com/vuejs/vue/blob/v2.7.16/src/platforms/web/runtime/modules/dom-props.ts#L42-L44
360
+ // - https://github.com/vuejs/vue/blob/v2.7.16/test/unit/modules/vdom/modules/dom-props.spec.ts#L38
361
+ // - https://github.com/vuejs/core/blob/v3.4.27/packages/runtime-dom/src/modules/props.ts#L48-L50
362
+ // - https://github.com/vuejs/core/blob/v3.4.27/packages/runtime-dom/__tests__/patchProps.spec.ts#L25
363
+ // eslint-disable-next-line no-underscore-dangle
364
+ .map((option) => ('_value' in option ? option._value : option.value));
365
+
366
+ this.localValue = target.multiple ? selectedValue : selectedValue[0];
367
+
368
+ await this.$nextTick();
369
+ /**
370
+ * Emitted when the select value changes via user interaction
371
+ *
372
+ * @event change
373
+ * @type {any}
374
+ */
375
+ this.$emit('change', this.localValue);
376
+ },
377
+ async handleAutofocus() {
378
+ await this.$nextTick();
379
+ window.requestAnimationFrame(() => {
380
+ if (this.autofocus && isVisible(this.$refs.select)) this.focus();
381
+ });
382
+ },
57
383
  },
58
384
  };
59
385
  </script>
60
386
  <template>
61
387
  <span class="gl-form-select-wrapper" :class="cssClasses">
62
- <b-form-select class="gl-form-select" v-bind="$attrs" :class="selectClass" v-on="$listeners">
63
- <!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
64
- <template v-for="slot in Object.keys($slots)" #[slot]>
65
- <slot :name="slot"></slot>
388
+ <select
389
+ :id="computedId"
390
+ ref="select"
391
+ v-model="localValue"
392
+ :aria-invalid="computedAriaInvalid"
393
+ :aria-required="required ? 'true' : null"
394
+ :class="inputClass"
395
+ :disabled="disabled"
396
+ :form="form"
397
+ :multiple="multiple"
398
+ :name="name"
399
+ :required="required"
400
+ :size="computedSelectSize"
401
+ v-bind="$attrs"
402
+ @change="onChange"
403
+ >
404
+ <!-- @slot Slot to place options or option groups above options provided via the 'options' prop -->
405
+ <slot name="first"></slot>
406
+
407
+ <template v-for="(option, index) of normalizedOptions">
408
+ <template v-if="isOptionGroup(option)">
409
+ <optgroup :key="index" :label="option.label">
410
+ <template v-for="(childOption, childIndex) of option.options">
411
+ <option
412
+ v-if="childOption.html"
413
+ :key="`${childIndex}-html`"
414
+ v-safe-html="childOption.html"
415
+ :value="childOption.value"
416
+ :disabled="childOption.disabled"
417
+ ></option>
418
+ <option
419
+ v-else
420
+ :key="childIndex"
421
+ :value="childOption.value"
422
+ :disabled="childOption.disabled"
423
+ >
424
+ {{ childOption.text }}
425
+ </option>
426
+ </template>
427
+ </optgroup>
428
+ </template>
429
+
430
+ <template v-else>
431
+ <option
432
+ v-if="option.html"
433
+ :key="`${index}-html`"
434
+ v-safe-html="option.html"
435
+ :value="option.value"
436
+ :disabled="option.disabled"
437
+ ></option>
438
+ <option v-else :key="index" :value="option.value" :disabled="option.disabled">
439
+ {{ option.text }}
440
+ </option>
441
+ </template>
66
442
  </template>
67
- </b-form-select>
443
+
444
+ <!-- @slot Content to place in the form select after 'options' prop options -->
445
+ <slot></slot>
446
+ </select>
68
447
  </span>
69
448
  </template>
@@ -146,11 +146,9 @@ export default {
146
146
  },
147
147
  computed: {
148
148
  shouldRenderModalOk() {
149
- // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
150
149
  return Boolean(this.$slots['modal-ok']);
151
150
  },
152
151
  shouldRenderModalCancel() {
153
- // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
154
152
  return Boolean(this.$slots['modal-cancel']);
155
153
  },
156
154
  shouldRenderModalFooter() {
@@ -158,7 +156,6 @@ export default {
158
156
  this.actionCancel ||
159
157
  this.actionSecondary ||
160
158
  this.actionPrimary ||
161
- // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
162
159
  this.$slots['modal-footer'],
163
160
  );
164
161
  },
@@ -1,4 +1,4 @@
1
- /* eslint-disable import/no-default-export */
1
+ /* eslint-disable import-x/no-default-export */
2
2
  import { MountingPortal } from 'portal-vue';
3
3
  import { POSITION_ABSOLUTE, POSITION_FIXED } from '../constants';
4
4
 
@@ -247,7 +247,6 @@ export default {
247
247
  disclosureTag() {
248
248
  if (
249
249
  this.items?.length ||
250
- // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
251
250
  hasOnlyListItems(this.$scopedSlots.default || this.$slots.default)
252
251
  ) {
253
252
  return 'ul';
@@ -125,7 +125,6 @@ export default {
125
125
  v-bind="passthroughAttrs"
126
126
  v-on="$listeners"
127
127
  >
128
- <!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
129
128
  <template v-for="slot in Object.keys($slots)" #[slot]>
130
129
  <slot :name="slot"></slot>
131
130
  </template>
@@ -238,7 +238,6 @@ export default {
238
238
  v-bind="$attrs"
239
239
  v-on="listeners"
240
240
  >
241
- <!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
242
241
  <template v-for="slot in Object.keys($slots)" #[slot]>
243
242
  <slot :name="slot"></slot>
244
243
  </template>
@@ -1,4 +1,4 @@
1
- /* eslint-disable import/no-default-export */
1
+ /* eslint-disable import-x/no-default-export */
2
2
  import Vue from 'vue';
3
3
  import { createNewChildComponent } from '../../../vendor/bootstrap-vue/src/utils/create-new-child-component';
4
4
  import GlToaster from './toaster.vue';
@@ -97,7 +97,6 @@ export default {
97
97
  return Boolean(this.$scopedSlots.description || this.description) && this.isVerticalLayout;
98
98
  },
99
99
  shouldRenderHelp() {
100
- // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots
101
100
  return Boolean(this.$slots.help || this.help) && this.isVerticalLayout;
102
101
  },
103
102
  toggleClasses() {
@@ -1,4 +1,4 @@
1
1
  import Heatmap from './heatmap.vue';
2
2
 
3
- // eslint-disable-next-line import/no-default-export
3
+ // eslint-disable-next-line import-x/no-default-export
4
4
  export default Heatmap;
@@ -1,6 +1,6 @@
1
1
  import { tooltipActionEvents } from '../../utils/constants';
2
2
 
3
- // eslint-disable-next-line import/no-default-export
3
+ // eslint-disable-next-line import-x/no-default-export
4
4
  export default (tooltipRefName) => ({
5
5
  mounted() {
6
6
  /**