UNPKG

bootstrap-vue

Version:

With more than 85 components, over 45 available plugins, several directives, and 1000+ icons, BootstrapVue provides one of the most comprehensive implementations of the Bootstrap v4 component and grid system available for Vue.js v2.6, complete with extens

333 lines (314 loc) 9.78 kB
import { extend } from '../vue' import { PROP_TYPE_ANY, PROP_TYPE_BOOLEAN, PROP_TYPE_STRING } from '../constants/props' import { EVENT_NAME_CHANGE } from '../constants/events' import { attemptBlur, attemptFocus } from '../utils/dom' import { isBoolean } from '../utils/inspect' import { looseEqual } from '../utils/loose-equal' import { makeModelMixin } from '../utils/model' import { sortKeys } from '../utils/object' import { makeProp, makePropsConfigurable } from '../utils/props' import { attrsMixin } from './attrs' import { formControlMixin, props as formControlProps } from './form-control' import { formCustomMixin, props as formCustomProps } from './form-custom' import { formSizeMixin, props as formSizeProps } from './form-size' import { formStateMixin, props as formStateProps } from './form-state' import { idMixin, props as idProps } from './id' import { normalizeSlotMixin } from './normalize-slot' // --- Constants --- const { mixin: modelMixin, props: modelProps, prop: MODEL_PROP_NAME, event: MODEL_EVENT_NAME } = makeModelMixin('checked', { defaultValue: null }) export { MODEL_PROP_NAME, MODEL_EVENT_NAME } // --- Props --- export const props = makePropsConfigurable( sortKeys({ ...idProps, ...modelProps, ...formControlProps, ...formSizeProps, ...formStateProps, ...formCustomProps, ariaLabel: makeProp(PROP_TYPE_STRING), ariaLabelledby: makeProp(PROP_TYPE_STRING), // Only applicable in standalone mode (non group) button: makeProp(PROP_TYPE_BOOLEAN, false), // Only applicable when rendered with button style buttonVariant: makeProp(PROP_TYPE_STRING), inline: makeProp(PROP_TYPE_BOOLEAN, false), value: makeProp(PROP_TYPE_ANY) }), 'formRadioCheckControls' ) // --- Mixin --- // @vue/component export const formRadioCheckMixin = extend({ mixins: [ attrsMixin, idMixin, modelMixin, normalizeSlotMixin, formControlMixin, formSizeMixin, formStateMixin, formCustomMixin ], inheritAttrs: false, props, data() { return { localChecked: this.isGroup ? this.bvGroup[MODEL_PROP_NAME] : this[MODEL_PROP_NAME], hasFocus: false } }, computed: { computedLocalChecked: { get() { return this.isGroup ? this.bvGroup.localChecked : this.localChecked }, set(value) { if (this.isGroup) { this.bvGroup.localChecked = value } else { this.localChecked = value } } }, isChecked() { return looseEqual(this.value, this.computedLocalChecked) }, isRadio() { return true }, isGroup() { // Is this check/radio a child of check-group or radio-group? return !!this.bvGroup }, isBtnMode() { // Support button style in single input mode return this.isGroup ? this.bvGroup.buttons : this.button }, isPlain() { return this.isBtnMode ? false : this.isGroup ? this.bvGroup.plain : this.plain }, isCustom() { return this.isBtnMode ? false : !this.isPlain }, isSwitch() { // Custom switch styling (checkboxes only) return this.isBtnMode || this.isRadio || this.isPlain ? false : this.isGroup ? this.bvGroup.switches : this.switch }, isInline() { return this.isGroup ? this.bvGroup.inline : this.inline }, isDisabled() { // Child can be disabled while parent isn't, but is always disabled if group is return this.isGroup ? this.bvGroup.disabled || this.disabled : this.disabled }, isRequired() { // Required only works when a name is provided for the input(s) // Child can only be required when parent is // Groups will always have a name (either user supplied or auto generated) return this.computedName && (this.isGroup ? this.bvGroup.required : this.required) }, computedName() { // Group name preferred over local name return (this.isGroup ? this.bvGroup.groupName : this.name) || null }, computedForm() { return (this.isGroup ? this.bvGroup.form : this.form) || null }, computedSize() { return (this.isGroup ? this.bvGroup.size : this.size) || '' }, computedState() { return this.isGroup ? this.bvGroup.computedState : isBoolean(this.state) ? this.state : null }, computedButtonVariant() { // Local variant preferred over group variant const { buttonVariant } = this if (buttonVariant) { return buttonVariant } if (this.isGroup && this.bvGroup.buttonVariant) { return this.bvGroup.buttonVariant } return 'secondary' }, buttonClasses() { const { computedSize } = this return [ 'btn', `btn-${this.computedButtonVariant}`, { [`btn-${computedSize}`]: computedSize, // 'disabled' class makes "button" look disabled disabled: this.isDisabled, // 'active' class makes "button" look pressed active: this.isChecked, // Focus class makes button look focused focus: this.hasFocus } ] }, computedAttrs() { const { isDisabled: disabled, isRequired: required } = this return { ...this.bvAttrs, id: this.safeId(), type: this.isRadio ? 'radio' : 'checkbox', name: this.computedName, form: this.computedForm, disabled, required, 'aria-required': required || null, 'aria-label': this.ariaLabel || null, 'aria-labelledby': this.ariaLabelledby || null } } }, watch: { [MODEL_PROP_NAME](...args) { this[`${MODEL_PROP_NAME}Watcher`](...args) }, computedLocalChecked(...args) { this.computedLocalCheckedWatcher(...args) } }, methods: { [`${MODEL_PROP_NAME}Watcher`](newValue) { if (!looseEqual(newValue, this.computedLocalChecked)) { this.computedLocalChecked = newValue } }, computedLocalCheckedWatcher(newValue, oldValue) { if (!looseEqual(newValue, oldValue)) { this.$emit(MODEL_EVENT_NAME, newValue) } }, handleChange({ target: { checked } }) { const { value } = this const localChecked = checked ? value : null this.computedLocalChecked = value // Fire events in a `$nextTick()` to ensure the `v-model` is updated this.$nextTick(() => { // Change is only emitted on user interaction this.$emit(EVENT_NAME_CHANGE, localChecked) // If this is a child of a group, we emit a change event on it as well if (this.isGroup) { this.bvGroup.$emit(EVENT_NAME_CHANGE, localChecked) } }) }, handleFocus(event) { // When in buttons mode, we need to add 'focus' class to label when input focused // As it is the hidden input which has actual focus if (event.target) { if (event.type === 'focus') { this.hasFocus = true } else if (event.type === 'blur') { this.hasFocus = false } } }, // Convenience methods for focusing the input focus() { if (!this.isDisabled) { attemptFocus(this.$refs.input) } }, blur() { if (!this.isDisabled) { attemptBlur(this.$refs.input) } } }, render(h) { const { isRadio, isBtnMode, isPlain, isCustom, isInline, isSwitch, computedSize, bvAttrs } = this const $content = this.normalizeSlot() const $input = h('input', { class: [ { 'form-check-input': isPlain, 'custom-control-input': isCustom, // https://github.com/bootstrap-vue/bootstrap-vue/issues/2911 'position-static': isPlain && !$content }, isBtnMode ? '' : this.stateClass ], directives: [{ name: 'model', value: this.computedLocalChecked }], attrs: this.computedAttrs, domProps: { value: this.value, checked: this.isChecked }, on: { change: this.handleChange, ...(isBtnMode ? { focus: this.handleFocus, blur: this.handleFocus } : {}) }, key: 'input', ref: 'input' }) if (isBtnMode) { let $button = h('label', { class: this.buttonClasses }, [$input, $content]) if (!this.isGroup) { // Standalone button mode, so wrap in 'btn-group-toggle' // and flag it as inline-block to mimic regular buttons $button = h('div', { class: ['btn-group-toggle', 'd-inline-block'] }, [$button]) } return $button } // If no label content in plain mode we dont render the label // See: https://github.com/bootstrap-vue/bootstrap-vue/issues/2911 let $label = h() if (!(isPlain && !$content)) { $label = h( 'label', { class: { 'form-check-label': isPlain, 'custom-control-label': isCustom }, attrs: { for: this.safeId() } }, $content ) } return h( 'div', { class: [ { 'form-check': isPlain, 'form-check-inline': isPlain && isInline, 'custom-control': isCustom, 'custom-control-inline': isCustom && isInline, 'custom-checkbox': isCustom && !isRadio && !isSwitch, 'custom-switch': isSwitch, 'custom-radio': isCustom && isRadio, // Temporary until Bootstrap v4 supports sizing (most likely in V5) [`b-custom-control-${computedSize}`]: computedSize && !isBtnMode }, bvAttrs.class ], style: bvAttrs.style }, [$input, $label] ) } })