vuetify
Version:
Vue.js 2 Semantic Component Framework
169 lines (148 loc) • 4.32 kB
text/typescript
// Styles
import '../../stylus/components/_buttons.styl'
// Types
import { VNode, VNodeChildren } from 'vue'
import { PropValidator } from 'vue/types/options'
import mixins from '../../util/mixins'
import { RippleOptions } from '../../directives/ripple'
// Components
import VProgressCircular from '../VProgressCircular'
// Mixins
import Colorable from '../../mixins/colorable'
import { factory as GroupableFactory } from '../../mixins/groupable'
import Positionable from '../../mixins/positionable'
import Routable from '../../mixins/routable'
import Themeable from '../../mixins/themeable'
import { factory as ToggleableFactory } from '../../mixins/toggleable'
// Utilities
import { getObjectValueByPath } from '../../util/helpers'
export default mixins(
Colorable,
Routable,
Positionable,
Themeable,
GroupableFactory('btnToggle'),
ToggleableFactory('inputValue')
/* @vue/component */
).extend({
name: 'v-btn',
props: {
activeClass: {
type: String,
default: 'v-btn--active'
},
block: Boolean,
depressed: Boolean,
fab: Boolean,
flat: Boolean,
icon: Boolean,
large: Boolean,
loading: Boolean,
outline: Boolean,
ripple: {
type: [Boolean, Object],
default: null
},
round: Boolean,
small: Boolean,
tag: {
type: String,
default: 'button'
},
type: {
type: String,
default: 'button'
},
value: null as any as PropValidator<any>
},
computed: {
classes (): any {
return {
'v-btn': true,
[this.activeClass]: this.isActive,
'v-btn--absolute': this.absolute,
'v-btn--block': this.block,
'v-btn--bottom': this.bottom,
'v-btn--disabled': this.disabled,
'v-btn--flat': this.flat,
'v-btn--floating': this.fab,
'v-btn--fixed': this.fixed,
'v-btn--icon': this.icon,
'v-btn--large': this.large,
'v-btn--left': this.left,
'v-btn--loader': this.loading,
'v-btn--outline': this.outline,
'v-btn--depressed': (this.depressed && !this.flat) || this.outline,
'v-btn--right': this.right,
'v-btn--round': this.round,
'v-btn--router': this.to,
'v-btn--small': this.small,
'v-btn--top': this.top,
...this.themeClasses
}
},
computedRipple (): RippleOptions | boolean {
const defaultRipple = this.icon || this.fab ? { circle: true } : true
if (this.disabled) return false
else return this.ripple !== null ? this.ripple : defaultRipple
}
},
watch: {
'$route': 'onRouteChange'
},
methods: {
// Prevent focus to match md spec
click (e: MouseEvent): void {
!this.fab &&
e.detail &&
this.$el.blur()
this.$emit('click', e)
this.btnToggle && this.toggle()
},
genContent (): VNode {
return this.$createElement(
'div',
{ 'class': 'v-btn__content' },
[this.$slots.default]
)
},
genLoader (): VNode {
const children: VNodeChildren = []
if (!this.$slots.loader) {
children.push(this.$createElement(VProgressCircular, {
props: {
indeterminate: true,
size: 23,
width: 2
}
}))
} else {
children.push(this.$slots.loader)
}
return this.$createElement('span', { 'class': 'v-btn__loading' }, children)
},
onRouteChange () {
if (!this.to || !this.$refs.link) return
const path = `_vnode.data.class.${this.activeClass}`
this.$nextTick(() => {
if (getObjectValueByPath(this.$refs.link, path)) {
this.toggle()
}
})
}
},
render (h): VNode {
const setColor = (!this.outline && !this.flat && !this.disabled) ? this.setBackgroundColor : this.setTextColor
const { tag, data } = this.generateRouteLink(this.classes)
const children = [this.genContent()]
tag === 'button' && (data.attrs!.type = this.type)
this.loading && children.push(this.genLoader())
data.attrs!.value = ['string', 'number'].includes(typeof this.value)
? this.value
: JSON.stringify(this.value)
if (this.btnToggle) {
data.ref = 'link'
}
return h(tag, setColor(this.color, data), children)
}
})