diff --git a/src/components/form-checkbox/form-checkbox-group.js b/src/components/form-checkbox/form-checkbox-group.js index 497fe2bdd00..618153d0bf0 100644 --- a/src/components/form-checkbox/form-checkbox-group.js +++ b/src/components/form-checkbox/form-checkbox-group.js @@ -1,60 +1,41 @@ import Vue from '../../vue' import { NAME_FORM_CHECKBOX_GROUP } from '../../constants/components' import { makePropsConfigurable } from '../../utils/config' -import formControlMixin, { props as formControlProps } from '../../mixins/form-control' -import formOptionsMixin, { props as formOptionsProps } from '../../mixins/form-options' import formRadioCheckGroupMixin, { props as formRadioCheckGroupProps } from '../../mixins/form-radio-check-group' -import formSizeMixin, { props as formSizeProps } from '../../mixins/form-size' -import formStateMixin, { props as formStateProps } from '../../mixins/form-state' -import idMixin from '../../mixins/id' // --- Props --- export const props = makePropsConfigurable( { - ...formControlProps, - ...formOptionsProps, ...formRadioCheckGroupProps, - ...formSizeProps, - ...formStateProps, + checked: { + type: Array, + default: () => [] + }, switches: { // Custom switch styling type: Boolean, default: false - }, - checked: { - type: Array, - default: null } }, NAME_FORM_CHECKBOX_GROUP ) // --- Main component --- + // @vue/component export const BFormCheckboxGroup = /*#__PURE__*/ Vue.extend({ name: NAME_FORM_CHECKBOX_GROUP, - mixins: [ - idMixin, - formControlMixin, - formRadioCheckGroupMixin, // Includes render function - formOptionsMixin, - formSizeMixin, - formStateMixin - ], + // Includes render function + mixins: [formRadioCheckGroupMixin], provide() { return { bvCheckGroup: this } }, props, - data() { - return { - localChecked: this.checked || [] - } - }, computed: { isRadioGroup() { return false diff --git a/src/components/form-checkbox/form-checkbox-group.spec.js b/src/components/form-checkbox/form-checkbox-group.spec.js index 7fe14f45f68..e0efa5c8d87 100644 --- a/src/components/form-checkbox/form-checkbox-group.spec.js +++ b/src/components/form-checkbox/form-checkbox-group.spec.js @@ -311,11 +311,11 @@ describe('form-checkbox-group', () => { } }) - expect(wrapper.classes()).toBeDefined() + expect(wrapper.vm.isRadioGroup).toEqual(false) + expect(wrapper.vm.localChecked).toEqual([]) const $inputs = wrapper.findAll('input') expect($inputs.length).toBe(3) - expect(wrapper.vm.localChecked).toEqual([]) expect($inputs.wrappers.every(c => c.find('input[type=checkbox]').exists())).toBe(true) wrapper.destroy() diff --git a/src/components/form-group/form-group.js b/src/components/form-group/form-group.js index 3b32861ca99..ed30b6c7a0f 100644 --- a/src/components/form-group/form-group.js +++ b/src/components/form-group/form-group.js @@ -419,7 +419,7 @@ export const BFormGroup = { id: this.safeId(), disabled: isFieldset ? this.disabled : null, role: isFieldset ? null : 'group', - 'aria-invalid': state === false ? 'true' : null, + 'aria-invalid': this.computedAriaInvalid, // Only apply aria-labelledby if we are a horizontal fieldset // as the legend is no longer a direct child of fieldset 'aria-labelledby': isFieldset && isHorizontal ? labelId : null, diff --git a/src/components/form-radio/form-radio-group.js b/src/components/form-radio/form-radio-group.js index 8351271589e..df2a553991d 100644 --- a/src/components/form-radio/form-radio-group.js +++ b/src/components/form-radio/form-radio-group.js @@ -1,55 +1,26 @@ import Vue from '../../vue' import { NAME_FORM_RADIO_GROUP } from '../../constants/components' import { makePropsConfigurable } from '../../utils/config' -import idMixin from '../../mixins/id' -import formControlMixin, { props as formControlProps } from '../../mixins/form-control' -import formOptionsMixin, { props as formOptionsProps } from '../../mixins/form-options' import formRadioCheckGroupMixin, { props as formRadioCheckGroupProps } from '../../mixins/form-radio-check-group' -import formSizeMixin, { props as formSizeProps } from '../../mixins/form-size' -import formStateMixin, { props as formStateProps } from '../../mixins/form-state' // --- Props --- -export const props = makePropsConfigurable( - { - ...formControlProps, - ...formOptionsProps, - ...formRadioCheckGroupProps, - ...formSizeProps, - ...formStateProps, - checked: { - // type: [String, Number, Boolean, Object], - default: null - } - }, - NAME_FORM_RADIO_GROUP -) +export const props = makePropsConfigurable(formRadioCheckGroupProps, NAME_FORM_RADIO_GROUP) // --- Main component --- + // @vue/component export const BFormRadioGroup = /*#__PURE__*/ Vue.extend({ name: NAME_FORM_RADIO_GROUP, - mixins: [ - idMixin, - formControlMixin, - formRadioCheckGroupMixin, // Includes render function - formOptionsMixin, - formSizeMixin, - formStateMixin - ], + mixins: [formRadioCheckGroupMixin], provide() { return { bvRadioGroup: this } }, props, - data() { - return { - localChecked: this.checked - } - }, computed: { isRadioGroup() { return true diff --git a/src/components/form-radio/form-radio-group.spec.js b/src/components/form-radio/form-radio-group.spec.js index 1b504ea752e..c9bbf5474dc 100644 --- a/src/components/form-radio/form-radio-group.spec.js +++ b/src/components/form-radio/form-radio-group.spec.js @@ -288,10 +288,12 @@ describe('form-radio-group', () => { checked: '' } }) - expect(wrapper.classes()).toBeDefined() + + expect(wrapper.vm.isRadioGroup).toEqual(true) + expect(wrapper.vm.localChecked).toEqual('') + const radios = wrapper.findAll('input') expect(radios.length).toBe(3) - expect(wrapper.vm.localChecked).toEqual('') expect(radios.wrappers.every(c => c.find('input[type=radio]').exists())).toBe(true) wrapper.destroy() diff --git a/src/components/form-select/form-select.js b/src/components/form-select/form-select.js index 93574a50926..9a6358752cc 100644 --- a/src/components/form-select/form-select.js +++ b/src/components/form-select/form-select.js @@ -77,12 +77,6 @@ export const BFormSelect = /*#__PURE__*/ Vue.extend({ this.size && !this.plain ? `custom-select-${this.size}` : null, this.stateClass ] - }, - computedAriaInvalid() { - if (this.ariaInvalid === true || this.ariaInvalid === 'true') { - return 'true' - } - return this.stateClass === 'is-invalid' ? 'true' : null } }, watch: { diff --git a/src/mixins/form-radio-check-group.js b/src/mixins/form-radio-check-group.js index 09ca16e79e0..ed8aea45472 100644 --- a/src/mixins/form-radio-check-group.js +++ b/src/mixins/form-radio-check-group.js @@ -4,14 +4,27 @@ import { makePropsConfigurable } from '../utils/config' import { htmlOrText } from '../utils/html' import { BFormCheckbox } from '../components/form-checkbox/form-checkbox' import { BFormRadio } from '../components/form-radio/form-radio' +import formControlMixin, { props as formControlProps } from './form-control' import formCustomMixin, { props as formCustomProps } from './form-custom' +import formOptionsMixin, { props as formOptionsProps } from './form-options' +import formSizeMixin, { props as formSizeProps } from './form-size' +import formStateMixin, { props as formStateProps } from './form-state' +import idMixin from './id' import normalizeSlotMixin from './normalize-slot' // --- Props --- export const props = makePropsConfigurable( { + ...formControlProps, + ...formOptionsProps, + ...formSizeProps, + ...formStateProps, ...formCustomProps, + checked: { + // type: [Boolean, Number, Object, String] + default: null + }, validated: { type: Boolean, default: false @@ -39,14 +52,28 @@ export const props = makePropsConfigurable( ) // --- Mixin --- + // @vue/component export default { - mixins: [formCustomMixin, normalizeSlotMixin], + mixins: [ + idMixin, + normalizeSlotMixin, + formControlMixin, + formOptionsMixin, + formSizeMixin, + formStateMixin, + formCustomMixin + ], model: { prop: 'checked', event: 'input' }, props, + data() { + return { + localChecked: this.checked + } + }, computed: { inline() { return !this.stacked @@ -57,28 +84,28 @@ export default { return this.name || this.safeId() }, groupClasses() { + const { inline, size, validated } = this + + let classes = { 'was-validated': validated } if (this.buttons) { - return [ + classes = [ + classes, 'btn-group-toggle', - this.inline ? 'btn-group' : 'btn-group-vertical', - this.size ? `btn-group-${this.size}` : '', - this.validated ? `was-validated` : '' + { + 'btn-group': inline, + 'btn-group-vertical': !inline, + [`btn-group-${size}`]: !!size + } ] } - return [this.validated ? `was-validated` : ''] - }, - computedAriaInvalid() { - const ariaInvalid = this.ariaInvalid - if (ariaInvalid === true || ariaInvalid === 'true' || ariaInvalid === '') { - return 'true' - } - return this.computedState === false ? 'true' : null + + return classes } }, watch: { - checked(newVal) { - if (!looseEqual(newVal, this.localChecked)) { - this.localChecked = newVal + checked(newValue) { + if (!looseEqual(newValue, this.localChecked)) { + this.localChecked = newValue } }, localChecked(newValue, oldValue) { @@ -88,11 +115,14 @@ export default { } }, render(h) { + const { isRadioGroup } = this + const optionComponent = isRadioGroup ? BFormRadio : BFormCheckbox + const $inputs = this.formOptions.map((option, index) => { const key = `BV_option_${index}` return h( - this.isRadioGroup ? BFormRadio : BFormCheckbox, + optionComponent, { props: { id: this.safeId(key), @@ -116,7 +146,7 @@ export default { class: [this.groupClasses, 'bv-no-focus-ring'], attrs: { id: this.safeId(), - role: this.isRadioGroup ? 'radiogroup' : 'group', + role: isRadioGroup ? 'radiogroup' : 'group', // Add `tabindex="-1"` to allow group to be focused if needed by screen readers tabindex: '-1', 'aria-required': this.required ? 'true' : null, diff --git a/src/mixins/form-state.js b/src/mixins/form-state.js index 58d13dcdd84..b0cb15594b4 100644 --- a/src/mixins/form-state.js +++ b/src/mixins/form-state.js @@ -34,6 +34,13 @@ export default { stateClass() { const state = this.computedState return state === true ? 'is-valid' : state === false ? 'is-invalid' : null + }, + computedAriaInvalid() { + const { ariaInvalid } = this + if (ariaInvalid === true || ariaInvalid === 'true' || ariaInvalid === '') { + return 'true' + } + return this.computedState === false ? 'true' : ariaInvalid } } } diff --git a/src/mixins/form-text.js b/src/mixins/form-text.js index 6a24f943084..39f3c4e9bb1 100644 --- a/src/mixins/form-text.js +++ b/src/mixins/form-text.js @@ -95,18 +95,6 @@ export default { this.stateClass ] }, - computedAriaInvalid() { - if (!this.ariaInvalid || this.ariaInvalid === 'false') { - // `this.ariaInvalid` is `null` or `false` or 'false' - return this.computedState === false ? 'true' : null - } - if (this.ariaInvalid === true) { - // User wants explicit `:aria-invalid="true"` - return 'true' - } - // Most likely a string value (which could be the string 'true') - return this.ariaInvalid - }, computedDebounce() { // Ensure we have a positive number equal to or greater than 0 return mathMax(toInteger(this.debounce, 0), 0)