@gitlab/ui 120.0.0 → 121.0.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,11 +1,18 @@
1
1
  import uniqueId from 'lodash/uniqueId';
2
- import { BFormCheckbox } from '../../../../vendor/bootstrap-vue/src/components/form-checkbox/form-checkbox';
2
+ import isBoolean from 'lodash/isBoolean';
3
+ import { looseEqual } from '../../../../vendor/bootstrap-vue/src/utils/loose-equal';
4
+ import { looseIndexOf } from '../../../../vendor/bootstrap-vue/src/utils/loose-index-of';
3
5
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
4
6
 
5
7
  var script = {
6
8
  name: 'GlFormCheckbox',
7
- components: {
8
- BFormCheckbox
9
+ inject: {
10
+ getGroup: {
11
+ // When we remove BFormCheckboxGroup from GlFormCheckboxGroup, we can rename
12
+ // the `getBvCheckGroup` provide to `getCheckGroup`.
13
+ from: 'getBvCheckGroup',
14
+ default: () => () => null
15
+ }
9
16
  },
10
17
  inheritAttrs: false,
11
18
  model: {
@@ -13,28 +20,273 @@ var script = {
13
20
  event: 'input'
14
21
  },
15
22
  props: {
23
+ /**
24
+ * Used to set the `id` attribute on the rendered content, and used as the base to generate any additional element IDs as needed.
25
+ */
16
26
  id: {
17
27
  type: String,
18
28
  required: false,
19
29
  default: () => uniqueId()
30
+ },
31
+ /**
32
+ * The current value of the checkbox(es). Must be an array when there are multiple checkboxes bound to the same v-model.
33
+ */
34
+ checked: {
35
+ type: [Array, Boolean, String],
36
+ required: false,
37
+ default: null
38
+ },
39
+ /**
40
+ * When set to `true`, disables the component's functionality and places it in a disabled state.
41
+ */
42
+ disabled: {
43
+ type: Boolean,
44
+ required: false,
45
+ default: false
46
+ },
47
+ /**
48
+ * Sets the value of the `name` attribute on the form control.
49
+ */
50
+ name: {
51
+ type: String,
52
+ required: false,
53
+ default: undefined
54
+ },
55
+ /**
56
+ * Adds the `required` attribute to the form control.
57
+ */
58
+ required: {
59
+ type: Boolean,
60
+ required: false,
61
+ default: false
62
+ },
63
+ /**
64
+ * Controls the validation state appearance of the component. `true` for valid, `false` for invalid, or `null` for no validation state.
65
+ */
66
+ state: {
67
+ type: Boolean,
68
+ required: false,
69
+ default: null
70
+ },
71
+ /**
72
+ * Sets the value of `aria-label` attribute on the rendered element.
73
+ */
74
+ ariaLabel: {
75
+ type: String,
76
+ required: false,
77
+ default: undefined
78
+ },
79
+ /**
80
+ * The ID of the element that provides a label for this component. Used as the value for the `aria-labelledby` attribute.
81
+ */
82
+ ariaLabelledby: {
83
+ type: String,
84
+ required: false,
85
+ default: undefined
86
+ },
87
+ /**
88
+ * Renders the checkbox in an indeterminate state. Syncable via the .sync modifier.
89
+ */
90
+ indeterminate: {
91
+ type: Boolean,
92
+ required: false,
93
+ default: false
94
+ },
95
+ /**
96
+ * Value returned when this checkbox is checked.
97
+ */
98
+ value: {
99
+ type: [Array, Boolean, String],
100
+ required: false,
101
+ default: true
102
+ },
103
+ /**
104
+ * Value returned when this checkbox is unchecked. Note not applicable when multiple checkboxes bound to the same v-model array.
105
+ */
106
+ uncheckedValue: {
107
+ type: [Array, Boolean, String],
108
+ required: false,
109
+ default: false
110
+ }
111
+ },
112
+ data() {
113
+ const group = this.getGroup();
114
+ return {
115
+ localChecked: group ? group.checked : this.checked
116
+ };
117
+ },
118
+ computed: {
119
+ computedLocalChecked: {
120
+ get() {
121
+ return this.isGroup ? this.group.localChecked : this.localChecked;
122
+ },
123
+ set(value) {
124
+ if (this.isGroup) {
125
+ this.group.localChecked = value;
126
+ } else {
127
+ this.localChecked = value;
128
+ }
129
+ }
130
+ },
131
+ group() {
132
+ return this.getGroup();
133
+ },
134
+ isGroup() {
135
+ // Is this check a child of check-group?
136
+ return Boolean(this.group);
137
+ },
138
+ computedState() {
139
+ if (this.isGroup) return this.group.computedState;
140
+ return isBoolean(this.state) ? this.state : null;
141
+ },
142
+ stateClass() {
143
+ if (this.computedState === true) return 'is-valid';
144
+ if (this.computedState === false) return 'is-invalid';
145
+ return null;
146
+ },
147
+ computedAriaInvalid() {
148
+ return this.computedState === false ? 'true' : null;
149
+ },
150
+ isChecked() {
151
+ const {
152
+ value,
153
+ computedLocalChecked: checked
154
+ } = this;
155
+ return Array.isArray(checked) ? looseIndexOf(checked, value) > -1 : looseEqual(checked, value);
156
+ },
157
+ isDisabled() {
158
+ // Child can be disabled while parent isn't, but is always disabled if group is
159
+ return this.isGroup ? this.group.disabled || this.disabled : this.disabled;
160
+ },
161
+ isRequired() {
162
+ // Required only works when a name is provided for the input(s)
163
+ // Child can only be required when parent is
164
+ // Groups will always have a name (either user supplied or auto generated)
165
+ return this.computedName && (this.isGroup ? this.group.required : this.required);
166
+ },
167
+ computedName() {
168
+ // Group name preferred over local name
169
+ return (this.isGroup ? this.group.groupName : this.name) || null;
170
+ },
171
+ computedAttrs() {
172
+ const {
173
+ isDisabled: disabled,
174
+ isRequired: required,
175
+ value,
176
+ isChecked: checked
177
+ } = this;
178
+ return {
179
+ ...this.$attrs,
180
+ id: this.id,
181
+ name: this.computedName,
182
+ disabled,
183
+ required,
184
+ value,
185
+ checked,
186
+ 'aria-required': required || null,
187
+ 'aria-invalid': this.computedAriaInvalid,
188
+ 'aria-label': this.ariaLabel || null,
189
+ 'aria-labelledby': this.ariaLabelledby || null
190
+ };
20
191
  }
21
192
  },
193
+ watch: {
194
+ checked() {
195
+ this.checkedWatcher(...arguments);
196
+ },
197
+ indeterminate(newValue, oldValue) {
198
+ if (!looseEqual(newValue, oldValue)) {
199
+ this.setIndeterminate(newValue);
200
+ }
201
+ },
202
+ computedLocalChecked() {
203
+ this.computedLocalCheckedWatcher(...arguments);
204
+ }
205
+ },
206
+ mounted() {
207
+ // Set initial indeterminate state
208
+ this.setIndeterminate(this.indeterminate);
209
+ },
22
210
  methods: {
23
- change($event) {
24
- /**
25
- * Emitted when selected value(s) is changed due to user interaction.
26
- *
27
- * @event change
28
- */
29
- this.$emit('change', $event);
30
- },
31
- input($event) {
32
- /**
33
- * Emitted when checked state is changed.
34
- *
35
- * @event input
36
- */
37
- this.$emit('input', $event);
211
+ checkedWatcher(newValue) {
212
+ if (!looseEqual(newValue, this.computedLocalChecked)) {
213
+ this.computedLocalChecked = newValue;
214
+ }
215
+ },
216
+ computedLocalCheckedWatcher(newValue, oldValue) {
217
+ if (!looseEqual(newValue, oldValue)) {
218
+ this.$emit('input', newValue);
219
+ const {
220
+ input
221
+ } = this.$refs;
222
+ if (input) {
223
+ this.$emit('update:indeterminate', input.indeterminate);
224
+ }
225
+ }
226
+ },
227
+ handleChange(_ref) {
228
+ let {
229
+ target: {
230
+ checked,
231
+ indeterminate
232
+ }
233
+ } = _ref;
234
+ const {
235
+ value,
236
+ uncheckedValue
237
+ } = this;
238
+
239
+ // Update `computedLocalChecked`
240
+ let localChecked = this.computedLocalChecked;
241
+ if (Array.isArray(localChecked)) {
242
+ const index = looseIndexOf(localChecked, value);
243
+ if (checked && index < 0) {
244
+ // Add value to array
245
+ localChecked = localChecked.concat(value);
246
+ } else if (!checked && index > -1) {
247
+ // Remove value from array
248
+ localChecked = localChecked.slice(0, index).concat(localChecked.slice(index + 1));
249
+ }
250
+ } else {
251
+ localChecked = checked ? value : uncheckedValue;
252
+ }
253
+ this.computedLocalChecked = localChecked;
254
+
255
+ // Fire events in a `$nextTick()` to ensure the `v-model` is updated
256
+ this.$nextTick(() => {
257
+ // Change is only emitted on user interaction
258
+ this.$emit('change', localChecked);
259
+
260
+ // If this is a child of a group, we emit a change event on it as well
261
+ if (this.isGroup) {
262
+ this.group.$emit('change', localChecked);
263
+ }
264
+ this.$emit('indeterminate', indeterminate);
265
+ });
266
+ },
267
+ setIndeterminate(state) {
268
+ // Indeterminate only supported in single checkbox mode
269
+ const computedState = Array.isArray(this.computedLocalChecked) ? false : state;
270
+ const {
271
+ input
272
+ } = this.$refs;
273
+ if (input) {
274
+ input.indeterminate = computedState;
275
+ // Emit update event to prop
276
+ this.$emit('update:indeterminate', computedState);
277
+ }
278
+ },
279
+ focus() {
280
+ if (!this.disabled) {
281
+ var _this$$refs$input;
282
+ (_this$$refs$input = this.$refs.input) === null || _this$$refs$input === void 0 ? void 0 : _this$$refs$input.focus();
283
+ }
284
+ },
285
+ blur() {
286
+ if (!this.disabled) {
287
+ var _this$$refs$input2;
288
+ (_this$$refs$input2 = this.$refs.input) === null || _this$$refs$input2 === void 0 ? void 0 : _this$$refs$input2.blur();
289
+ }
38
290
  }
39
291
  }
40
292
  };
@@ -43,7 +295,7 @@ var script = {
43
295
  const __vue_script__ = script;
44
296
 
45
297
  /* template */
46
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('b-form-checkbox',_vm._b({staticClass:"gl-form-checkbox",attrs:{"id":_vm.id},on:{"change":_vm.change,"input":_vm.input}},'b-form-checkbox',_vm.$attrs,false),[_vm._t("default"),_vm._v(" "),(Boolean(_vm.$scopedSlots.help))?_c('p',{staticClass:"help-text"},[_vm._t("help")],2):_vm._e()],2)};
298
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{staticClass:"gl-form-checkbox custom-checkbox custom-control"},[_c('input',_vm._b({key:"input",ref:"input",staticClass:"custom-control-input",class:_vm.stateClass,attrs:{"type":"checkbox"},on:{"change":_vm.handleChange}},'input',_vm.computedAttrs,false)),_vm._v(" "),_c('label',{staticClass:"custom-control-label",attrs:{"for":_vm.id}},[_vm._t("default"),_vm._v(" "),(Boolean(_vm.$scopedSlots.help))?_c('p',{staticClass:"help-text"},[_vm._t("help")],2):_vm._e()],2)])};
47
299
  var __vue_staticRenderFns__ = [];
48
300
 
49
301
  /* style */
@@ -45,7 +45,7 @@ var script = {
45
45
  const __vue_script__ = script;
46
46
 
47
47
  /* template */
48
- var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('svg',_vm._g({key:_vm.spriteHref,attrs:{"data-testid":(_vm.name + "-illustration"),"aria-label":"","role":"presentation","width":_vm.illustrationSize,"height":_vm.illustrationSize}},_vm.$listeners),[_c('use',{attrs:{"href":_vm.spriteHref}})])};
48
+ var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('svg',_vm._g({key:_vm.spriteHref,attrs:{"data-testid":(_vm.name + "-illustration"),"role":"presentation","width":_vm.illustrationSize,"height":_vm.illustrationSize}},_vm.$listeners),[_c('use',{attrs:{"href":_vm.spriteHref}})])};
49
49
  var __vue_staticRenderFns__ = [];
50
50
 
51
51
  /* style */
@@ -1,10 +1,12 @@
1
1
  import GlButton from '../../base/button/button';
2
+ import GlIllustration from '../../base/illustration/illustration';
2
3
  import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
3
4
 
4
5
  var script = {
5
6
  name: 'GlEmptyState',
6
7
  components: {
7
- GlButton
8
+ GlButton,
9
+ GlIllustration
8
10
  },
9
11
  props: {
10
12
  /**
@@ -28,6 +30,14 @@ var script = {
28
30
  return value > 0 && value <= 6;
29
31
  }
30
32
  },
33
+ /**
34
+ * The illustration's name.
35
+ */
36
+ illustrationName: {
37
+ type: String,
38
+ required: false,
39
+ default: null
40
+ },
31
41
  /**
32
42
  * The illustration's URL.
33
43
  */
@@ -115,7 +125,7 @@ var script = {
115
125
  return this.shouldPreventImageReflow ? this.svgHeight : null;
116
126
  },
117
127
  shouldPreventImageReflow() {
118
- return Boolean(this.svgHeight);
128
+ return Boolean(this.svgHeight) && !this.illustrationName;
119
129
  },
120
130
  shouldRenderPrimaryButton() {
121
131
  return Boolean(this.primaryButtonLink && this.primaryButtonText);
@@ -136,7 +146,7 @@ const __vue_script__ = script;
136
146
  var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('section',{staticClass:"gl-flex",class:{
137
147
  'gl-empty-state gl-flex-col gl-text-center': !_vm.compact,
138
148
  'gl-flex-row': _vm.compact,
139
- }},[_c('div',{class:{ 'gl-hidden gl-px-4 @sm:gl-block': _vm.compact, 'gl-max-w-full': !_vm.compact }},[(_vm.svgPath)?_c('img',{staticClass:"gl-max-w-full",class:{ 'gl-dark-invert-keep-hue': _vm.invertInDarkMode },attrs:{"src":_vm.svgPath,"alt":"","height":_vm.height}}):_vm._e()]),_vm._v(" "),_c('div',{staticClass:"gl-empty-state-content gl-mx-auto gl-my-0",class:_vm.contentClasses,attrs:{"data-testid":"gl-empty-state-content"}},[_vm._t("title",function(){return [_c(_vm.headerComponent,{tag:"component",staticClass:"gl-mb-0 gl-mt-0 gl-text-size-h-display gl-leading-36",class:_vm.compact ? 'h5' : 'h4'},[_vm._v("\n "+_vm._s(_vm.title)+"\n ")])]}),_vm._v(" "),(_vm.description || _vm.$scopedSlots.description)?_c('p',{ref:"description",staticClass:"gl-mb-0 gl-mt-4 gl-text-subtle"},[_vm._t("description",function(){return [_vm._v("\n "+_vm._s(_vm.description)+"\n ")]})],2):_vm._e(),_vm._v(" "),_c('div',{staticClass:"gl-mt-5 gl-flex gl-flex-wrap",class:{ 'gl-justify-center': !_vm.compact }},[_vm._t("actions",function(){return [(_vm.shouldRenderPrimaryButton)?_c('gl-button',{staticClass:"gl-mb-3",class:_vm.compact ? 'gl-mr-3' : 'gl-mx-2',attrs:{"variant":"confirm","href":_vm.primaryButtonLink}},[_vm._v(_vm._s(_vm.primaryButtonText))]):_vm._e(),_vm._v(" "),(_vm.shouldRenderSecondaryButton)?_c('gl-button',{staticClass:"gl-mb-3 gl-mr-3",class:{ '!gl-mx-2': !_vm.compact },attrs:{"href":_vm.secondaryButtonLink}},[_vm._v(_vm._s(_vm.secondaryButtonText)+"\n ")]):_vm._e()]})],2)],2)])};
149
+ }},[_c('div',{class:{ 'gl-hidden gl-px-4 @sm:gl-block': _vm.compact, 'gl-max-w-full': !_vm.compact }},[(_vm.illustrationName)?_c('gl-illustration',{attrs:{"name":_vm.illustrationName}}):(_vm.svgPath)?_c('img',{staticClass:"gl-max-w-full",class:{ 'gl-dark-invert-keep-hue': _vm.invertInDarkMode },attrs:{"src":_vm.svgPath,"alt":"","height":_vm.height}}):_vm._e()],1),_vm._v(" "),_c('div',{staticClass:"gl-empty-state-content gl-mx-auto gl-my-0",class:_vm.contentClasses,attrs:{"data-testid":"gl-empty-state-content"}},[_vm._t("title",function(){return [_c(_vm.headerComponent,{tag:"component",staticClass:"gl-mb-0 gl-mt-0 gl-text-size-h-display gl-leading-36",class:_vm.compact ? 'h5' : 'h4'},[_vm._v("\n "+_vm._s(_vm.title)+"\n ")])]}),_vm._v(" "),(_vm.description || _vm.$scopedSlots.description)?_c('p',{ref:"description",staticClass:"gl-mb-0 gl-mt-4 gl-text-subtle"},[_vm._t("description",function(){return [_vm._v("\n "+_vm._s(_vm.description)+"\n ")]})],2):_vm._e(),_vm._v(" "),_c('div',{staticClass:"gl-mt-5 gl-flex gl-flex-wrap",class:{ 'gl-justify-center': !_vm.compact }},[_vm._t("actions",function(){return [(_vm.shouldRenderPrimaryButton)?_c('gl-button',{staticClass:"gl-mb-3",class:_vm.compact ? 'gl-mr-3' : 'gl-mx-2',attrs:{"variant":"confirm","href":_vm.primaryButtonLink}},[_vm._v(_vm._s(_vm.primaryButtonText))]):_vm._e(),_vm._v(" "),(_vm.shouldRenderSecondaryButton)?_c('gl-button',{staticClass:"gl-mb-3 gl-mr-3",class:{ '!gl-mx-2': !_vm.compact },attrs:{"href":_vm.secondaryButtonLink}},[_vm._v(_vm._s(_vm.secondaryButtonText)+"\n ")]):_vm._e()]})],2)],2)])};
140
150
  var __vue_staticRenderFns__ = [];
141
151
 
142
152
  /* style */
@@ -40,7 +40,10 @@ const BVPopoverTemplate = /*#__PURE__*/extend({
40
40
  ref: 'arrow'
41
41
  }), isUndefinedOrNull($title) || $title === '' ? /* istanbul ignore next */h() : h('h3', {
42
42
  staticClass: 'popover-header',
43
- domProps: titleDomProps
43
+ domProps: titleDomProps,
44
+ attrs: {
45
+ role: 'presentation'
46
+ }
44
47
  }, [$title]), isUndefinedOrNull($content) || $content === '' ? /* istanbul ignore next */h() : h('div', {
45
48
  staticClass: 'popover-body',
46
49
  domProps: contentDomProps
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/ui",
3
- "version": "120.0.0",
3
+ "version": "121.0.0",
4
4
  "description": "GitLab UI Components",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -122,9 +122,9 @@
122
122
  "@storybook/vue3": "^7.6.20",
123
123
  "@storybook/vue3-webpack5": "^7.6.20",
124
124
  "@types/jest-image-snapshot": "^6.4.0",
125
- "@vue/compat": "^3.5.19",
126
- "@vue/compiler-sfc": "^3.5.19",
127
- "@vue/server-renderer": "^3.5.19",
125
+ "@vue/compat": "^3.5.20",
126
+ "@vue/compiler-sfc": "^3.5.20",
127
+ "@vue/server-renderer": "^3.5.20",
128
128
  "@vue/test-utils": "1.3.6",
129
129
  "@vue/test-utils-vue3": "npm:@vue/test-utils@^2.4.6",
130
130
  "@vue/vue2-jest": "29.2.6",
@@ -165,7 +165,7 @@
165
165
  "rollup-plugin-string": "^3.0.0",
166
166
  "rollup-plugin-svg": "^2.0.0",
167
167
  "rollup-plugin-vue": "^5.1.9",
168
- "sass": "^1.90.0",
168
+ "sass": "^1.91.0",
169
169
  "sass-loader": "^10.5.2",
170
170
  "sass-true": "^9",
171
171
  "start-server-and-test": "^2.0.13",
@@ -1,11 +1,18 @@
1
1
  <script>
2
2
  import uniqueId from 'lodash/uniqueId';
3
- import { BFormCheckbox } from '../../../../vendor/bootstrap-vue/src/components/form-checkbox/form-checkbox';
3
+ import isBoolean from 'lodash/isBoolean';
4
+ import { looseEqual } from '../../../../vendor/bootstrap-vue/src/utils/loose-equal';
5
+ import { looseIndexOf } from '../../../../vendor/bootstrap-vue/src/utils/loose-index-of';
4
6
 
5
7
  export default {
6
8
  name: 'GlFormCheckbox',
7
- components: {
8
- BFormCheckbox,
9
+ inject: {
10
+ getGroup: {
11
+ // When we remove BFormCheckboxGroup from GlFormCheckboxGroup, we can rename
12
+ // the `getBvCheckGroup` provide to `getCheckGroup`.
13
+ from: 'getBvCheckGroup',
14
+ default: () => () => null,
15
+ },
9
16
  },
10
17
  inheritAttrs: false,
11
18
  model: {
@@ -13,46 +20,277 @@ export default {
13
20
  event: 'input',
14
21
  },
15
22
  props: {
23
+ /**
24
+ * Used to set the `id` attribute on the rendered content, and used as the base to generate any additional element IDs as needed.
25
+ */
16
26
  id: {
17
27
  type: String,
18
28
  required: false,
19
29
  default: () => uniqueId(),
20
30
  },
31
+ /**
32
+ * The current value of the checkbox(es). Must be an array when there are multiple checkboxes bound to the same v-model.
33
+ */
34
+ checked: {
35
+ type: [Array, Boolean, String],
36
+ required: false,
37
+ default: null,
38
+ },
39
+ /**
40
+ * When set to `true`, disables the component's functionality and places it in a disabled state.
41
+ */
42
+ disabled: {
43
+ type: Boolean,
44
+ required: false,
45
+ default: false,
46
+ },
47
+ /**
48
+ * Sets the value of the `name` attribute on the form control.
49
+ */
50
+ name: {
51
+ type: String,
52
+ required: false,
53
+ default: undefined,
54
+ },
55
+ /**
56
+ * Adds the `required` attribute to the form control.
57
+ */
58
+ required: {
59
+ type: Boolean,
60
+ required: false,
61
+ default: false,
62
+ },
63
+ /**
64
+ * Controls the validation state appearance of the component. `true` for valid, `false` for invalid, or `null` for no validation state.
65
+ */
66
+ state: {
67
+ type: Boolean,
68
+ required: false,
69
+ default: null,
70
+ },
71
+ /**
72
+ * Sets the value of `aria-label` attribute on the rendered element.
73
+ */
74
+ ariaLabel: {
75
+ type: String,
76
+ required: false,
77
+ default: undefined,
78
+ },
79
+ /**
80
+ * The ID of the element that provides a label for this component. Used as the value for the `aria-labelledby` attribute.
81
+ */
82
+ ariaLabelledby: {
83
+ type: String,
84
+ required: false,
85
+ default: undefined,
86
+ },
87
+ /**
88
+ * Renders the checkbox in an indeterminate state. Syncable via the .sync modifier.
89
+ */
90
+ indeterminate: {
91
+ type: Boolean,
92
+ required: false,
93
+ default: false,
94
+ },
95
+ /**
96
+ * Value returned when this checkbox is checked.
97
+ */
98
+ value: {
99
+ type: [Array, Boolean, String],
100
+ required: false,
101
+ default: true,
102
+ },
103
+ /**
104
+ * Value returned when this checkbox is unchecked. Note not applicable when multiple checkboxes bound to the same v-model array.
105
+ */
106
+ uncheckedValue: {
107
+ type: [Array, Boolean, String],
108
+ required: false,
109
+ default: false,
110
+ },
111
+ },
112
+ data() {
113
+ const group = this.getGroup();
114
+ return {
115
+ localChecked: group ? group.checked : this.checked,
116
+ };
117
+ },
118
+ computed: {
119
+ computedLocalChecked: {
120
+ get() {
121
+ return this.isGroup ? this.group.localChecked : this.localChecked;
122
+ },
123
+ set(value) {
124
+ if (this.isGroup) {
125
+ this.group.localChecked = value;
126
+ } else {
127
+ this.localChecked = value;
128
+ }
129
+ },
130
+ },
131
+ group() {
132
+ return this.getGroup();
133
+ },
134
+ isGroup() {
135
+ // Is this check a child of check-group?
136
+ return Boolean(this.group);
137
+ },
138
+ computedState() {
139
+ if (this.isGroup) return this.group.computedState;
140
+ return isBoolean(this.state) ? this.state : null;
141
+ },
142
+ stateClass() {
143
+ if (this.computedState === true) return 'is-valid';
144
+ if (this.computedState === false) return 'is-invalid';
145
+ return null;
146
+ },
147
+ computedAriaInvalid() {
148
+ return this.computedState === false ? 'true' : null;
149
+ },
150
+ isChecked() {
151
+ const { value, computedLocalChecked: checked } = this;
152
+ return Array.isArray(checked)
153
+ ? looseIndexOf(checked, value) > -1
154
+ : looseEqual(checked, value);
155
+ },
156
+ isDisabled() {
157
+ // Child can be disabled while parent isn't, but is always disabled if group is
158
+ return this.isGroup ? this.group.disabled || this.disabled : this.disabled;
159
+ },
160
+ isRequired() {
161
+ // Required only works when a name is provided for the input(s)
162
+ // Child can only be required when parent is
163
+ // Groups will always have a name (either user supplied or auto generated)
164
+ return this.computedName && (this.isGroup ? this.group.required : this.required);
165
+ },
166
+ computedName() {
167
+ // Group name preferred over local name
168
+ return (this.isGroup ? this.group.groupName : this.name) || null;
169
+ },
170
+ computedAttrs() {
171
+ const { isDisabled: disabled, isRequired: required, value, isChecked: checked } = this;
172
+
173
+ return {
174
+ ...this.$attrs,
175
+ id: this.id,
176
+ name: this.computedName,
177
+ disabled,
178
+ required,
179
+ value,
180
+ checked,
181
+ 'aria-required': required || null,
182
+ 'aria-invalid': this.computedAriaInvalid,
183
+ 'aria-label': this.ariaLabel || null,
184
+ 'aria-labelledby': this.ariaLabelledby || null,
185
+ };
186
+ },
187
+ },
188
+ watch: {
189
+ checked(...args) {
190
+ this.checkedWatcher(...args);
191
+ },
192
+ indeterminate(newValue, oldValue) {
193
+ if (!looseEqual(newValue, oldValue)) {
194
+ this.setIndeterminate(newValue);
195
+ }
196
+ },
197
+ computedLocalChecked(...args) {
198
+ this.computedLocalCheckedWatcher(...args);
199
+ },
200
+ },
201
+ mounted() {
202
+ // Set initial indeterminate state
203
+ this.setIndeterminate(this.indeterminate);
21
204
  },
22
205
  methods: {
23
- change($event) {
24
- /**
25
- * Emitted when selected value(s) is changed due to user interaction.
26
- *
27
- * @event change
28
- */
29
- this.$emit('change', $event);
30
- },
31
- input($event) {
32
- /**
33
- * Emitted when checked state is changed.
34
- *
35
- * @event input
36
- */
37
- this.$emit('input', $event);
206
+ checkedWatcher(newValue) {
207
+ if (!looseEqual(newValue, this.computedLocalChecked)) {
208
+ this.computedLocalChecked = newValue;
209
+ }
210
+ },
211
+ computedLocalCheckedWatcher(newValue, oldValue) {
212
+ if (!looseEqual(newValue, oldValue)) {
213
+ this.$emit('input', newValue);
214
+
215
+ const { input } = this.$refs;
216
+ if (input) {
217
+ this.$emit('update:indeterminate', input.indeterminate);
218
+ }
219
+ }
220
+ },
221
+ handleChange({ target: { checked, indeterminate } }) {
222
+ const { value, uncheckedValue } = this;
223
+
224
+ // Update `computedLocalChecked`
225
+ let localChecked = this.computedLocalChecked;
226
+ if (Array.isArray(localChecked)) {
227
+ const index = looseIndexOf(localChecked, value);
228
+ if (checked && index < 0) {
229
+ // Add value to array
230
+ localChecked = localChecked.concat(value);
231
+ } else if (!checked && index > -1) {
232
+ // Remove value from array
233
+ localChecked = localChecked.slice(0, index).concat(localChecked.slice(index + 1));
234
+ }
235
+ } else {
236
+ localChecked = checked ? value : uncheckedValue;
237
+ }
238
+ this.computedLocalChecked = localChecked;
239
+
240
+ // Fire events in a `$nextTick()` to ensure the `v-model` is updated
241
+ this.$nextTick(() => {
242
+ // Change is only emitted on user interaction
243
+ this.$emit('change', localChecked);
244
+
245
+ // If this is a child of a group, we emit a change event on it as well
246
+ if (this.isGroup) {
247
+ this.group.$emit('change', localChecked);
248
+ }
249
+
250
+ this.$emit('indeterminate', indeterminate);
251
+ });
252
+ },
253
+ setIndeterminate(state) {
254
+ // Indeterminate only supported in single checkbox mode
255
+ const computedState = Array.isArray(this.computedLocalChecked) ? false : state;
256
+
257
+ const { input } = this.$refs;
258
+ if (input) {
259
+ input.indeterminate = computedState;
260
+ // Emit update event to prop
261
+ this.$emit('update:indeterminate', computedState);
262
+ }
263
+ },
264
+ focus() {
265
+ if (!this.disabled) {
266
+ this.$refs.input?.focus();
267
+ }
268
+ },
269
+ blur() {
270
+ if (!this.disabled) {
271
+ this.$refs.input?.blur();
272
+ }
38
273
  },
39
274
  },
40
275
  };
41
276
  </script>
42
277
 
43
278
  <template>
44
- <b-form-checkbox
45
- v-bind="$attrs"
46
- :id="id"
47
- class="gl-form-checkbox"
48
- @change="change"
49
- @input="input"
50
- >
51
- <!-- @slot The checkbox content to display. -->
52
- <slot></slot>
53
- <p v-if="Boolean($scopedSlots.help)" class="help-text">
54
- <!-- @slot The help text to display. -->
55
- <slot name="help"></slot>
56
- </p>
57
- </b-form-checkbox>
279
+ <div class="gl-form-checkbox custom-checkbox custom-control">
280
+ <input
281
+ ref="input"
282
+ key="input"
283
+ v-bind="computedAttrs"
284
+ type="checkbox"
285
+ class="custom-control-input"
286
+ :class="stateClass"
287
+ @change="handleChange"
288
+ />
289
+ <label :for="id" class="custom-control-label">
290
+ <slot></slot>
291
+ <p v-if="Boolean($scopedSlots.help)" class="help-text">
292
+ <slot name="help"></slot>
293
+ </p>
294
+ </label>
295
+ </div>
58
296
  </template>
@@ -45,7 +45,6 @@ export default {
45
45
  <svg
46
46
  :key="spriteHref"
47
47
  :data-testid="`${name}-illustration`"
48
- aria-label=""
49
48
  role="presentation"
50
49
  :width="illustrationSize"
51
50
  :height="illustrationSize"
@@ -1,10 +1,12 @@
1
1
  <script>
2
2
  import GlButton from '../../base/button/button.vue';
3
+ import GlIllustration from '../../base/illustration/illustration.vue';
3
4
 
4
5
  export default {
5
6
  name: 'GlEmptyState',
6
7
  components: {
7
8
  GlButton,
9
+ GlIllustration,
8
10
  },
9
11
  props: {
10
12
  /**
@@ -28,6 +30,14 @@ export default {
28
30
  return value > 0 && value <= 6;
29
31
  },
30
32
  },
33
+ /**
34
+ * The illustration's name.
35
+ */
36
+ illustrationName: {
37
+ type: String,
38
+ required: false,
39
+ default: null,
40
+ },
31
41
  /**
32
42
  * The illustration's URL.
33
43
  */
@@ -115,7 +125,7 @@ export default {
115
125
  return this.shouldPreventImageReflow ? this.svgHeight : null;
116
126
  },
117
127
  shouldPreventImageReflow() {
118
- return Boolean(this.svgHeight);
128
+ return Boolean(this.svgHeight) && !this.illustrationName;
119
129
  },
120
130
  shouldRenderPrimaryButton() {
121
131
  return Boolean(this.primaryButtonLink && this.primaryButtonText);
@@ -139,8 +149,9 @@ export default {
139
149
  }"
140
150
  >
141
151
  <div :class="{ 'gl-hidden gl-px-4 @sm:gl-block': compact, 'gl-max-w-full': !compact }">
152
+ <gl-illustration v-if="illustrationName" :name="illustrationName" />
142
153
  <img
143
- v-if="svgPath"
154
+ v-else-if="svgPath"
144
155
  :src="svgPath"
145
156
  alt=""
146
157
  :class="{ 'gl-dark-invert-keep-hue': invertInDarkMode }"
@@ -39,7 +39,15 @@ export const BVPopoverTemplate = /*#__PURE__*/ extend({
39
39
  }),
40
40
  isUndefinedOrNull($title) || $title === ''
41
41
  ? /* istanbul ignore next */ h()
42
- : h('h3', { staticClass: 'popover-header', domProps: titleDomProps }, [$title]),
42
+ : h(
43
+ 'h3',
44
+ {
45
+ staticClass: 'popover-header',
46
+ domProps: titleDomProps,
47
+ attrs: { role: 'presentation' }
48
+ },
49
+ [$title]
50
+ ),
43
51
  isUndefinedOrNull($content) || $content === ''
44
52
  ? /* istanbul ignore next */ h()
45
53
  : h('div', { staticClass: 'popover-body', domProps: contentDomProps }, [$content])