@gitlab/ui 127.1.1 → 128.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.
- package/dist/components/base/form/form_group/form_group.js +3 -0
- package/dist/components/base/new_dropdowns/base_dropdown/base_dropdown.js +95 -20
- package/dist/components/base/new_dropdowns/disclosure/disclosure_dropdown.js +1 -1
- package/dist/components/base/new_dropdowns/listbox/listbox.js +47 -10
- package/package.json +1 -1
- package/src/components/base/form/form_checkbox/form_checkbox.vue +0 -3
- package/src/components/base/form/form_checkbox/form_checkbox_group.vue +148 -21
- package/src/components/base/form/form_group/form_group.vue +3 -0
- package/src/components/base/new_dropdowns/base_dropdown/base_dropdown.vue +101 -22
- package/src/components/base/new_dropdowns/disclosure/disclosure_dropdown.vue +3 -2
- package/src/components/base/new_dropdowns/listbox/listbox.vue +52 -9
- package/src/scss/bootstrap_vue.scss +0 -1
- package/src/vendor/bootstrap-vue/src/constants/components.js +0 -2
- package/src/vendor/bootstrap-vue/src/mixins/form-radio-check-group.js +1 -2
- package/src/vendor/bootstrap-vue/src/components/form-checkbox/_form-checkbox-group.scss +0 -1
- package/src/vendor/bootstrap-vue/src/components/form-checkbox/_form-checkbox.scss +0 -125
- package/src/vendor/bootstrap-vue/src/components/form-checkbox/form-checkbox-group.js +0 -42
- package/src/vendor/bootstrap-vue/src/components/form-checkbox/form-checkbox.js +0 -132
- package/src/vendor/bootstrap-vue/src/components/form-checkbox/index.js +0 -4
- package/src/vendor/bootstrap-vue/src/components/form-checkbox/index.scss +0 -2
|
@@ -106,6 +106,26 @@ var script = {
|
|
|
106
106
|
return ['menu', 'listbox', 'tree', 'grid', 'dialog', true, false].includes(value);
|
|
107
107
|
}
|
|
108
108
|
},
|
|
109
|
+
activeItemId: {
|
|
110
|
+
type: String,
|
|
111
|
+
required: false,
|
|
112
|
+
default: undefined
|
|
113
|
+
},
|
|
114
|
+
hasExternalLabel: {
|
|
115
|
+
type: Boolean,
|
|
116
|
+
required: false,
|
|
117
|
+
default: false
|
|
118
|
+
},
|
|
119
|
+
hasSearchableListbox: {
|
|
120
|
+
type: Boolean,
|
|
121
|
+
required: false,
|
|
122
|
+
default: false
|
|
123
|
+
},
|
|
124
|
+
isDisclosure: {
|
|
125
|
+
type: Boolean,
|
|
126
|
+
required: false,
|
|
127
|
+
default: false
|
|
128
|
+
},
|
|
109
129
|
/**
|
|
110
130
|
* Id that will be referenced by `aria-labelledby` attribute of the dropdown content`
|
|
111
131
|
*/
|
|
@@ -113,6 +133,15 @@ var script = {
|
|
|
113
133
|
type: String,
|
|
114
134
|
required: true
|
|
115
135
|
},
|
|
136
|
+
/**
|
|
137
|
+
* Span Id that will be referenced by listbox `aria-labelledby` if there's an external label.
|
|
138
|
+
* This prop will only be defined by `GlCollapsibleListbox` when `isInFormGroup` injected is true.
|
|
139
|
+
*/
|
|
140
|
+
listboxId: {
|
|
141
|
+
type: String,
|
|
142
|
+
required: false,
|
|
143
|
+
default: undefined
|
|
144
|
+
},
|
|
116
145
|
/**
|
|
117
146
|
* The `aria-labelledby` attribute value for the toggle `button`
|
|
118
147
|
*/
|
|
@@ -157,6 +186,10 @@ var script = {
|
|
|
157
186
|
};
|
|
158
187
|
},
|
|
159
188
|
computed: {
|
|
189
|
+
ariaActiveDescendant() {
|
|
190
|
+
if (!this.isDisclosure && this.visible) return this.activeItemId;
|
|
191
|
+
return undefined;
|
|
192
|
+
},
|
|
160
193
|
hasNoVisibleToggleText() {
|
|
161
194
|
var _this$toggleText;
|
|
162
195
|
return !((_this$toggleText = this.toggleText) !== null && _this$toggleText !== void 0 && _this$toggleText.length) || this.textSrOnly;
|
|
@@ -170,12 +203,47 @@ var script = {
|
|
|
170
203
|
isCaretOnly() {
|
|
171
204
|
return !this.noCaret && !this.icon && this.hasNoVisibleToggleText;
|
|
172
205
|
},
|
|
173
|
-
|
|
206
|
+
isDefaultToggle() {
|
|
207
|
+
return !this.$scopedSlots.toggle;
|
|
208
|
+
},
|
|
209
|
+
isToggleCombobox() {
|
|
210
|
+
if (this.hasSearchableListbox || this.isDisclosure) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
},
|
|
215
|
+
isToggleLabelledExternally() {
|
|
216
|
+
if (this.hasExternalLabel && this.toggleId) return true;
|
|
217
|
+
return false;
|
|
218
|
+
},
|
|
219
|
+
computedToggleId() {
|
|
220
|
+
if (this.isDisclosure) {
|
|
221
|
+
return this.isDefaultToggle ? this.toggleId : undefined;
|
|
222
|
+
}
|
|
223
|
+
if (this.isToggleLabelledExternally) {
|
|
224
|
+
return this.isDefaultToggle ? this.toggleId : undefined;
|
|
225
|
+
}
|
|
226
|
+
if (this.hasSearchableListbox) {
|
|
227
|
+
return this.toggleId;
|
|
228
|
+
}
|
|
229
|
+
return undefined;
|
|
230
|
+
},
|
|
231
|
+
computedToggleInnerId() {
|
|
232
|
+
if (this.isToggleCombobox && !this.isToggleLabelledExternally) {
|
|
233
|
+
return this.toggleId;
|
|
234
|
+
}
|
|
235
|
+
if (this.isToggleCombobox && this.isToggleLabelledExternally) {
|
|
236
|
+
return this.listboxId;
|
|
237
|
+
}
|
|
238
|
+
return undefined;
|
|
239
|
+
},
|
|
240
|
+
toggleAriaAttributes() {
|
|
174
241
|
return {
|
|
175
|
-
'aria-haspopup': this.ariaHaspopup,
|
|
176
|
-
'aria-expanded': String(this.visible),
|
|
177
242
|
'aria-controls': this.baseDropdownId,
|
|
178
|
-
'aria-
|
|
243
|
+
'aria-expanded': String(this.visible),
|
|
244
|
+
'aria-haspopup': this.ariaHaspopup,
|
|
245
|
+
'aria-labelledby': this.toggleLabelledBy,
|
|
246
|
+
'aria-activedescendant': this.ariaActiveDescendant
|
|
179
247
|
};
|
|
180
248
|
},
|
|
181
249
|
toggleButtonClasses() {
|
|
@@ -191,10 +259,28 @@ var script = {
|
|
|
191
259
|
return this.block ? 'gl-w-full' : '';
|
|
192
260
|
},
|
|
193
261
|
toggleLabelledBy() {
|
|
262
|
+
if (this.isToggleCombobox) {
|
|
263
|
+
if (this.ariaLabelledby) {
|
|
264
|
+
return `${this.ariaLabelledby} ${this.toggleId}`;
|
|
265
|
+
}
|
|
266
|
+
return this.toggleId;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// For non-combobox toggles, combine IDs or use the button's own text
|
|
194
270
|
return this.ariaLabelledby ? `${this.ariaLabelledby} ${this.toggleId}` : undefined;
|
|
195
271
|
},
|
|
196
|
-
|
|
197
|
-
|
|
272
|
+
toggleRole() {
|
|
273
|
+
if (this.isToggleCombobox) {
|
|
274
|
+
return 'combobox';
|
|
275
|
+
}
|
|
276
|
+
return undefined;
|
|
277
|
+
},
|
|
278
|
+
toggleAccessibilityAttributes() {
|
|
279
|
+
return {
|
|
280
|
+
...this.toggleAriaAttributes,
|
|
281
|
+
id: this.toggleId,
|
|
282
|
+
role: this.toggleRole
|
|
283
|
+
};
|
|
198
284
|
},
|
|
199
285
|
toggleOptions() {
|
|
200
286
|
if (this.isDefaultToggle) {
|
|
@@ -209,7 +295,8 @@ var script = {
|
|
|
209
295
|
disabled: this.disabled,
|
|
210
296
|
loading: this.loading,
|
|
211
297
|
class: this.toggleButtonClasses,
|
|
212
|
-
|
|
298
|
+
role: this.toggleRole,
|
|
299
|
+
...this.toggleAriaAttributes,
|
|
213
300
|
listeners: {
|
|
214
301
|
keydown: event => this.onKeydown(event),
|
|
215
302
|
click: event => this.toggle(event)
|
|
@@ -287,18 +374,6 @@ var script = {
|
|
|
287
374
|
};
|
|
288
375
|
}
|
|
289
376
|
},
|
|
290
|
-
watch: {
|
|
291
|
-
ariaAttributes: {
|
|
292
|
-
deep: true,
|
|
293
|
-
handler(ariaAttributes) {
|
|
294
|
-
if (this.$scopedSlots.toggle) {
|
|
295
|
-
Object.keys(ariaAttributes).forEach(key => {
|
|
296
|
-
this.toggleElement.setAttribute(key, ariaAttributes[key]);
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
},
|
|
302
377
|
mounted() {
|
|
303
378
|
this.checkToggleFocusable();
|
|
304
379
|
},
|
|
@@ -538,7 +613,7 @@ var script = {
|
|
|
538
613
|
const __vue_script__ = script;
|
|
539
614
|
|
|
540
615
|
/* template */
|
|
541
|
-
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{class:[_vm.$options.BASE_DROPDOWN_CLASS, { '!gl-block': _vm.block }]},[_c(_vm.toggleComponent,_vm._g(_vm._b({ref:"toggle",tag:"component",attrs:{"id":_vm.
|
|
616
|
+
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{class:[_vm.$options.BASE_DROPDOWN_CLASS, { '!gl-block': _vm.block }]},[_c(_vm.toggleComponent,_vm._g(_vm._b({ref:"toggle",tag:"component",attrs:{"id":_vm.computedToggleId,"data-testid":"base-dropdown-toggle"}},'component',_vm.toggleAttributes,false),_vm.toggleListeners),[_vm._t("toggle",function(){return [_c('span',{staticClass:"gl-new-dropdown-button-text",class:{ 'gl-sr-only': _vm.textSrOnly },attrs:{"id":_vm.computedToggleInnerId,"data-testid":"base-dropdown-span"}},[_vm._v("\n "+_vm._s(_vm.toggleText)+"\n ")]),_vm._v(" "),(!_vm.noCaret)?_c('gl-icon',{staticClass:"gl-button-icon gl-new-dropdown-chevron",attrs:{"name":"chevron-down"}}):_vm._e()]},{"accessibilityAttributes":_vm.toggleAccessibilityAttributes})],2),_vm._v(" "),_c('dropdown-container',{attrs:{"positioning-strategy":_vm.positioningStrategy}},[_c('div',{directives:[{name:"outside",rawName:"v-outside.click.focusin",value:(_vm.handleClickOutside),expression:"handleClickOutside",modifiers:{"click":true,"focusin":true}}],ref:"dropdownContainer",class:_vm.$options.DROPDOWN_CONTAINER_CLASS},[_c('div',{ref:"content",staticClass:"gl-new-dropdown-panel",class:_vm.panelClasses,attrs:{"id":_vm.baseDropdownId,"data-testid":"base-dropdown-menu"},on:{"keydown":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"esc",27,$event.key,["Esc","Escape"])){ return null; }$event.stopPropagation();$event.preventDefault();return _vm.closeAndFocus.apply(null, arguments)}}},[_c('div',{ref:"dropdownArrow",staticClass:"gl-new-dropdown-arrow"}),_vm._v(" "),_c('div',{staticClass:"gl-new-dropdown-inner"},[_vm._t("default",null,{"visible":_vm.visible})],2)])])])],1)};
|
|
542
617
|
var __vue_staticRenderFns__ = [];
|
|
543
618
|
|
|
544
619
|
/* style */
|
|
@@ -346,7 +346,7 @@ var script = {
|
|
|
346
346
|
const __vue_script__ = script;
|
|
347
347
|
|
|
348
348
|
/* template */
|
|
349
|
-
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-base-dropdown',{ref:"baseDropdown",staticClass:"gl-disclosure-dropdown",attrs:{"aria-labelledby":_vm.toggleAriaLabelledBy,"arrow-element":_vm.$refs.disclosureArrow,"toggle-id":_vm.toggleId,"toggle-text":_vm.toggleText,"toggle-class":_vm.toggleClass,"text-sr-only":_vm.textSrOnly,"category":_vm.category,"variant":_vm.variant,"size":_vm.size,"icon":_vm.icon,"disabled":_vm.disabled,"loading":_vm.loading,"no-caret":_vm.noCaret,"placement":_vm.placement,"block":_vm.block,"offset":_vm.dropdownOffset,"fluid-width":_vm.fluidWidth,"positioning-strategy":_vm.positioningStrategy},on:_vm._d({},[_vm.$options.events.GL_DROPDOWN_SHOWN,_vm.onShow,_vm.$options.events.GL_DROPDOWN_HIDDEN,_vm.onHide,_vm.$options.events.GL_DROPDOWN_BEFORE_CLOSE,_vm.onBeforeClose,_vm.$options.events.GL_DROPDOWN_FOCUS_CONTENT,_vm.onKeydown]),scopedSlots:_vm._u([(_vm.hasCustomToggle)?{key:"toggle",fn:function(){return [_vm._t("toggle")]}
|
|
349
|
+
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-base-dropdown',{ref:"baseDropdown",staticClass:"gl-disclosure-dropdown",attrs:{"aria-labelledby":_vm.toggleAriaLabelledBy,"arrow-element":_vm.$refs.disclosureArrow,"toggle-id":_vm.toggleId,"toggle-text":_vm.toggleText,"toggle-class":_vm.toggleClass,"text-sr-only":_vm.textSrOnly,"category":_vm.category,"variant":_vm.variant,"size":_vm.size,"icon":_vm.icon,"disabled":_vm.disabled,"loading":_vm.loading,"no-caret":_vm.noCaret,"placement":_vm.placement,"block":_vm.block,"offset":_vm.dropdownOffset,"fluid-width":_vm.fluidWidth,"positioning-strategy":_vm.positioningStrategy,"is-disclosure":""},on:_vm._d({},[_vm.$options.events.GL_DROPDOWN_SHOWN,_vm.onShow,_vm.$options.events.GL_DROPDOWN_HIDDEN,_vm.onHide,_vm.$options.events.GL_DROPDOWN_BEFORE_CLOSE,_vm.onBeforeClose,_vm.$options.events.GL_DROPDOWN_FOCUS_CONTENT,_vm.onKeydown]),scopedSlots:_vm._u([(_vm.hasCustomToggle)?{key:"toggle",fn:function(slotProps){return [_vm._t("toggle",null,null,slotProps)]}}:null],null,true)},[_vm._v(" "),_vm._t("header"),_vm._v(" "),_c(_vm.disclosureTag,{ref:"content",tag:"component",class:_vm.$options.GL_DROPDOWN_CONTENTS_CLASS,attrs:{"id":_vm.disclosureId,"aria-labelledby":_vm.listAriaLabelledBy || _vm.toggleId,"data-testid":"disclosure-content","tabindex":"-1"},on:{"keydown":_vm.onKeydown,"click":_vm.handleAutoClose}},[_vm._t("default",function(){return [_vm._l((_vm.items),function(item,index){return [(_vm.isItem(item))?[_c('gl-disclosure-dropdown-item',{key:_vm.uniqueItemId(),attrs:{"item":item},on:{"action":_vm.handleAction},scopedSlots:_vm._u([('list-item' in _vm.$scopedSlots)?{key:"list-item",fn:function(){return [_vm._t("list-item",null,{"item":item})]},proxy:true}:null],null,true)})]:[_c('gl-disclosure-dropdown-group',{key:item.name,attrs:{"bordered":index !== 0,"group":item},on:{"action":_vm.handleAction},scopedSlots:_vm._u([(_vm.$scopedSlots['group-label'])?{key:"group-label",fn:function(){return [_vm._t("group-label",null,{"group":item})]},proxy:true}:null,(_vm.$scopedSlots['list-item'])?{key:"default",fn:function(){return _vm._l((item.items),function(groupItem){return _c('gl-disclosure-dropdown-item',{key:_vm.uniqueItemId(),attrs:{"item":groupItem},on:{"action":_vm.handleAction},scopedSlots:_vm._u([{key:"list-item",fn:function(){return [_vm._t("list-item",null,{"item":groupItem})]},proxy:true}],null,true)})})},proxy:true}:null],null,true)})]]})]})],2),_vm._v(" "),_vm._t("footer")],2)};
|
|
350
350
|
var __vue_staticRenderFns__ = [];
|
|
351
351
|
|
|
352
352
|
/* style */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import clamp from 'lodash/clamp';
|
|
2
2
|
import uniqueId from 'lodash/uniqueId';
|
|
3
3
|
import { logWarning, stopEvent } from '../../../../utils/utils';
|
|
4
|
-
import { GL_DROPDOWN_SHOWN, GL_DROPDOWN_HIDDEN, POSITION_ABSOLUTE, POSITION_FIXED, GL_DROPDOWN_CONTENTS_CLASS, ARROW_UP, ENTER, ARROW_DOWN, END, HOME } from '../constants';
|
|
4
|
+
import { GL_DROPDOWN_SHOWN, GL_DROPDOWN_HIDDEN, GL_DROPDOWN_FOCUS_CONTENT, POSITION_ABSOLUTE, POSITION_FIXED, GL_DROPDOWN_CONTENTS_CLASS, ARROW_UP, ENTER, ARROW_DOWN, END, HOME } from '../constants';
|
|
5
5
|
import { buttonCategoryOptions, dropdownVariantOptions, buttonSizeOptions, dropdownPlacements } from '../../../../utils/constants';
|
|
6
6
|
import GlButton from '../../button/button';
|
|
7
7
|
import GlLoadingIcon from '../../loading_icon/loading_icon';
|
|
@@ -25,7 +25,8 @@ var script = {
|
|
|
25
25
|
HEADER_ITEMS_BORDER_CLASSES,
|
|
26
26
|
events: {
|
|
27
27
|
GL_DROPDOWN_SHOWN,
|
|
28
|
-
GL_DROPDOWN_HIDDEN
|
|
28
|
+
GL_DROPDOWN_HIDDEN,
|
|
29
|
+
GL_DROPDOWN_FOCUS_CONTENT
|
|
29
30
|
},
|
|
30
31
|
components: {
|
|
31
32
|
GlBaseDropdown,
|
|
@@ -37,6 +38,11 @@ var script = {
|
|
|
37
38
|
GlLoadingIcon,
|
|
38
39
|
GlIntersectionObserver
|
|
39
40
|
},
|
|
41
|
+
inject: {
|
|
42
|
+
isInFormGroup: {
|
|
43
|
+
default: false
|
|
44
|
+
}
|
|
45
|
+
},
|
|
40
46
|
model: {
|
|
41
47
|
prop: 'selected',
|
|
42
48
|
event: 'select'
|
|
@@ -184,7 +190,7 @@ var script = {
|
|
|
184
190
|
},
|
|
185
191
|
/**
|
|
186
192
|
* The `aria-labelledby` attribute value for the toggle button
|
|
187
|
-
* Provide the string of
|
|
193
|
+
* Provide the string of IDs seperated by space
|
|
188
194
|
*/
|
|
189
195
|
toggleAriaLabelledBy: {
|
|
190
196
|
type: String,
|
|
@@ -193,7 +199,7 @@ var script = {
|
|
|
193
199
|
},
|
|
194
200
|
/**
|
|
195
201
|
* The `aria-labelledby` attribute value for the list of options
|
|
196
|
-
* Provide the string of
|
|
202
|
+
* Provide the string of IDs seperated by space
|
|
197
203
|
*/
|
|
198
204
|
listAriaLabelledBy: {
|
|
199
205
|
type: String,
|
|
@@ -351,15 +357,42 @@ var script = {
|
|
|
351
357
|
};
|
|
352
358
|
},
|
|
353
359
|
computed: {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
360
|
+
/**
|
|
361
|
+
* Determines the `aria-labelledby` attribute value for the listbox by
|
|
362
|
+
* evaluating a series of conditions in priority order. The returned ID
|
|
363
|
+
* references the element that best describes the listbox content, with
|
|
364
|
+
* preference given to headers in searchable lists, followed by search
|
|
365
|
+
* input, form labels, and finally fallback options.
|
|
366
|
+
*/
|
|
367
|
+
listboxAriaLabelledByID() {
|
|
368
|
+
// Listbox is labelled by closest heading, creating a meaningful relationship
|
|
369
|
+
if (this.headerId && this.searchable) return `${this.headerId}`;
|
|
370
|
+
|
|
371
|
+
// Listbox is labelled by the search input with role="combobox"
|
|
372
|
+
if (this.searchable) return this.searchInputId;
|
|
373
|
+
|
|
374
|
+
// Listbox is labelledy by the text inside an externally labelled button
|
|
375
|
+
if (this.isInFormGroup) return this.listboxIdComputed;
|
|
376
|
+
|
|
377
|
+
// Fallback. Return a header ID or the toggle button ID.
|
|
358
378
|
return this.listAriaLabelledBy || this.headerId || this.toggleIdComputed;
|
|
359
379
|
},
|
|
360
380
|
toggleIdComputed() {
|
|
361
381
|
return this.toggleId || uniqueId('dropdown-toggle-btn-');
|
|
362
382
|
},
|
|
383
|
+
// Generate a custom listbox ID when inside GlFormGroup
|
|
384
|
+
listboxIdComputed() {
|
|
385
|
+
if (this.isInFormGroup) {
|
|
386
|
+
if (this.listAriaLabelledBy) {
|
|
387
|
+
return this.listAriaLabelledBy;
|
|
388
|
+
}
|
|
389
|
+
if (this.toggleId) {
|
|
390
|
+
return `${this.toggleId}-span`;
|
|
391
|
+
}
|
|
392
|
+
return uniqueId('dropdown-toggle-span-');
|
|
393
|
+
}
|
|
394
|
+
return undefined;
|
|
395
|
+
},
|
|
363
396
|
listboxTag() {
|
|
364
397
|
if (!this.hasItems || isOption(this.items[0])) return 'ul';
|
|
365
398
|
return 'div';
|
|
@@ -567,6 +600,10 @@ var script = {
|
|
|
567
600
|
(_this$scrollObserver = this.scrollObserver) === null || _this$scrollObserver === void 0 ? void 0 : _this$scrollObserver.disconnect();
|
|
568
601
|
},
|
|
569
602
|
methods: {
|
|
603
|
+
onFocusContent(event) {
|
|
604
|
+
event.preventDefault();
|
|
605
|
+
this.open();
|
|
606
|
+
},
|
|
570
607
|
open() {
|
|
571
608
|
this.$refs.baseDropdown.open();
|
|
572
609
|
},
|
|
@@ -874,9 +911,9 @@ var script = {
|
|
|
874
911
|
const __vue_script__ = script;
|
|
875
912
|
|
|
876
913
|
/* template */
|
|
877
|
-
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-base-dropdown',{ref:"baseDropdown",attrs:{"aria-haspopup":"listbox","aria-labelledby":_vm.toggleAriaLabelledBy,"block":_vm.block,"toggle-id":_vm.toggleIdComputed,"toggle-text":_vm.listboxToggleText,"toggle-class":_vm.toggleButtonClasses,"text-sr-only":_vm.textSrOnly,"category":_vm.category,"variant":_vm.variant,"size":_vm.size,"icon":_vm.icon,"disabled":_vm.disabled,"loading":_vm.loading,"no-caret":_vm.noCaret,"placement":_vm.placement,"offset":_vm.dropdownOffset,"fluid-width":_vm.fluidWidth,"positioning-strategy":_vm.positioningStrategy},on:_vm._d({},[_vm.$options.events.GL_DROPDOWN_SHOWN,_vm.onShow,_vm.$options.events.GL_DROPDOWN_HIDDEN,_vm.onHide]),scopedSlots:_vm._u([(_vm.hasCustomToggle)?{key:"toggle",fn:function(){return [_vm._t("toggle")]}
|
|
914
|
+
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('gl-base-dropdown',{ref:"baseDropdown",attrs:{"aria-haspopup":"listbox","active-item-id":_vm.activeItemId,"aria-labelledby":_vm.toggleAriaLabelledBy,"block":_vm.block,"has-searchable-listbox":_vm.searchable,"has-external-label":_vm.isInFormGroup,"listbox-id":_vm.listboxIdComputed,"toggle-id":_vm.toggleIdComputed,"toggle-text":_vm.listboxToggleText,"toggle-class":_vm.toggleButtonClasses,"text-sr-only":_vm.textSrOnly,"category":_vm.category,"variant":_vm.variant,"size":_vm.size,"icon":_vm.icon,"disabled":_vm.disabled,"loading":_vm.loading,"no-caret":_vm.noCaret,"placement":_vm.placement,"offset":_vm.dropdownOffset,"fluid-width":_vm.fluidWidth,"positioning-strategy":_vm.positioningStrategy},on:_vm._d({},[_vm.$options.events.GL_DROPDOWN_FOCUS_CONTENT,_vm.onFocusContent,_vm.$options.events.GL_DROPDOWN_SHOWN,_vm.onShow,_vm.$options.events.GL_DROPDOWN_HIDDEN,_vm.onHide]),scopedSlots:_vm._u([(_vm.hasCustomToggle)?{key:"toggle",fn:function(slotProps){return [_vm._t("toggle",null,null,slotProps)]}}:null,{key:"default",fn:function(ref){
|
|
878
915
|
var visible = ref.visible;
|
|
879
|
-
return [(_vm.headerText)?_c('div',{staticClass:"gl-flex gl-min-h-8 gl-items-center !gl-p-4",class:_vm.$options.HEADER_ITEMS_BORDER_CLASSES},[_c('div',{staticClass:"gl-grow gl-pr-2 gl-text-sm gl-font-bold gl-text-strong",attrs:{"id":_vm.headerId,"data-testid":"listbox-header-text"}},[_vm._v("\n "+_vm._s(_vm.headerText)+"\n ")]),_vm._v(" "),(_vm.showResetButton)?_c('gl-button',{staticClass:"!gl-m-0 !gl-w-auto gl-max-w-1/2 gl-flex-shrink-0 gl-text-ellipsis !gl-px-2 !gl-text-sm focus:!gl-focus-inset",attrs:{"category":"tertiary","size":"small","data-testid":"listbox-reset-button"},on:{"click":_vm.onResetButtonClicked}},[_vm._v("\n "+_vm._s(_vm.resetButtonLabel)+"\n ")]):_vm._e(),_vm._v(" "),(_vm.showSelectAllButton)?_c('gl-button',{staticClass:"!gl-m-0 !gl-w-auto gl-max-w-1/2 gl-flex-shrink-0 gl-text-ellipsis !gl-px-2 !gl-text-sm focus:!gl-focus-inset",attrs:{"category":"tertiary","size":"small","data-testid":"listbox-select-all-button"},on:{"click":_vm.onSelectAllButtonClicked}},[_vm._v("\n "+_vm._s(_vm.showSelectAllButtonLabel)+"\n ")]):_vm._e()],1):_vm._e(),_vm._v(" "),(_vm.searchable)?_c('div',{class:_vm.$options.HEADER_ITEMS_BORDER_CLASSES},[_c('gl-listbox-search-input',{ref:"searchBox",class:{ 'gl-listbox-topmost': !_vm.headerText },attrs:{"id":_vm.searchInputId,"data-testid":"listbox-search-input","role":"combobox","aria-expanded":String(visible),"aria-controls":_vm.listboxId,"aria-activedescendant":_vm.activeItemId,"aria-haspopup":"listbox","placeholder":_vm.searchPlaceholder},on:{"input":_vm.search,"keydown":[function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"enter",13,$event.key,"Enter")){ return null; }$event.preventDefault();},_vm.onKeydown]},model:{value:(_vm.searchStr),callback:function ($$v) {_vm.searchStr=$$v;},expression:"searchStr"}}),_vm._v(" "),(_vm.searching)?_c('gl-loading-icon',{staticClass:"gl-my-3",attrs:{"data-testid":"listbox-search-loader","size":"md"}}):_vm._e()],1):_vm._e(),_vm._v(" "),(_vm.showList)?_c(_vm.listboxTag,{ref:"list",tag:"component",staticClass:"gl-new-dropdown-contents gl-new-dropdown-contents-with-scrim-overlay",class:_vm.listboxClasses,attrs:{"id":_vm.listboxId,"aria-busy":_vm.isBusy,"aria-labelledby":_vm.
|
|
916
|
+
return [(_vm.headerText)?_c('div',{staticClass:"gl-flex gl-min-h-8 gl-items-center !gl-p-4",class:_vm.$options.HEADER_ITEMS_BORDER_CLASSES},[_c('div',{staticClass:"gl-grow gl-pr-2 gl-text-sm gl-font-bold gl-text-strong",attrs:{"id":_vm.headerId,"data-testid":"listbox-header-text"}},[_vm._v("\n "+_vm._s(_vm.headerText)+"\n ")]),_vm._v(" "),(_vm.showResetButton)?_c('gl-button',{staticClass:"!gl-m-0 !gl-w-auto gl-max-w-1/2 gl-flex-shrink-0 gl-text-ellipsis !gl-px-2 !gl-text-sm focus:!gl-focus-inset",attrs:{"category":"tertiary","size":"small","data-testid":"listbox-reset-button"},on:{"click":_vm.onResetButtonClicked}},[_vm._v("\n "+_vm._s(_vm.resetButtonLabel)+"\n ")]):_vm._e(),_vm._v(" "),(_vm.showSelectAllButton)?_c('gl-button',{staticClass:"!gl-m-0 !gl-w-auto gl-max-w-1/2 gl-flex-shrink-0 gl-text-ellipsis !gl-px-2 !gl-text-sm focus:!gl-focus-inset",attrs:{"category":"tertiary","size":"small","data-testid":"listbox-select-all-button"},on:{"click":_vm.onSelectAllButtonClicked}},[_vm._v("\n "+_vm._s(_vm.showSelectAllButtonLabel)+"\n ")]):_vm._e()],1):_vm._e(),_vm._v(" "),(_vm.searchable)?_c('div',{class:_vm.$options.HEADER_ITEMS_BORDER_CLASSES},[_c('gl-listbox-search-input',{ref:"searchBox",class:{ 'gl-listbox-topmost': !_vm.headerText },attrs:{"id":_vm.searchInputId,"data-testid":"listbox-search-input","role":"combobox","aria-expanded":String(visible),"aria-controls":_vm.listboxId,"aria-activedescendant":_vm.activeItemId,"aria-haspopup":"listbox","placeholder":_vm.searchPlaceholder},on:{"input":_vm.search,"keydown":[function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"enter",13,$event.key,"Enter")){ return null; }$event.preventDefault();},_vm.onKeydown]},model:{value:(_vm.searchStr),callback:function ($$v) {_vm.searchStr=$$v;},expression:"searchStr"}}),_vm._v(" "),(_vm.searching)?_c('gl-loading-icon',{staticClass:"gl-my-3",attrs:{"data-testid":"listbox-search-loader","size":"md"}}):_vm._e()],1):_vm._e(),_vm._v(" "),(_vm.showList)?_c(_vm.listboxTag,{ref:"list",tag:"component",staticClass:"gl-new-dropdown-contents gl-new-dropdown-contents-with-scrim-overlay",class:_vm.listboxClasses,attrs:{"id":_vm.listboxId,"aria-busy":_vm.isBusy,"aria-labelledby":_vm.listboxAriaLabelledByID,"aria-multiselectable":_vm.multiple ? 'true' : undefined,"role":"listbox","tabindex":"0"},on:{"keydown":_vm.onKeydown}},[_c(_vm.itemTag,{tag:"component",staticClass:"top-scrim-wrapper",attrs:{"aria-hidden":"true","data-testid":"top-scrim"}},[_c('div',{staticClass:"top-scrim",class:{ 'top-scrim-light': !_vm.hasHeader, 'top-scrim-dark': _vm.hasHeader }})]),_vm._v(" "),_c(_vm.itemTag,{ref:"top-boundary",tag:"component",attrs:{"aria-hidden":"true"}}),_vm._v(" "),_vm._l((_vm.items),function(item,index){return [(_vm.isOption(item))?[_c('gl-listbox-item',_vm._b({key:_vm.listboxItemKey(item),attrs:{"id":_vm.generateItemId(item),"data-testid":("listbox-item-" + (item.value)),"is-highlighted":_vm.isHighlighted(item),"is-selected":_vm.isSelected(item),"is-focused":_vm.isFocused(item),"is-check-centered":_vm.isCheckCentered},on:{"select":function($event){return _vm.onSelect(item, $event)}}},'gl-listbox-item',_vm.listboxItemMoreItemsAriaAttributes(index),false),[_vm._t("list-item",function(){return [_vm._v("\n "+_vm._s(item.text)+"\n ")]},{"item":item})],2)]:[_c('gl-listbox-group',{key:item.text,class:_vm.groupClasses(index),attrs:{"name":item.text,"text-sr-only":item.textSrOnly},scopedSlots:_vm._u([(_vm.$scopedSlots['group-label'])?{key:"group-label",fn:function(){return [_vm._t("group-label",null,{"group":item})]},proxy:true}:null],null,true)},[_vm._v(" "),_vm._l((item.options),function(option){return _c('gl-listbox-item',{key:_vm.listboxItemKey(option),attrs:{"id":_vm.generateItemId(option),"data-testid":("listbox-item-" + (option.value)),"is-highlighted":_vm.isHighlighted(option),"is-selected":_vm.isSelected(option),"is-focused":_vm.isFocused(option),"is-check-centered":_vm.isCheckCentered},on:{"select":function($event){return _vm.onSelect(option, $event)}}},[_vm._t("list-item",function(){return [_vm._v("\n "+_vm._s(option.text)+"\n ")]},{"item":option})],2)})],2)]]}),_vm._v(" "),(_vm.infiniteScrollLoading)?_c(_vm.itemTag,{tag:"component"},[_c('gl-loading-icon',{staticClass:"gl-my-3",attrs:{"data-testid":"listbox-infinite-scroll-loader","size":"md"}})],1):_vm._e(),_vm._v(" "),(_vm.showIntersectionObserver)?_c('gl-intersection-observer',{on:{"appear":_vm.onIntersectionObserverAppear}}):_vm._e(),_vm._v(" "),_c(_vm.itemTag,{ref:"bottom-boundary",tag:"component",attrs:{"aria-hidden":"true"}}),_vm._v(" "),_c(_vm.itemTag,{tag:"component",staticClass:"bottom-scrim-wrapper",attrs:{"aria-hidden":"true","data-testid":"bottom-scrim"}},[_c('div',{staticClass:"bottom-scrim",class:{ '!gl-rounded-none': _vm.hasFooter }})])],2):_vm._e(),_vm._v(" "),(_vm.announceSRSearchResults)?_c('span',{staticClass:"gl-sr-only",attrs:{"data-testid":"listbox-number-of-results","aria-live":"assertive"}},[_vm._t("search-summary-sr-only",function(){return [_vm._v("\n "+_vm._s(_vm.srOnlyResultsLabel(_vm.flattenedOptions.length))+"\n ")]})],2):_vm._e(),_vm._v(" "),(_vm.isBusy)?_c('span',{staticClass:"gl-sr-only",attrs:{"aria-live":"polite","data-testid":"listbox-loading-announcement"}},[_vm._v("\n "+_vm._s(_vm.loadingAnnouncementText)+"\n ")]):(_vm.showNoResultsText)?_c('div',{staticClass:"gl-py-3 gl-pl-7 gl-pr-5 gl-text-base gl-text-subtle",attrs:{"aria-live":"assertive","data-testid":"listbox-no-results-text"}},[_vm._v("\n "+_vm._s(_vm.noResultsText)+"\n ")]):_vm._e(),_vm._v(" "),_vm._t("footer")]}}],null,true)})};
|
|
880
917
|
var __vue_staticRenderFns__ = [];
|
|
881
918
|
|
|
882
919
|
/* style */
|
package/package.json
CHANGED
|
@@ -8,9 +8,6 @@ export default {
|
|
|
8
8
|
name: 'GlFormCheckbox',
|
|
9
9
|
inject: {
|
|
10
10
|
getGroup: {
|
|
11
|
-
// When we remove BFormCheckboxGroup from GlFormCheckboxGroup, we can rename
|
|
12
|
-
// the `getBvCheckGroup` provide to `getCheckGroup`.
|
|
13
|
-
from: 'getBvCheckGroup',
|
|
14
11
|
default: () => () => null,
|
|
15
12
|
},
|
|
16
13
|
},
|
|
@@ -1,44 +1,171 @@
|
|
|
1
1
|
<script>
|
|
2
|
+
import uniqueId from 'lodash/uniqueId';
|
|
3
|
+
import isBoolean from 'lodash/isBoolean';
|
|
4
|
+
import omit from 'lodash/omit';
|
|
5
|
+
import pick from 'lodash/pick';
|
|
6
|
+
import { looseEqual } from '../../../../vendor/bootstrap-vue/src/utils/loose-equal';
|
|
2
7
|
import { formOptionsMixin } from '../../../../vendor/bootstrap-vue/src/mixins/form-options';
|
|
3
|
-
import { BFormCheckboxGroup } from '../../../../vendor/bootstrap-vue/src/components/form-checkbox/form-checkbox-group';
|
|
4
8
|
import { SafeHtmlDirective as SafeHtml } from '../../../../directives/safe_html/safe_html';
|
|
5
9
|
import GlFormCheckbox from './form_checkbox.vue';
|
|
6
10
|
|
|
11
|
+
// Attributes to pass down to checks/radios instead of applying them to the group
|
|
12
|
+
const PASS_DOWN_ATTRS = ['aria-describedby', 'aria-labelledby'];
|
|
13
|
+
|
|
7
14
|
export default {
|
|
8
15
|
name: 'GlFormCheckboxGroup',
|
|
9
|
-
components: {
|
|
16
|
+
components: { GlFormCheckbox },
|
|
10
17
|
directives: {
|
|
11
18
|
SafeHtml,
|
|
12
19
|
},
|
|
13
20
|
mixins: [formOptionsMixin],
|
|
21
|
+
provide() {
|
|
22
|
+
return {
|
|
23
|
+
getGroup: () => this,
|
|
24
|
+
};
|
|
25
|
+
},
|
|
14
26
|
inheritAttrs: false,
|
|
15
27
|
model: {
|
|
16
28
|
prop: 'checked',
|
|
17
29
|
event: 'input',
|
|
18
30
|
},
|
|
31
|
+
props: {
|
|
32
|
+
/**
|
|
33
|
+
* Used to set the `id` attribute on the rendered content, and used as the base to generate any additional element IDs as needed.
|
|
34
|
+
*/
|
|
35
|
+
id: {
|
|
36
|
+
type: String,
|
|
37
|
+
required: false,
|
|
38
|
+
default: undefined,
|
|
39
|
+
},
|
|
40
|
+
/**
|
|
41
|
+
* The current value of the checkbox.
|
|
42
|
+
*/
|
|
43
|
+
checked: {
|
|
44
|
+
type: Array,
|
|
45
|
+
required: false,
|
|
46
|
+
default: () => [],
|
|
47
|
+
},
|
|
48
|
+
/**
|
|
49
|
+
* Array of items to render in the component
|
|
50
|
+
*/
|
|
51
|
+
options: {
|
|
52
|
+
type: Array,
|
|
53
|
+
required: false,
|
|
54
|
+
default: () => [],
|
|
55
|
+
},
|
|
56
|
+
/**
|
|
57
|
+
* When set to `true`, disables the component's functionality and places it in a disabled state.
|
|
58
|
+
*/
|
|
59
|
+
disabled: {
|
|
60
|
+
type: Boolean,
|
|
61
|
+
required: false,
|
|
62
|
+
default: false,
|
|
63
|
+
},
|
|
64
|
+
/**
|
|
65
|
+
* Sets the value of the `name` attribute on the form control.
|
|
66
|
+
*/
|
|
67
|
+
name: {
|
|
68
|
+
type: String,
|
|
69
|
+
required: false,
|
|
70
|
+
default: undefined,
|
|
71
|
+
},
|
|
72
|
+
/**
|
|
73
|
+
* Adds the `required` attribute to the form control.
|
|
74
|
+
*/
|
|
75
|
+
required: {
|
|
76
|
+
type: Boolean,
|
|
77
|
+
required: false,
|
|
78
|
+
default: false,
|
|
79
|
+
},
|
|
80
|
+
/**
|
|
81
|
+
* Controls the validation state appearance of the component. `true` for valid, `false` for invalid, or `null` for no validation state.
|
|
82
|
+
*/
|
|
83
|
+
state: {
|
|
84
|
+
type: Boolean,
|
|
85
|
+
required: false,
|
|
86
|
+
default: null,
|
|
87
|
+
},
|
|
88
|
+
/**
|
|
89
|
+
* 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
|
|
90
|
+
*/
|
|
91
|
+
ariaInvalid: {
|
|
92
|
+
type: [Boolean, String],
|
|
93
|
+
required: false,
|
|
94
|
+
default: false,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
data() {
|
|
98
|
+
return {
|
|
99
|
+
internalId: this.id ? this.id : uniqueId('gitlab_ui_checkbox_group_'),
|
|
100
|
+
localChecked: this.checked,
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
computed: {
|
|
104
|
+
computedState() {
|
|
105
|
+
return isBoolean(this.state) ? this.state : null;
|
|
106
|
+
},
|
|
107
|
+
stateClass() {
|
|
108
|
+
if (this.computedState === true) return 'is-valid';
|
|
109
|
+
if (this.computedState === false) return 'is-invalid';
|
|
110
|
+
return null;
|
|
111
|
+
},
|
|
112
|
+
computedAriaInvalid() {
|
|
113
|
+
const { ariaInvalid } = this;
|
|
114
|
+
if (ariaInvalid === true || ariaInvalid === 'true' || ariaInvalid === '') {
|
|
115
|
+
return 'true';
|
|
116
|
+
}
|
|
117
|
+
return this.computedState === false ? 'true' : ariaInvalid;
|
|
118
|
+
},
|
|
119
|
+
computedAttrs() {
|
|
120
|
+
return {
|
|
121
|
+
...omit(this.$attrs, PASS_DOWN_ATTRS),
|
|
122
|
+
id: this.internalId,
|
|
123
|
+
'aria-invalid': this.computedAriaInvalid,
|
|
124
|
+
'aria-required': this.required || null,
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
passDownAttrs() {
|
|
128
|
+
return pick(this.$attrs, PASS_DOWN_ATTRS);
|
|
129
|
+
},
|
|
130
|
+
groupName() {
|
|
131
|
+
// Checks/Radios tied to the same model must have the same name,
|
|
132
|
+
// especially for ARIA accessibility
|
|
133
|
+
return this.name || this.internalId;
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
watch: {
|
|
137
|
+
checked(newValue) {
|
|
138
|
+
if (!looseEqual(newValue, this.localChecked)) {
|
|
139
|
+
this.localChecked = newValue;
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
localChecked(newValue, oldValue) {
|
|
143
|
+
if (!looseEqual(newValue, oldValue)) {
|
|
144
|
+
this.$emit('input', newValue);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
},
|
|
19
148
|
};
|
|
20
149
|
</script>
|
|
21
150
|
|
|
22
151
|
<template>
|
|
23
|
-
<div
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
152
|
+
<div
|
|
153
|
+
v-bind="computedAttrs"
|
|
154
|
+
role="group"
|
|
155
|
+
tabindex="-1"
|
|
156
|
+
class="gl-form-checkbox-group gl-outline-none"
|
|
157
|
+
>
|
|
158
|
+
<slot name="first"></slot>
|
|
159
|
+
<gl-form-checkbox
|
|
160
|
+
v-for="(option, idx) in formOptions"
|
|
161
|
+
v-bind="passDownAttrs"
|
|
162
|
+
:key="idx"
|
|
163
|
+
:value="option.value"
|
|
164
|
+
:disabled="option.disabled"
|
|
30
165
|
>
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
:value="option.value"
|
|
36
|
-
:disabled="option.disabled"
|
|
37
|
-
>
|
|
38
|
-
<span v-if="option.html" v-safe-html="option.html"></span>
|
|
39
|
-
<span v-else>{{ option.text }}</span>
|
|
40
|
-
</gl-form-checkbox>
|
|
41
|
-
<slot></slot>
|
|
42
|
-
</b-form-checkbox-group>
|
|
166
|
+
<span v-if="option.html" v-safe-html="option.html"></span>
|
|
167
|
+
<span v-else>{{ option.text }}</span>
|
|
168
|
+
</gl-form-checkbox>
|
|
169
|
+
<slot></slot>
|
|
43
170
|
</div>
|
|
44
171
|
</template>
|
|
@@ -138,6 +138,26 @@ export default {
|
|
|
138
138
|
return ['menu', 'listbox', 'tree', 'grid', 'dialog', true, false].includes(value);
|
|
139
139
|
},
|
|
140
140
|
},
|
|
141
|
+
activeItemId: {
|
|
142
|
+
type: String,
|
|
143
|
+
required: false,
|
|
144
|
+
default: undefined,
|
|
145
|
+
},
|
|
146
|
+
hasExternalLabel: {
|
|
147
|
+
type: Boolean,
|
|
148
|
+
required: false,
|
|
149
|
+
default: false,
|
|
150
|
+
},
|
|
151
|
+
hasSearchableListbox: {
|
|
152
|
+
type: Boolean,
|
|
153
|
+
required: false,
|
|
154
|
+
default: false,
|
|
155
|
+
},
|
|
156
|
+
isDisclosure: {
|
|
157
|
+
type: Boolean,
|
|
158
|
+
required: false,
|
|
159
|
+
default: false,
|
|
160
|
+
},
|
|
141
161
|
/**
|
|
142
162
|
* Id that will be referenced by `aria-labelledby` attribute of the dropdown content`
|
|
143
163
|
*/
|
|
@@ -145,6 +165,15 @@ export default {
|
|
|
145
165
|
type: String,
|
|
146
166
|
required: true,
|
|
147
167
|
},
|
|
168
|
+
/**
|
|
169
|
+
* Span Id that will be referenced by listbox `aria-labelledby` if there's an external label.
|
|
170
|
+
* This prop will only be defined by `GlCollapsibleListbox` when `isInFormGroup` injected is true.
|
|
171
|
+
*/
|
|
172
|
+
listboxId: {
|
|
173
|
+
type: String,
|
|
174
|
+
required: false,
|
|
175
|
+
default: undefined,
|
|
176
|
+
},
|
|
148
177
|
/**
|
|
149
178
|
* The `aria-labelledby` attribute value for the toggle `button`
|
|
150
179
|
*/
|
|
@@ -187,6 +216,10 @@ export default {
|
|
|
187
216
|
};
|
|
188
217
|
},
|
|
189
218
|
computed: {
|
|
219
|
+
ariaActiveDescendant() {
|
|
220
|
+
if (!this.isDisclosure && this.visible) return this.activeItemId;
|
|
221
|
+
return undefined;
|
|
222
|
+
},
|
|
190
223
|
hasNoVisibleToggleText() {
|
|
191
224
|
return !this.toggleText?.length || this.textSrOnly;
|
|
192
225
|
},
|
|
@@ -199,12 +232,47 @@ export default {
|
|
|
199
232
|
isCaretOnly() {
|
|
200
233
|
return !this.noCaret && !this.icon && this.hasNoVisibleToggleText;
|
|
201
234
|
},
|
|
202
|
-
|
|
235
|
+
isDefaultToggle() {
|
|
236
|
+
return !this.$scopedSlots.toggle;
|
|
237
|
+
},
|
|
238
|
+
isToggleCombobox() {
|
|
239
|
+
if (this.hasSearchableListbox || this.isDisclosure) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
return true;
|
|
243
|
+
},
|
|
244
|
+
isToggleLabelledExternally() {
|
|
245
|
+
if (this.hasExternalLabel && this.toggleId) return true;
|
|
246
|
+
return false;
|
|
247
|
+
},
|
|
248
|
+
computedToggleId() {
|
|
249
|
+
if (this.isDisclosure) {
|
|
250
|
+
return this.isDefaultToggle ? this.toggleId : undefined;
|
|
251
|
+
}
|
|
252
|
+
if (this.isToggleLabelledExternally) {
|
|
253
|
+
return this.isDefaultToggle ? this.toggleId : undefined;
|
|
254
|
+
}
|
|
255
|
+
if (this.hasSearchableListbox) {
|
|
256
|
+
return this.toggleId;
|
|
257
|
+
}
|
|
258
|
+
return undefined;
|
|
259
|
+
},
|
|
260
|
+
computedToggleInnerId() {
|
|
261
|
+
if (this.isToggleCombobox && !this.isToggleLabelledExternally) {
|
|
262
|
+
return this.toggleId;
|
|
263
|
+
}
|
|
264
|
+
if (this.isToggleCombobox && this.isToggleLabelledExternally) {
|
|
265
|
+
return this.listboxId;
|
|
266
|
+
}
|
|
267
|
+
return undefined;
|
|
268
|
+
},
|
|
269
|
+
toggleAriaAttributes() {
|
|
203
270
|
return {
|
|
204
|
-
'aria-haspopup': this.ariaHaspopup,
|
|
205
|
-
'aria-expanded': String(this.visible),
|
|
206
271
|
'aria-controls': this.baseDropdownId,
|
|
272
|
+
'aria-expanded': String(this.visible),
|
|
273
|
+
'aria-haspopup': this.ariaHaspopup,
|
|
207
274
|
'aria-labelledby': this.toggleLabelledBy,
|
|
275
|
+
'aria-activedescendant': this.ariaActiveDescendant,
|
|
208
276
|
};
|
|
209
277
|
},
|
|
210
278
|
toggleButtonClasses() {
|
|
@@ -223,10 +291,28 @@ export default {
|
|
|
223
291
|
return this.block ? 'gl-w-full' : '';
|
|
224
292
|
},
|
|
225
293
|
toggleLabelledBy() {
|
|
294
|
+
if (this.isToggleCombobox) {
|
|
295
|
+
if (this.ariaLabelledby) {
|
|
296
|
+
return `${this.ariaLabelledby} ${this.toggleId}`;
|
|
297
|
+
}
|
|
298
|
+
return this.toggleId;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// For non-combobox toggles, combine IDs or use the button's own text
|
|
226
302
|
return this.ariaLabelledby ? `${this.ariaLabelledby} ${this.toggleId}` : undefined;
|
|
227
303
|
},
|
|
228
|
-
|
|
229
|
-
|
|
304
|
+
toggleRole() {
|
|
305
|
+
if (this.isToggleCombobox) {
|
|
306
|
+
return 'combobox';
|
|
307
|
+
}
|
|
308
|
+
return undefined;
|
|
309
|
+
},
|
|
310
|
+
toggleAccessibilityAttributes() {
|
|
311
|
+
return {
|
|
312
|
+
...this.toggleAriaAttributes,
|
|
313
|
+
id: this.toggleId,
|
|
314
|
+
role: this.toggleRole,
|
|
315
|
+
};
|
|
230
316
|
},
|
|
231
317
|
toggleOptions() {
|
|
232
318
|
if (this.isDefaultToggle) {
|
|
@@ -241,7 +327,8 @@ export default {
|
|
|
241
327
|
disabled: this.disabled,
|
|
242
328
|
loading: this.loading,
|
|
243
329
|
class: this.toggleButtonClasses,
|
|
244
|
-
|
|
330
|
+
role: this.toggleRole,
|
|
331
|
+
...this.toggleAriaAttributes,
|
|
245
332
|
listeners: {
|
|
246
333
|
keydown: (event) => this.onKeydown(event),
|
|
247
334
|
click: (event) => this.toggle(event),
|
|
@@ -264,7 +351,6 @@ export default {
|
|
|
264
351
|
|
|
265
352
|
toggleAttributes() {
|
|
266
353
|
const { listeners, is, ...attributes } = this.toggleOptions;
|
|
267
|
-
|
|
268
354
|
return attributes;
|
|
269
355
|
},
|
|
270
356
|
toggleComponent() {
|
|
@@ -325,18 +411,6 @@ export default {
|
|
|
325
411
|
};
|
|
326
412
|
},
|
|
327
413
|
},
|
|
328
|
-
watch: {
|
|
329
|
-
ariaAttributes: {
|
|
330
|
-
deep: true,
|
|
331
|
-
handler(ariaAttributes) {
|
|
332
|
-
if (this.$scopedSlots.toggle) {
|
|
333
|
-
Object.keys(ariaAttributes).forEach((key) => {
|
|
334
|
-
this.toggleElement.setAttribute(key, ariaAttributes[key]);
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
},
|
|
338
|
-
},
|
|
339
|
-
},
|
|
340
414
|
mounted() {
|
|
341
415
|
this.checkToggleFocusable();
|
|
342
416
|
},
|
|
@@ -593,14 +667,19 @@ export default {
|
|
|
593
667
|
<component
|
|
594
668
|
:is="toggleComponent"
|
|
595
669
|
v-bind="toggleAttributes"
|
|
596
|
-
:id="
|
|
670
|
+
:id="computedToggleId"
|
|
597
671
|
ref="toggle"
|
|
598
672
|
data-testid="base-dropdown-toggle"
|
|
599
673
|
v-on="toggleListeners"
|
|
600
674
|
>
|
|
601
675
|
<!-- @slot Custom toggle button content -->
|
|
602
|
-
<slot name="toggle">
|
|
603
|
-
<span
|
|
676
|
+
<slot name="toggle" :accessibility-attributes="toggleAccessibilityAttributes">
|
|
677
|
+
<span
|
|
678
|
+
:id="computedToggleInnerId"
|
|
679
|
+
class="gl-new-dropdown-button-text"
|
|
680
|
+
:class="{ 'gl-sr-only': textSrOnly }"
|
|
681
|
+
data-testid="base-dropdown-span"
|
|
682
|
+
>
|
|
604
683
|
{{ toggleText }}
|
|
605
684
|
</span>
|
|
606
685
|
<gl-icon
|
|
@@ -389,14 +389,15 @@ export default {
|
|
|
389
389
|
:fluid-width="fluidWidth"
|
|
390
390
|
:positioning-strategy="positioningStrategy"
|
|
391
391
|
class="gl-disclosure-dropdown"
|
|
392
|
+
is-disclosure
|
|
392
393
|
@[$options.events.GL_DROPDOWN_SHOWN]="onShow"
|
|
393
394
|
@[$options.events.GL_DROPDOWN_HIDDEN]="onHide"
|
|
394
395
|
@[$options.events.GL_DROPDOWN_BEFORE_CLOSE]="onBeforeClose"
|
|
395
396
|
@[$options.events.GL_DROPDOWN_FOCUS_CONTENT]="onKeydown"
|
|
396
397
|
>
|
|
397
|
-
<template v-if="hasCustomToggle" #toggle>
|
|
398
|
+
<template v-if="hasCustomToggle" #toggle="slotProps">
|
|
398
399
|
<!-- @slot Custom toggle content -->
|
|
399
|
-
<slot name="toggle"></slot>
|
|
400
|
+
<slot name="toggle" v-bind="slotProps"></slot>
|
|
400
401
|
</template>
|
|
401
402
|
|
|
402
403
|
<!-- @slot Content to display in dropdown header -->
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
ARROW_DOWN,
|
|
12
12
|
ARROW_UP,
|
|
13
13
|
GL_DROPDOWN_CONTENTS_CLASS,
|
|
14
|
+
GL_DROPDOWN_FOCUS_CONTENT,
|
|
14
15
|
POSITION_ABSOLUTE,
|
|
15
16
|
POSITION_FIXED,
|
|
16
17
|
} from '../constants';
|
|
@@ -53,6 +54,7 @@ export default {
|
|
|
53
54
|
events: {
|
|
54
55
|
GL_DROPDOWN_SHOWN,
|
|
55
56
|
GL_DROPDOWN_HIDDEN,
|
|
57
|
+
GL_DROPDOWN_FOCUS_CONTENT,
|
|
56
58
|
},
|
|
57
59
|
components: {
|
|
58
60
|
GlBaseDropdown,
|
|
@@ -64,6 +66,11 @@ export default {
|
|
|
64
66
|
GlLoadingIcon,
|
|
65
67
|
GlIntersectionObserver,
|
|
66
68
|
},
|
|
69
|
+
inject: {
|
|
70
|
+
isInFormGroup: {
|
|
71
|
+
default: false,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
67
74
|
model: {
|
|
68
75
|
prop: 'selected',
|
|
69
76
|
event: 'select',
|
|
@@ -211,7 +218,7 @@ export default {
|
|
|
211
218
|
},
|
|
212
219
|
/**
|
|
213
220
|
* The `aria-labelledby` attribute value for the toggle button
|
|
214
|
-
* Provide the string of
|
|
221
|
+
* Provide the string of IDs seperated by space
|
|
215
222
|
*/
|
|
216
223
|
toggleAriaLabelledBy: {
|
|
217
224
|
type: String,
|
|
@@ -220,7 +227,7 @@ export default {
|
|
|
220
227
|
},
|
|
221
228
|
/**
|
|
222
229
|
* The `aria-labelledby` attribute value for the list of options
|
|
223
|
-
* Provide the string of
|
|
230
|
+
* Provide the string of IDs seperated by space
|
|
224
231
|
*/
|
|
225
232
|
listAriaLabelledBy: {
|
|
226
233
|
type: String,
|
|
@@ -382,15 +389,42 @@ export default {
|
|
|
382
389
|
};
|
|
383
390
|
},
|
|
384
391
|
computed: {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
392
|
+
/**
|
|
393
|
+
* Determines the `aria-labelledby` attribute value for the listbox by
|
|
394
|
+
* evaluating a series of conditions in priority order. The returned ID
|
|
395
|
+
* references the element that best describes the listbox content, with
|
|
396
|
+
* preference given to headers in searchable lists, followed by search
|
|
397
|
+
* input, form labels, and finally fallback options.
|
|
398
|
+
*/
|
|
399
|
+
listboxAriaLabelledByID() {
|
|
400
|
+
// Listbox is labelled by closest heading, creating a meaningful relationship
|
|
401
|
+
if (this.headerId && this.searchable) return `${this.headerId}`;
|
|
402
|
+
|
|
403
|
+
// Listbox is labelled by the search input with role="combobox"
|
|
404
|
+
if (this.searchable) return this.searchInputId;
|
|
405
|
+
|
|
406
|
+
// Listbox is labelledy by the text inside an externally labelled button
|
|
407
|
+
if (this.isInFormGroup) return this.listboxIdComputed;
|
|
408
|
+
|
|
409
|
+
// Fallback. Return a header ID or the toggle button ID.
|
|
389
410
|
return this.listAriaLabelledBy || this.headerId || this.toggleIdComputed;
|
|
390
411
|
},
|
|
391
412
|
toggleIdComputed() {
|
|
392
413
|
return this.toggleId || uniqueId('dropdown-toggle-btn-');
|
|
393
414
|
},
|
|
415
|
+
// Generate a custom listbox ID when inside GlFormGroup
|
|
416
|
+
listboxIdComputed() {
|
|
417
|
+
if (this.isInFormGroup) {
|
|
418
|
+
if (this.listAriaLabelledBy) {
|
|
419
|
+
return this.listAriaLabelledBy;
|
|
420
|
+
}
|
|
421
|
+
if (this.toggleId) {
|
|
422
|
+
return `${this.toggleId}-span`;
|
|
423
|
+
}
|
|
424
|
+
return uniqueId('dropdown-toggle-span-');
|
|
425
|
+
}
|
|
426
|
+
return undefined;
|
|
427
|
+
},
|
|
394
428
|
listboxTag() {
|
|
395
429
|
if (!this.hasItems || isOption(this.items[0])) return 'ul';
|
|
396
430
|
return 'div';
|
|
@@ -606,6 +640,10 @@ export default {
|
|
|
606
640
|
this.scrollObserver?.disconnect();
|
|
607
641
|
},
|
|
608
642
|
methods: {
|
|
643
|
+
onFocusContent(event) {
|
|
644
|
+
event.preventDefault();
|
|
645
|
+
this.open();
|
|
646
|
+
},
|
|
609
647
|
open() {
|
|
610
648
|
this.$refs.baseDropdown.open();
|
|
611
649
|
},
|
|
@@ -922,8 +960,12 @@ export default {
|
|
|
922
960
|
<gl-base-dropdown
|
|
923
961
|
ref="baseDropdown"
|
|
924
962
|
aria-haspopup="listbox"
|
|
963
|
+
:active-item-id="activeItemId"
|
|
925
964
|
:aria-labelledby="toggleAriaLabelledBy"
|
|
926
965
|
:block="block"
|
|
966
|
+
:has-searchable-listbox="searchable"
|
|
967
|
+
:has-external-label="isInFormGroup"
|
|
968
|
+
:listbox-id="listboxIdComputed"
|
|
927
969
|
:toggle-id="toggleIdComputed"
|
|
928
970
|
:toggle-text="listboxToggleText"
|
|
929
971
|
:toggle-class="toggleButtonClasses"
|
|
@@ -939,12 +981,13 @@ export default {
|
|
|
939
981
|
:offset="dropdownOffset"
|
|
940
982
|
:fluid-width="fluidWidth"
|
|
941
983
|
:positioning-strategy="positioningStrategy"
|
|
984
|
+
@[$options.events.GL_DROPDOWN_FOCUS_CONTENT]="onFocusContent"
|
|
942
985
|
@[$options.events.GL_DROPDOWN_SHOWN]="onShow"
|
|
943
986
|
@[$options.events.GL_DROPDOWN_HIDDEN]="onHide"
|
|
944
987
|
>
|
|
945
|
-
<template v-if="hasCustomToggle" #toggle>
|
|
988
|
+
<template v-if="hasCustomToggle" #toggle="slotProps">
|
|
946
989
|
<!-- @slot Custom toggle content -->
|
|
947
|
-
<slot name="toggle"></slot>
|
|
990
|
+
<slot name="toggle" v-bind="slotProps"></slot>
|
|
948
991
|
</template>
|
|
949
992
|
|
|
950
993
|
<template #default="{ visible }">
|
|
@@ -1013,7 +1056,7 @@ export default {
|
|
|
1013
1056
|
:id="listboxId"
|
|
1014
1057
|
ref="list"
|
|
1015
1058
|
:aria-busy="isBusy"
|
|
1016
|
-
:aria-labelledby="
|
|
1059
|
+
:aria-labelledby="listboxAriaLabelledByID"
|
|
1017
1060
|
:aria-multiselectable="multiple ? 'true' : undefined"
|
|
1018
1061
|
role="listbox"
|
|
1019
1062
|
class="gl-new-dropdown-contents gl-new-dropdown-contents-with-scrim-overlay"
|
|
@@ -7,7 +7,6 @@ $bv-enable-popover-variants: false;
|
|
|
7
7
|
@import '../vendor/bootstrap-vue/src/utilities.scss';
|
|
8
8
|
|
|
9
9
|
@import '../vendor/bootstrap-vue/src/components/dropdown/index.scss';
|
|
10
|
-
@import '../vendor/bootstrap-vue/src/components/form-checkbox/index.scss';
|
|
11
10
|
@import '../vendor/bootstrap-vue/src/components/form-input/index.scss';
|
|
12
11
|
@import '../vendor/bootstrap-vue/src/components/form-radio/index.scss';
|
|
13
12
|
@import '../vendor/bootstrap-vue/src/components/modal/index.scss';
|
|
@@ -11,8 +11,6 @@ export const NAME_DROPDOWN_ITEM = 'BDropdownItem'
|
|
|
11
11
|
export const NAME_DROPDOWN_ITEM_BUTTON = 'BDropdownItemButton'
|
|
12
12
|
export const NAME_DROPDOWN_TEXT = 'BDropdownText'
|
|
13
13
|
export const NAME_FORM = 'BForm'
|
|
14
|
-
export const NAME_FORM_CHECKBOX = 'BFormCheckbox'
|
|
15
|
-
export const NAME_FORM_CHECKBOX_GROUP = 'BFormCheckboxGroup'
|
|
16
14
|
export const NAME_FORM_GROUP = 'BFormGroup'
|
|
17
15
|
export const NAME_FORM_INVALID_FEEDBACK = 'BFormInvalidFeedback'
|
|
18
16
|
export const NAME_FORM_RADIO = 'BFormRadio'
|
|
@@ -6,7 +6,6 @@ import { looseEqual } from '../utils/loose-equal'
|
|
|
6
6
|
import { makeModelMixin } from '../utils/model'
|
|
7
7
|
import { omit, pick, sortKeys } from '../utils/object'
|
|
8
8
|
import { makeProp, makePropsConfigurable } from '../utils/props'
|
|
9
|
-
import { BFormCheckbox } from '../components/form-checkbox/form-checkbox'
|
|
10
9
|
import { BFormRadio } from '../components/form-radio/form-radio'
|
|
11
10
|
import { formControlMixin, props as formControlProps } from './form-control'
|
|
12
11
|
import { formCustomMixin, props as formCustomProps } from './form-custom'
|
|
@@ -99,7 +98,7 @@ export const formRadioCheckGroupMixin = extend({
|
|
|
99
98
|
render(h) {
|
|
100
99
|
const { isRadioGroup } = this
|
|
101
100
|
const attrs = pick(this.$attrs, PASS_DOWN_ATTRS)
|
|
102
|
-
const optionComponent =
|
|
101
|
+
const optionComponent = BFormRadio
|
|
103
102
|
|
|
104
103
|
const $inputs = this.formOptions.map((option, index) => {
|
|
105
104
|
const key = `BV_option_${index}`
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@import "../../utilities";
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
// Adds control sizing to Bootstrap custom checkbox/switch inputs
|
|
2
|
-
|
|
3
|
-
.custom-checkbox.b-custom-control-lg,
|
|
4
|
-
.input-group-lg .custom-checkbox {
|
|
5
|
-
font-size: $font-size-lg;
|
|
6
|
-
line-height: $line-height-lg;
|
|
7
|
-
padding-left: $b-custom-control-gutter-lg + $b-custom-control-indicator-size-lg;
|
|
8
|
-
|
|
9
|
-
.custom-control-label::before {
|
|
10
|
-
top: ($font-size-lg * $line-height-lg - $b-custom-control-indicator-size-lg) * 0.5;
|
|
11
|
-
left: -($b-custom-control-gutter-lg + $b-custom-control-indicator-size-lg);
|
|
12
|
-
width: $b-custom-control-indicator-size-lg;
|
|
13
|
-
height: $b-custom-control-indicator-size-lg;
|
|
14
|
-
@include border-radius($b-custom-checkbox-indicator-border-radius-lg);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.custom-control-label::after {
|
|
18
|
-
top: ($font-size-lg * $line-height-lg - $b-custom-control-indicator-size-lg) * 0.5;
|
|
19
|
-
left: -($b-custom-control-gutter-lg + $b-custom-control-indicator-size-lg);
|
|
20
|
-
width: $b-custom-control-indicator-size-lg;
|
|
21
|
-
height: $b-custom-control-indicator-size-lg;
|
|
22
|
-
background-size: $b-custom-control-indicator-bg-size-lg;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.custom-checkbox.b-custom-control-sm,
|
|
27
|
-
.input-group-sm .custom-checkbox {
|
|
28
|
-
font-size: $font-size-sm;
|
|
29
|
-
line-height: $line-height-sm;
|
|
30
|
-
padding-left: $b-custom-control-gutter-sm + $b-custom-control-indicator-size-sm;
|
|
31
|
-
|
|
32
|
-
.custom-control-label::before {
|
|
33
|
-
top: ($font-size-sm * $line-height-sm - $b-custom-control-indicator-size-sm) * 0.5;
|
|
34
|
-
left: -($b-custom-control-gutter-sm + $b-custom-control-indicator-size-sm);
|
|
35
|
-
width: $b-custom-control-indicator-size-sm;
|
|
36
|
-
height: $b-custom-control-indicator-size-sm;
|
|
37
|
-
@include border-radius($b-custom-checkbox-indicator-border-radius-sm);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.custom-control-label::after {
|
|
41
|
-
top: ($font-size-sm * $line-height-sm - $b-custom-control-indicator-size-sm) * 0.5;
|
|
42
|
-
left: -($b-custom-control-gutter-sm + $b-custom-control-indicator-size-sm);
|
|
43
|
-
width: $b-custom-control-indicator-size-sm;
|
|
44
|
-
height: $b-custom-control-indicator-size-sm;
|
|
45
|
-
background-size: $b-custom-control-indicator-bg-size-sm;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.custom-switch.b-custom-control-lg,
|
|
50
|
-
.input-group-lg .custom-switch {
|
|
51
|
-
padding-left: $b-custom-switch-width-lg + $b-custom-control-gutter-lg;
|
|
52
|
-
|
|
53
|
-
.custom-control-label {
|
|
54
|
-
font-size: $font-size-lg;
|
|
55
|
-
line-height: $line-height-lg;
|
|
56
|
-
|
|
57
|
-
&::before {
|
|
58
|
-
top: ($font-size-lg * $line-height-lg - $b-custom-control-indicator-size-lg) * 0.5;
|
|
59
|
-
height: $b-custom-control-indicator-size-lg;
|
|
60
|
-
left: -($b-custom-switch-width-lg + $b-custom-control-gutter-lg);
|
|
61
|
-
width: $b-custom-switch-width-lg;
|
|
62
|
-
border-radius: $b-custom-switch-indicator-border-radius-lg;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
&::after {
|
|
66
|
-
top: calc(
|
|
67
|
-
#{(($font-size-lg * $line-height-lg - $b-custom-control-indicator-size-lg) * 0.5)} + #{$custom-control-indicator-border-width *
|
|
68
|
-
2}
|
|
69
|
-
);
|
|
70
|
-
left: calc(
|
|
71
|
-
#{- ($b-custom-switch-width-lg + $b-custom-control-gutter-lg)} + #{$custom-control-indicator-border-width *
|
|
72
|
-
2}
|
|
73
|
-
);
|
|
74
|
-
width: $b-custom-switch-indicator-size-lg;
|
|
75
|
-
height: $b-custom-switch-indicator-size-lg;
|
|
76
|
-
border-radius: $b-custom-switch-indicator-border-radius-lg;
|
|
77
|
-
background-size: $b-custom-control-indicator-bg-size-lg;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.custom-control-input:checked ~ .custom-control-label {
|
|
82
|
-
&::after {
|
|
83
|
-
transform: translateX($b-custom-switch-width-lg - $b-custom-control-indicator-size-lg);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.custom-switch.b-custom-control-sm,
|
|
89
|
-
.input-group-sm .custom-switch {
|
|
90
|
-
padding-left: $b-custom-switch-width-sm + $b-custom-control-gutter-sm;
|
|
91
|
-
|
|
92
|
-
.custom-control-label {
|
|
93
|
-
font-size: $font-size-sm;
|
|
94
|
-
line-height: $line-height-sm;
|
|
95
|
-
|
|
96
|
-
&::before {
|
|
97
|
-
top: ($font-size-sm * $line-height-sm - $b-custom-control-indicator-size-sm) * 0.5;
|
|
98
|
-
left: -($b-custom-switch-width-sm + $b-custom-control-gutter-sm);
|
|
99
|
-
width: $b-custom-switch-width-sm;
|
|
100
|
-
height: $b-custom-control-indicator-size-sm;
|
|
101
|
-
border-radius: $b-custom-switch-indicator-border-radius-sm;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
&::after {
|
|
105
|
-
top: calc(
|
|
106
|
-
#{(($font-size-sm * $line-height-sm - $b-custom-control-indicator-size-sm) * 0.5)} + #{$custom-control-indicator-border-width *
|
|
107
|
-
2}
|
|
108
|
-
);
|
|
109
|
-
left: calc(
|
|
110
|
-
#{- ($b-custom-switch-width-sm + $b-custom-control-gutter-sm)} + #{$custom-control-indicator-border-width *
|
|
111
|
-
2}
|
|
112
|
-
);
|
|
113
|
-
width: $b-custom-switch-indicator-size-sm;
|
|
114
|
-
height: $b-custom-switch-indicator-size-sm;
|
|
115
|
-
border-radius: $b-custom-switch-indicator-border-radius-sm;
|
|
116
|
-
background-size: $b-custom-control-indicator-bg-size-sm;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.custom-control-input:checked ~ .custom-control-label {
|
|
121
|
-
&::after {
|
|
122
|
-
transform: translateX($b-custom-switch-width-sm - $b-custom-control-indicator-size-sm);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { extend } from '../../vue'
|
|
2
|
-
import { NAME_FORM_CHECKBOX_GROUP } from '../../constants/components'
|
|
3
|
-
import { PROP_TYPE_ARRAY, PROP_TYPE_BOOLEAN } from '../../constants/props'
|
|
4
|
-
import { sortKeys } from '../../utils/object'
|
|
5
|
-
import { makeProp, makePropsConfigurable } from '../../utils/props'
|
|
6
|
-
import {
|
|
7
|
-
MODEL_PROP_NAME,
|
|
8
|
-
formRadioCheckGroupMixin,
|
|
9
|
-
props as formRadioCheckGroupProps
|
|
10
|
-
} from '../../mixins/form-radio-check-group'
|
|
11
|
-
|
|
12
|
-
// --- Props ---
|
|
13
|
-
|
|
14
|
-
export const props = makePropsConfigurable(
|
|
15
|
-
sortKeys({
|
|
16
|
-
...formRadioCheckGroupProps,
|
|
17
|
-
[MODEL_PROP_NAME]: makeProp(PROP_TYPE_ARRAY, []),
|
|
18
|
-
// Custom switch styling
|
|
19
|
-
switches: makeProp(PROP_TYPE_BOOLEAN, false)
|
|
20
|
-
}),
|
|
21
|
-
NAME_FORM_CHECKBOX_GROUP
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
// --- Main component ---
|
|
25
|
-
|
|
26
|
-
// @vue/component
|
|
27
|
-
export const BFormCheckboxGroup = /*#__PURE__*/ extend({
|
|
28
|
-
name: NAME_FORM_CHECKBOX_GROUP,
|
|
29
|
-
// Includes render function
|
|
30
|
-
mixins: [formRadioCheckGroupMixin],
|
|
31
|
-
provide() {
|
|
32
|
-
return {
|
|
33
|
-
getBvCheckGroup: () => this
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
props,
|
|
37
|
-
computed: {
|
|
38
|
-
isRadioGroup() {
|
|
39
|
-
return false
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
})
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { extend } from '../../vue'
|
|
2
|
-
import { NAME_FORM_CHECKBOX } from '../../constants/components'
|
|
3
|
-
import { EVENT_NAME_CHANGE, MODEL_EVENT_NAME_PREFIX } from '../../constants/events'
|
|
4
|
-
import { PROP_TYPE_ANY, PROP_TYPE_BOOLEAN } from '../../constants/props'
|
|
5
|
-
import { isArray } from '../../utils/inspect'
|
|
6
|
-
import { looseEqual } from '../../utils/loose-equal'
|
|
7
|
-
import { looseIndexOf } from '../../utils/loose-index-of'
|
|
8
|
-
import { sortKeys } from '../../utils/object'
|
|
9
|
-
import { makeProp, makePropsConfigurable } from '../../utils/props'
|
|
10
|
-
import {
|
|
11
|
-
MODEL_EVENT_NAME,
|
|
12
|
-
formRadioCheckMixin,
|
|
13
|
-
props as formRadioCheckProps
|
|
14
|
-
} from '../../mixins/form-radio-check'
|
|
15
|
-
|
|
16
|
-
// --- Constants ---
|
|
17
|
-
|
|
18
|
-
const MODEL_PROP_NAME_INDETERMINATE = 'indeterminate'
|
|
19
|
-
const MODEL_EVENT_NAME_INDETERMINATE = MODEL_EVENT_NAME_PREFIX + MODEL_PROP_NAME_INDETERMINATE
|
|
20
|
-
|
|
21
|
-
// --- Props ---
|
|
22
|
-
|
|
23
|
-
export const props = makePropsConfigurable(
|
|
24
|
-
sortKeys({
|
|
25
|
-
...formRadioCheckProps,
|
|
26
|
-
// Not applicable in multi-check mode
|
|
27
|
-
[MODEL_PROP_NAME_INDETERMINATE]: makeProp(PROP_TYPE_BOOLEAN, false),
|
|
28
|
-
// Custom switch styling
|
|
29
|
-
switch: makeProp(PROP_TYPE_BOOLEAN, false),
|
|
30
|
-
// Not applicable in multi-check mode
|
|
31
|
-
uncheckedValue: makeProp(PROP_TYPE_ANY, false),
|
|
32
|
-
value: makeProp(PROP_TYPE_ANY, true)
|
|
33
|
-
}),
|
|
34
|
-
NAME_FORM_CHECKBOX
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
// --- Main component ---
|
|
38
|
-
|
|
39
|
-
// @vue/component
|
|
40
|
-
export const BFormCheckbox = /*#__PURE__*/ extend({
|
|
41
|
-
name: NAME_FORM_CHECKBOX,
|
|
42
|
-
mixins: [formRadioCheckMixin],
|
|
43
|
-
inject: {
|
|
44
|
-
getBvGroup: {
|
|
45
|
-
from: 'getBvCheckGroup',
|
|
46
|
-
default: () => () => null
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
props,
|
|
50
|
-
computed: {
|
|
51
|
-
bvGroup() {
|
|
52
|
-
return this.getBvGroup()
|
|
53
|
-
},
|
|
54
|
-
isChecked() {
|
|
55
|
-
const { value, computedLocalChecked: checked } = this
|
|
56
|
-
return isArray(checked) ? looseIndexOf(checked, value) > -1 : looseEqual(checked, value)
|
|
57
|
-
},
|
|
58
|
-
isRadio() {
|
|
59
|
-
return false
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
watch: {
|
|
63
|
-
[MODEL_PROP_NAME_INDETERMINATE](newValue, oldValue) {
|
|
64
|
-
if (!looseEqual(newValue, oldValue)) {
|
|
65
|
-
this.setIndeterminate(newValue)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
mounted() {
|
|
70
|
-
// Set initial indeterminate state
|
|
71
|
-
this.setIndeterminate(this[MODEL_PROP_NAME_INDETERMINATE])
|
|
72
|
-
},
|
|
73
|
-
methods: {
|
|
74
|
-
computedLocalCheckedWatcher(newValue, oldValue) {
|
|
75
|
-
if (!looseEqual(newValue, oldValue)) {
|
|
76
|
-
this.$emit(MODEL_EVENT_NAME, newValue)
|
|
77
|
-
|
|
78
|
-
const $input = this.$refs.input
|
|
79
|
-
if ($input) {
|
|
80
|
-
this.$emit(MODEL_EVENT_NAME_INDETERMINATE, $input.indeterminate)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
handleChange({ target: { checked, indeterminate } }) {
|
|
86
|
-
const { value, uncheckedValue } = this
|
|
87
|
-
|
|
88
|
-
// Update `computedLocalChecked`
|
|
89
|
-
let localChecked = this.computedLocalChecked
|
|
90
|
-
if (isArray(localChecked)) {
|
|
91
|
-
const index = looseIndexOf(localChecked, value)
|
|
92
|
-
if (checked && index < 0) {
|
|
93
|
-
// Add value to array
|
|
94
|
-
localChecked = localChecked.concat(value)
|
|
95
|
-
} else if (!checked && index > -1) {
|
|
96
|
-
// Remove value from array
|
|
97
|
-
localChecked = localChecked.slice(0, index).concat(localChecked.slice(index + 1))
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
localChecked = checked ? value : uncheckedValue
|
|
101
|
-
}
|
|
102
|
-
this.computedLocalChecked = localChecked
|
|
103
|
-
|
|
104
|
-
// Fire events in a `$nextTick()` to ensure the `v-model` is updated
|
|
105
|
-
this.$nextTick(() => {
|
|
106
|
-
// Change is only emitted on user interaction
|
|
107
|
-
this.$emit(EVENT_NAME_CHANGE, localChecked)
|
|
108
|
-
|
|
109
|
-
// If this is a child of a group, we emit a change event on it as well
|
|
110
|
-
if (this.isGroup) {
|
|
111
|
-
this.bvGroup.$emit(EVENT_NAME_CHANGE, localChecked)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
this.$emit(MODEL_EVENT_NAME_INDETERMINATE, indeterminate)
|
|
115
|
-
})
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
setIndeterminate(state) {
|
|
119
|
-
// Indeterminate only supported in single checkbox mode
|
|
120
|
-
if (isArray(this.computedLocalChecked)) {
|
|
121
|
-
state = false
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const $input = this.$refs.input
|
|
125
|
-
if ($input) {
|
|
126
|
-
$input.indeterminate = state
|
|
127
|
-
// Emit update event to prop
|
|
128
|
-
this.$emit(MODEL_EVENT_NAME_INDETERMINATE, state)
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
})
|