bootstrap-vue
Version:
BootstrapVue, with more than 85 custom components, over 45 plugins, several custom directives, and over 300 icons, provides one of the most comprehensive implementations of Bootstrap v4 components and grid system for Vue.js. With extensive and automated W
319 lines (311 loc) • 8.61 kB
JavaScript
//
// Private component used by `b-form-datepicker` and `b-form-timepicker`
//
import Vue from './vue'
import { toString } from './string'
import dropdownMixin, { commonProps } from '../mixins/dropdown'
import idMixin from '../mixins/id'
import normalizeSlotMixin from '../mixins/normalize-slot'
import { VBHover } from '../directives/hover/hover'
import { BIconChevronDown } from '../icons/icons'
// Re-export common dropdown props used for convenience
export const dropdownProps = commonProps
// @vue/component
export const BVFormBtnLabelControl = /*#__PURE__*/ Vue.extend({
name: 'BVFormBtnLabelControl',
directives: {
BHover: VBHover
},
mixins: [idMixin, normalizeSlotMixin, dropdownMixin],
props: {
value: {
// This is the value placed on the hidden input
type: String,
default: ''
},
formattedValue: {
// This is the value shown in the label
// Defaults back to `value`
type: String
// default: null
},
placeholder: {
// This is the value placed on the hidden input when no value selected
type: String
// default: null
},
labelSelected: {
// Value placed in sr-only span inside label when value is present
type: String
// default: null
},
state: {
// Tri-state prop: `true`, `false`, or `null`
type: Boolean,
// We must explicitly default to `null` here otherwise
// Vue coerces `undefined` into Boolean `false`
default: null
},
size: {
type: String
// default: null
},
name: {
type: String
// default: null
},
form: {
type: String
// default: null
},
disabled: {
type: Boolean,
default: false
},
readonly: {
type: Boolean,
default: false
},
required: {
type: Boolean,
default: false
},
lang: {
type: String
// default: null
},
rtl: {
// Tri-state prop: `true`, `false` or `null`
type: Boolean,
// We must explicitly default to `null` here otherwise
// Vue coerces `undefined` into Boolean `false`
default: null
},
buttonOnly: {
// When true, renders a btn-group wrapper and visually hides the label
type: Boolean,
default: false
},
buttonVariant: {
// Applicable in button mode only
type: String,
default: 'secondary'
},
menuClass: {
// Extra classes to apply to the `dropdown-menu` div
type: [String, Array, Object]
// default: null
}
},
data() {
return {
isHovered: false,
hasFocus: false
}
},
computed: {
idButton() {
return this.safeId()
},
idLabel() {
return this.safeId('_value_')
},
idMenu() {
return this.safeId('_dialog_')
},
idWrapper() {
return this.safeId('_outer_')
},
computedDir() {
return this.rtl === true ? 'rtl' : this.rtl === false ? 'ltr' : null
}
},
methods: {
focus() {
if (!this.disabled) {
try {
this.$refs.toggle.focus()
} catch {}
}
},
blur() {
if (!this.disabled) {
try {
this.$refs.toggle.blur()
} catch {}
}
},
setFocus(evt) {
this.hasFocus = evt.type === 'focus'
},
handleHover(hovered) {
this.isHovered = hovered
},
/* istanbul ignore next */
stopEvent(evt) /* istanbul ignore next */ {
evt.stopPropagation()
}
},
render(h) {
const idButton = this.idButton
const idLabel = this.idLabel
const idMenu = this.idMenu
const idWrapper = this.idWrapper
const disabled = this.disabled
const readonly = this.readonly
const required = this.required
const isHovered = this.isHovered
const hasFocus = this.hasFocus
const state = this.state
const visible = this.visible
const size = this.size
const value = toString(this.value) || ''
const labelSelected = this.labelSelected
const buttonOnly = !!this.buttonOnly
const buttonVariant = this.buttonVariant
const btnScope = { isHovered, hasFocus, state, opened: visible }
const $button = h(
'button',
{
ref: 'toggle',
staticClass: 'btn',
class: {
[`btn-${buttonVariant}`]: buttonOnly,
[`btn-${size}`]: !!size,
'h-auto': !buttonOnly,
// `dropdown-toggle` is needed for proper
// corner rounding in button only mode
'dropdown-toggle': buttonOnly,
'dropdown-toggle-no-caret': buttonOnly
},
attrs: {
id: idButton,
type: 'button',
disabled: disabled,
'aria-haspopup': 'dialog',
'aria-expanded': visible ? 'true' : 'false',
'aria-invalid': state === false || (required && !value) ? 'true' : null,
'aria-required': required ? 'true' : null
},
directives: [{ name: 'b-hover', value: this.handleHover }],
on: {
mousedown: this.onMousedown,
click: this.toggle,
keydown: this.toggle, // Handle ENTER, SPACE and DOWN
'!focus': this.setFocus,
'!blur': this.setFocus
}
},
[
this.hasNormalizedSlot('button-content')
? this.normalizeSlot('button-content', btnScope)
: /* istanbul ignore next */ h(BIconChevronDown, { props: { scale: 1.25 } })
]
)
// Hidden input
let $hidden = h()
if (this.name && !disabled) {
$hidden = h('input', {
attrs: {
type: 'hidden',
name: this.name || null,
form: this.form || null,
value: value
}
})
}
// Dropdown content
const $menu = h(
'div',
{
ref: 'menu',
staticClass: 'dropdown-menu',
class: [
this.menuClass,
{
show: visible,
'dropdown-menu-right': this.right
}
],
attrs: {
id: idMenu,
role: 'dialog',
tabindex: '-1',
'aria-modal': 'false',
'aria-labelledby': idLabel
},
on: {
keydown: this.onKeydown // Handle ESC
}
},
[this.normalizeSlot('default', { opened: visible })]
)
// Value label
const $label = h(
'label',
{
staticClass: 'form-control text-break text-wrap bg-transparent h-auto',
class: {
// Hidden in button only mode
'sr-only': buttonOnly,
// Mute the text if showing the placeholder
'text-muted': !value,
[`form-control-${size}`]: !!size,
'is-invalid': state === false,
'is-valid': state === true
},
attrs: {
id: idLabel,
for: idButton,
'aria-invalid': state === false || (required && !value) ? 'true' : null,
'aria-required': required ? 'true' : null
},
directives: [{ name: 'b-hover', value: this.handleHover }],
on: {
// Disable bubbling of the click event to
// prevent menu from closing and re-opening
'!click': this.stopEvent
}
},
[
value ? this.formattedValue || value : this.placeholder || '',
// Add the selected label for screen readers when a value is provided
value && labelSelected ? h('bdi', { staticClass: 'sr-only' }, labelSelected) : ''
]
)
// Return the custom form control wrapper
return h(
'div',
{
staticClass: 'b-form-btn-label-control dropdown',
class: [
this.directionClass,
{
'btn-group': buttonOnly,
'form-control': !buttonOnly,
[`form-control-${size}`]: !!size && !buttonOnly,
'd-flex': !buttonOnly,
'h-auto': !buttonOnly,
'align-items-stretch': !buttonOnly,
focus: hasFocus && !buttonOnly,
show: visible,
'is-valid': state === true,
'is-invalid': state === false
}
],
attrs: {
id: idWrapper,
role: buttonOnly ? null : 'group',
lang: this.lang || null,
dir: this.computedDir,
'aria-disabled': disabled,
'aria-readonly': readonly && !disabled,
'aria-labelledby': idLabel,
'aria-invalid': state === false || (required && !value) ? 'true' : null,
'aria-required': required ? 'true' : null
}
},
[$button, $hidden, $menu, $label]
)
}
})