quasar
Version:
Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
276 lines (226 loc) • 6.67 kB
JavaScript
import Vue from 'vue'
import QField from '../field/QField.js'
import MaskMixin from '../../mixins/mask.js'
import CompositionMixin from '../../mixins/composition.js'
import debounce from '../../utils/debounce.js'
import { stop } from '../../utils/event.js'
export default Vue.extend({
name: 'QInput',
mixins: [ QField, MaskMixin, CompositionMixin ],
props: {
value: { required: false },
type: {
type: String,
default: 'text'
},
debounce: [String, Number],
autogrow: Boolean, // makes a textarea
inputClass: [Array, String, Object],
inputStyle: [Array, String, Object]
},
watch: {
value (v) {
if (this.hasMask === true) {
if (this.stopValueWatcher === true) {
this.stopValueWatcher = false
return
}
this.__updateMaskValue(v)
}
else if (this.innerValue !== v) {
this.innerValue = v
if (
this.type === 'number' &&
this.hasOwnProperty('tempValue') === true
) {
if (this.typedNumber === true) {
this.typedNumber = false
}
else {
delete this.tempValue
}
}
}
// textarea only
this.autogrow === true && this.$nextTick(this.__adjustHeightDebounce)
},
autogrow (autogrow) {
// textarea only
if (autogrow === true) {
this.$nextTick(this.__adjustHeightDebounce)
}
// if it has a number of rows set respect it
else if (this.$attrs.rows > 0 && this.$refs.input !== void 0) {
const inp = this.$refs.input
inp.style.height = 'auto'
}
},
dense () {
this.autogrow === true && this.$nextTick(this.__adjustHeight)
}
},
data () {
return { innerValue: this.__getInitialMaskedValue() }
},
computed: {
isTextarea () {
return this.type === 'textarea' || this.autogrow === true
},
fieldClass () {
return `q-${this.isTextarea === true ? 'textarea' : 'input'}` +
(this.autogrow === true ? ' q-textarea--autogrow' : '')
}
},
methods: {
focus () {
const el = document.activeElement
if (
this.$refs.input !== void 0 &&
this.$refs.input !== el &&
// IE can have null document.activeElement
(el === null || el.id !== this.targetUid)
) {
this.$refs.input.focus()
}
},
select () {
this.$refs.input !== void 0 && this.$refs.input.select()
},
__onInput (e) {
if (e && e.target && e.target.composing === true) {
return
}
if (this.type === 'file') {
this.$emit('input', e.target.files)
return
}
const val = e.target.value
if (this.hasMask === true) {
this.__updateMaskValue(val)
}
else {
this.__emitValue(val)
}
// we need to trigger it immediately too,
// to avoid "flickering"
this.autogrow === true && this.__adjustHeight()
},
__emitValue (val, stopWatcher) {
this.emitValueFn = () => {
if (
this.type !== 'number' &&
this.hasOwnProperty('tempValue') === true
) {
delete this.tempValue
}
if (this.value !== val) {
stopWatcher === true && (this.stopValueWatcher = true)
this.$emit('input', val)
}
this.emitValueFn = void 0
}
if (this.type === 'number') {
this.typedNumber = true
this.tempValue = val
}
if (this.debounce !== void 0) {
clearTimeout(this.emitTimer)
this.tempValue = val
this.emitTimer = setTimeout(this.emitValueFn, this.debounce)
}
else {
this.emitValueFn()
}
},
// textarea only
__adjustHeight () {
const inp = this.$refs.input
if (inp !== void 0) {
const parentStyle = inp.parentNode.style
// reset height of textarea to a small size to detect the real height
// but keep the total control size the same
parentStyle.marginBottom = (inp.scrollHeight - 1) + 'px'
inp.style.height = '1px'
inp.style.height = inp.scrollHeight + 'px'
parentStyle.marginBottom = ''
}
},
__onChange (e) {
this.__onComposition(e)
clearTimeout(this.emitTimer)
this.emitValueFn !== void 0 && this.emitValueFn()
this.$emit('change', e)
},
__onFinishEditing (e) {
e !== void 0 && stop(e)
clearTimeout(this.emitTimer)
this.emitValueFn !== void 0 && this.emitValueFn()
this.typedNumber = false
this.stopValueWatcher = false
delete this.tempValue
this.type !== 'file' && this.$nextTick(() => {
if (this.$refs.input !== void 0) {
this.$refs.input.value = this.innerValue
}
})
},
__getControl (h) {
const on = {
...this.$listeners,
input: this.__onInput,
// Safari < 10.2 & UIWebView doesn't fire compositionend when
// switching focus before confirming composition choice
// this also fixes the issue where some browsers e.g. iOS Chrome
// fires "change" instead of "input" on autocomplete.
change: this.__onChange,
blur: this.__onFinishEditing,
focus: stop
}
on.compositionstart = on.compositionupdate = on.compositionend = this.__onComposition
if (this.hasMask === true) {
on.keydown = this.__onMaskedKeydown
}
const attrs = {
tabindex: 0,
autofocus: this.autofocus,
rows: this.type === 'textarea' ? 6 : void 0,
'aria-label': this.label,
...this.$attrs,
id: this.targetUid,
type: this.type,
maxlength: this.maxlength,
disabled: this.disable === true,
readonly: this.readonly === true
}
if (this.autogrow === true) {
attrs.rows = 1
}
return h(this.isTextarea === true ? 'textarea' : 'input', {
ref: 'input',
staticClass: 'q-field__native q-placeholder',
style: this.inputStyle,
class: this.inputClass,
attrs,
on,
domProps: this.type !== 'file'
? {
value: this.hasOwnProperty('tempValue') === true
? this.tempValue
: this.innerValue
}
: null
})
}
},
created () {
// textarea only
this.__adjustHeightDebounce = debounce(this.__adjustHeight, 100)
},
mounted () {
// textarea only
this.autogrow === true && this.__adjustHeight()
},
beforeDestroy () {
this.__onFinishEditing()
}
})