vuetify
Version:
Vue.js 2 Semantic Component Framework
289 lines (259 loc) • 7.46 kB
JavaScript
// Styles
import '../../stylus/components/_input-groups.styl'
import '../../stylus/components/_text-fields.styl'
// Mixins
import Colorable from '../../mixins/colorable'
import Input from '../../mixins/input'
import Maskable from '../../mixins/maskable'
import Soloable from '../../mixins/soloable'
export default {
name: 'v-text-field',
mixins: [
Colorable,
Input,
Maskable,
Soloable
],
inheritAttrs: false,
data () {
return {
initialValue: null,
inputHeight: null,
internalChange: false,
badInput: false
}
},
props: {
autofocus: Boolean,
autoGrow: Boolean,
box: Boolean,
clearable: Boolean,
color: {
type: String,
default: 'primary'
},
counter: [Boolean, Number, String],
fullWidth: Boolean,
multiLine: Boolean,
noResize: Boolean,
placeholder: String,
prefix: String,
rowHeight: {
type: [Number, String],
default: 24,
validator: v => !isNaN(parseFloat(v))
},
rows: {
type: [Number, String],
default: 5,
validator: v => !isNaN(parseInt(v, 10))
},
singleLine: Boolean,
suffix: String,
textarea: Boolean,
type: {
type: String,
default: 'text'
}
},
computed: {
classes () {
const classes = {
...this.genSoloClasses(),
'input-group--text-field': true,
'input-group--text-field-box': this.box,
'input-group--single-line': this.singleLine || this.isSolo,
'input-group--multi-line': this.multiLine,
'input-group--full-width': this.fullWidth,
'input-group--no-resize': this.noResizeHandle,
'input-group--prefix': this.prefix,
'input-group--suffix': this.suffix,
'input-group--textarea': this.textarea
}
if (this.hasError) {
classes['error--text'] = true
} else {
return this.addTextColorClassChecks(classes)
}
return classes
},
count () {
let inputLength
if (this.inputValue) inputLength = this.inputValue.toString().length
else inputLength = 0
return `${inputLength} / ${this.counterLength}`
},
counterLength () {
const parsedLength = parseInt(this.counter, 10)
return isNaN(parsedLength) ? 25 : parsedLength
},
inputValue: {
get () {
return this.lazyValue
},
set (val) {
if (this.mask) {
this.lazyValue = this.unmaskText(this.maskText(this.unmaskText(val)))
this.setSelectionRange()
} else {
this.lazyValue = val
this.$emit('input', this.lazyValue)
}
}
},
isDirty () {
return (this.lazyValue != null &&
this.lazyValue.toString().length > 0) ||
this.badInput ||
['time', 'date', 'datetime-local', 'week', 'month'].includes(this.type)
},
isTextarea () {
return this.multiLine || this.textarea
},
noResizeHandle () {
return this.isTextarea && (this.noResize || this.shouldAutoGrow)
},
shouldAutoGrow () {
return this.isTextarea && this.autoGrow
}
},
watch: {
isFocused (val) {
if (val) {
this.initialValue = this.lazyValue
} else if (this.initialValue !== this.lazyValue) {
this.$emit('change', this.lazyValue)
}
},
value (val) {
if (this.mask && !this.internalChange) {
const masked = this.maskText(this.unmaskText(val))
this.lazyValue = this.unmaskText(masked)
// Emit when the externally set value was modified internally
String(val) !== this.lazyValue && this.$nextTick(() => {
this.$refs.input.value = masked
this.$emit('input', this.lazyValue)
})
} else this.lazyValue = val
if (this.internalChange) this.internalChange = false
!this.validateOnBlur && this.validate()
this.shouldAutoGrow && this.calculateInputHeight()
}
},
mounted () {
this.shouldAutoGrow && this.calculateInputHeight()
this.autofocus && this.focus()
},
methods: {
calculateInputHeight () {
this.inputHeight = null
this.$nextTick(() => {
const height = this.$refs.input
? this.$refs.input.scrollHeight
: 0
const minHeight = parseInt(this.rows, 10) * parseFloat(this.rowHeight)
this.inputHeight = Math.max(minHeight, height)
})
},
onInput (e) {
this.mask && this.resetSelections(e.target)
this.inputValue = e.target.value
this.badInput = e.target.validity && e.target.validity.badInput
this.shouldAutoGrow && this.calculateInputHeight()
},
blur (e) {
this.isFocused = false
// Reset internalChange state
// to allow external change
// to persist
this.internalChange = false
this.$nextTick(() => {
this.validate()
})
this.$emit('blur', e)
},
focus (e) {
if (!this.$refs.input) return
this.isFocused = true
if (document.activeElement !== this.$refs.input) {
this.$refs.input.focus()
}
this.$emit('focus', e)
},
keyDown (e) {
// Prevents closing of a
// dialog when pressing
// enter
if (this.isTextarea &&
this.isFocused &&
e.keyCode === 13
) {
e.stopPropagation()
}
this.internalChange = true
},
genCounter () {
return this.$createElement('div', {
'class': {
'input-group__counter': true,
'input-group__counter--error': this.hasError
}
}, this.count)
},
genInput () {
const tag = this.isTextarea ? 'textarea' : 'input'
const listeners = Object.assign({}, this.$listeners)
delete listeners['change'] // Change should not be bound externally
const data = {
style: {},
domProps: {
value: this.maskText(this.lazyValue)
},
attrs: {
...this.$attrs,
autofocus: this.autofocus,
disabled: this.disabled,
required: this.required,
readonly: this.readonly,
tabindex: this.tabindex,
'aria-label': (!this.$attrs || !this.$attrs.id) && this.label // Label `for` will be set if we have an id
},
on: Object.assign(listeners, {
blur: this.blur,
input: this.onInput,
focus: this.focus,
keydown: this.keyDown
}),
ref: 'input'
}
if (this.shouldAutoGrow) {
data.style.height = this.inputHeight && `${this.inputHeight}px`
}
if (this.placeholder) data.attrs.placeholder = this.placeholder
if (!this.isTextarea) {
data.attrs.type = this.type
} else {
data.attrs.rows = this.rows
}
if (this.mask) {
data.attrs.maxlength = this.masked.length
}
const children = [this.$createElement(tag, data)]
this.prefix && children.unshift(this.genFix('prefix'))
this.suffix && children.push(this.genFix('suffix'))
return children
},
genFix (type) {
return this.$createElement('span', {
'class': `input-group--text-field__${type}`
}, this[type])
},
clearableCallback () {
this.inputValue = null
this.$nextTick(() => this.$refs.input.focus())
}
},
render () {
return this.genInputGroup(this.genInput(), { attrs: { tabindex: false } })
}
}