bootstrap-vue
Version:
BootstrapVue, with over 40 plugins and more than 80 custom components, 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 WAI-AR
160 lines (143 loc) • 5.23 kB
JavaScript
import { mergeData } from 'vue-functional-data-merge'
import identity from '../../utils/identity'
import memoize from '../../utils/memoize'
import suffixPropName from '../../utils/suffix-prop-name'
import { arrayIncludes } from '../../utils/array'
import { getBreakpointsUpCached } from '../../utils/config'
import { isUndefinedOrNull } from '../../utils/inspect'
import { assign, create, keys } from '../../utils/object'
import { lowerCase } from '../../utils/string'
const RX_COL_CLASS = /^col-/
// Generates a prop object with a type of `[Boolean, String, Number]`
const boolStrNum = () => ({
type: [Boolean, String, Number],
default: false
})
// Generates a prop object with a type of `[String, Number]`
const strNum = () => ({
type: [String, Number],
default: null
})
// Compute a breakpoint class name
const computeBreakpoint = (type, breakpoint, val) => {
let className = type
if (isUndefinedOrNull(val) || val === false) {
return undefined
}
if (breakpoint) {
className += `-${breakpoint}`
}
// Handling the boolean style prop when accepting [Boolean, String, Number]
// means Vue will not convert <b-col sm></b-col> to sm: true for us.
// Since the default is false, an empty string indicates the prop's presence.
if (type === 'col' && (val === '' || val === true)) {
// .col-md
return lowerCase(className)
}
// .order-md-6
className += `-${val}`
return lowerCase(className)
}
// Memoized function for better performance on generating class names
const computeBreakpointClass = memoize(computeBreakpoint)
// Cached copy of the breakpoint prop names
let breakpointPropMap = create(null)
// Lazy evaled props factory for BCol
const generateProps = () => {
// Grab the breakpoints from the cached config (exclude the '' (xs) breakpoint)
const breakpoints = getBreakpointsUpCached().filter(identity)
// Supports classes like: .col-sm, .col-md-6, .col-lg-auto
const breakpointCol = breakpoints.reduce((propMap, breakpoint) => {
if (breakpoint) {
// We filter out the '' breakpoint (xs), as making a prop name ''
// would not work. The `cols` prop is used for `xs`
propMap[breakpoint] = boolStrNum()
}
return propMap
}, create(null))
// Supports classes like: .offset-md-1, .offset-lg-12
const breakpointOffset = breakpoints.reduce((propMap, breakpoint) => {
propMap[suffixPropName(breakpoint, 'offset')] = strNum()
return propMap
}, create(null))
// Supports classes like: .order-md-1, .order-lg-12
const breakpointOrder = breakpoints.reduce((propMap, breakpoint) => {
propMap[suffixPropName(breakpoint, 'order')] = strNum()
return propMap
}, create(null))
// For loop doesn't need to check hasOwnProperty
// when using an object created from null
breakpointPropMap = assign(create(null), {
col: keys(breakpointCol),
offset: keys(breakpointOffset),
order: keys(breakpointOrder)
})
// Return the generated props
return {
// Generic flexbox .col (xs)
col: {
type: Boolean,
default: false
},
// .col-[1-12]|auto (xs)
cols: strNum(),
// Breakpoint Specific props
...breakpointCol,
offset: strNum(),
...breakpointOffset,
order: strNum(),
...breakpointOrder,
// Flex alignment
alignSelf: {
type: String,
default: null,
validator: str =>
arrayIncludes(['auto', 'start', 'end', 'center', 'baseline', 'stretch'], str)
},
tag: {
type: String,
default: 'div'
}
}
}
// We do not use Vue.extend here as that would evaluate the props
// immediately, which we do not want to happen
// @vue/component
export const BCol = {
name: 'BCol',
functional: true,
get props() {
// Allow props to be lazy evaled on first access and
// then they become a non-getter afterwards.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#Smart_self-overwriting_lazy_getters
delete this.props
// eslint-disable-next-line no-return-assign
return (this.props = generateProps())
},
render(h, { props, data, children }) {
const classList = []
// Loop through `col`, `offset`, `order` breakpoint props
for (const type in breakpointPropMap) {
// Returns colSm, offset, offsetSm, orderMd, etc.
const keys = breakpointPropMap[type]
for (let i = 0; i < keys.length; i++) {
// computeBreakpoint(col, colSm => Sm, value=[String, Number, Boolean])
const c = computeBreakpointClass(type, keys[i].replace(type, ''), props[keys[i]])
// If a class is returned, push it onto the array.
if (c) {
classList.push(c)
}
}
}
const hasColClasses = classList.some(className => RX_COL_CLASS.test(className))
classList.push({
// Default to .col if no other col-{bp}-* classes generated nor `cols` specified.
col: props.col || (!hasColClasses && !props.cols),
[`col-${props.cols}`]: props.cols,
[`offset-${props.offset}`]: props.offset,
[`order-${props.order}`]: props.order,
[`align-self-${props.alignSelf}`]: props.alignSelf
})
return h(props.tag, mergeData(data, { class: classList }), children)
}
}