UNPKG

quasar-framework

Version:

Build responsive SPA, SSR, PWA, Hybrid Mobile Apps and Electron apps, all simultaneously using the same codebase

415 lines (393 loc) 11.3 kB
import FrameMixin from '../../mixins/input-frame.js' import InputMixin from '../../mixins/input.js' import inputTypes from './input-types.js' import frameDebounce from '../../utils/frame-debounce.js' import { between } from '../../utils/format.js' import QResizeObservable from '../observables/QResizeObservable.js' import QInputFrame from '../input-frame/QInputFrame.js' import QSpinner from '../spinner/QSpinner.js' import QIcon from '../icon/QIcon.js' export default { name: 'QInput', mixins: [FrameMixin, InputMixin], props: { value: { required: true }, type: { type: String, default: 'text', validator: t => inputTypes.includes(t) }, align: { type: String, validator: v => ['left', 'center', 'right'].includes(v) }, noPassToggle: Boolean, numericKeyboardToggle: Boolean, readonly: Boolean, decimals: Number, step: Number, upperCase: Boolean, lowerCase: Boolean, initialShowPassword: Boolean }, data () { return { showPass: this.initialShowPassword, showNumber: true, model: this.value, watcher: null, autofilled: false, shadow: { val: this.model, set: this.__set, setNav: this.__set, loading: false, watched: 0, isEditable: () => this.editable, isDark: () => this.dark, hasFocus: () => document.activeElement === this.$refs.input, register: () => { this.shadow.watched += 1 this.__watcherRegister() }, unregister: () => { this.shadow.watched = Math.max(0, this.shadow.watched - 1) this.__watcherUnregister() }, getEl: () => this.$refs.input } } }, watch: { value (v) { const vOldNum = parseFloat(this.model), vNewNum = parseFloat(v) if (!this.isNumber || this.isNumberError || isNaN(vOldNum) || isNaN(vNewNum) || vOldNum !== vNewNum) { this.model = v } this.isNumberError = false this.isNegZero = false }, isTextarea (v) { this[v ? '__watcherRegister' : '__watcherUnregister']() }, '$attrs.rows' () { this.isTextarea && this.__updateArea() } }, provide () { return { __input: this.shadow } }, computed: { isNumber () { return this.type === 'number' }, isPassword () { return this.type === 'password' }, isTextarea () { return this.type === 'textarea' }, isLoading () { return this.loading || (this.shadow.watched && this.shadow.loading) }, keyboardToggle () { return this.$q.platform.is.mobile && this.isNumber && this.numericKeyboardToggle }, inputType () { if (this.isPassword) { return this.showPass && this.editable ? 'text' : 'password' } return this.isNumber ? (this.showNumber || !this.editable ? 'number' : 'text') : this.type }, inputClasses () { const classes = [] this.align && classes.push(`text-${this.align}`) this.autofilled && classes.push('q-input-autofill') return classes }, length () { return this.model !== null && this.model !== undefined ? ('' + this.model).length : 0 }, computedClearValue () { return this.clearValue === void 0 ? (this.isNumber ? null : '') : this.clearValue }, computedStep () { return this.step || (this.decimals ? 10 ** -this.decimals : 'any') }, frameProps () { return { prefix: this.prefix, suffix: this.suffix, stackLabel: this.stackLabel, floatLabel: this.floatLabel, placeholder: this.placeholder, error: this.error, warning: this.warning, disable: this.disable, readonly: this.readonly, inverted: this.inverted, invertedLight: this.invertedLight, dark: this.dark, hideUnderline: this.hideUnderline, before: this.before, after: this.after, color: this.color, noParentField: this.noParentField, focused: this.focused, length: this.autofilled + this.length } } }, methods: { togglePass () { this.showPass = !this.showPass clearTimeout(this.timer) this.focus() }, toggleNumber () { this.showNumber = !this.showNumber clearTimeout(this.timer) this.focus() }, __clearTimer () { this.$nextTick(() => clearTimeout(this.timer)) }, __onAnimationStart (e) { if (e.animationName.indexOf('webkit-autofill-') === 0) { const value = e.animationName === 'webkit-autofill-on' if (value !== this.autofilled) { e.value = this.autofilled = value e.el = this return this.$emit('autofill', e) } } }, __setModel (val) { clearTimeout(this.timer) this.focus() this.__set( this.isNumber && val === 0 ? val : val || (this.isNumber ? null : ''), true ) }, __set (e, forceUpdate) { let val = e && e.target ? e.target.value : e if (this.isNumber) { this.isNegZero = (1 / val) === -Infinity const forcedValue = this.isNegZero ? -0 : val this.model = val val = parseFloat(val) if (isNaN(val) || this.isNegZero) { this.isNumberError = true if (forceUpdate) { this.$emit('input', forcedValue) this.$nextTick(() => { if (String(1 / forcedValue) !== String(1 / this.value)) { this.$emit('change', forcedValue) } }) } return } this.isNumberError = false if (Number.isInteger(this.decimals)) { val = parseFloat(val.toFixed(this.decimals)) } } else { if (this.lowerCase) { val = val.toLowerCase() } else if (this.upperCase) { val = val.toUpperCase() } this.model = val } this.$emit('input', val) if (forceUpdate) { this.$nextTick(() => { if (JSON.stringify(val) !== JSON.stringify(this.value)) { this.$emit('change', val) } }) } }, __updateArea () { const shadow = this.$refs.shadow, input = this.$refs.input if (shadow && input) { const sH = shadow.scrollHeight, h = between(sH, shadow.offsetHeight, this.maxHeight || sH) input.style.height = `${h}px` input.style.overflowY = this.maxHeight && h < sH ? 'scroll' : 'hidden' } }, __watcher (value) { if (this.isTextarea) { this.__updateArea() } if (this.shadow.watched) { this.shadow.val = value } }, __watcherRegister () { if (!this.watcher) { this.watcher = this.$watch('model', this.__watcher) } }, __watcherUnregister (forceUnregister) { if ( this.watcher && (forceUnregister || (!this.isTextarea && !this.shadow.watched)) ) { this.watcher() this.watcher = null } }, __getTextarea (h) { const attrs = Object.assign({ rows: 1 }, this.$attrs) return h('div', { staticClass: 'col row relative-position' }, [ h(QResizeObservable, { on: { resize: this.__updateArea } }), h('textarea', { ref: 'shadow', staticClass: 'col q-input-target q-input-shadow absolute-top', domProps: { value: this.model }, attrs }), h('textarea', { ref: 'input', staticClass: 'col q-input-target q-input-area', attrs: Object.assign({}, attrs, { placeholder: this.inputPlaceholder, disabled: this.disable, readonly: this.readonly }), domProps: { value: this.model }, on: { input: this.__set, focus: this.__onFocus, blur: this.__onInputBlur, keydown: this.__onKeydown, keyup: this.__onKeyup, paste: this.__onPaste } }) ]) }, __getInput (h) { return h('input', { ref: 'input', staticClass: 'col q-input-target q-no-input-spinner ellipsis', 'class': this.inputClasses, attrs: Object.assign({}, this.$attrs, { type: this.inputType, placeholder: this.inputPlaceholder, disabled: this.disable, readonly: this.readonly, step: this.computedStep }), domProps: { value: this.model }, on: { input: this.__set, focus: this.__onFocus, blur: this.__onInputBlur, keydown: this.__onKeydown, keyup: this.__onKeyup, paste: this.__onPaste, animationstart: this.__onAnimationStart } }) } }, mounted () { this.__updateArea = frameDebounce(this.__updateArea) if (this.isTextarea) { this.__updateArea() this.__watcherRegister() } }, beforeDestroy () { this.__watcherUnregister(true) }, render (h) { return h(QInputFrame, { staticClass: 'q-input', props: this.frameProps, on: { click: this.__onClick, focus: this.__onFocus, paste: this.__onPaste } }, [].concat(this.$slots.before).concat([ this.isTextarea ? this.__getTextarea(h) : this.__getInput(h), (!this.disable && this.isPassword && !this.noPassToggle && (this.initialShowPassword || this.length) && h(QIcon, { slot: 'after', staticClass: 'q-if-control', props: { name: this.$q.icon.input[this.showPass ? 'showPass' : 'hidePass'] }, nativeOn: { mousedown: this.__clearTimer, touchstart: this.__clearTimer, click: this.togglePass } })) || void 0, (this.editable && this.keyboardToggle && h(QIcon, { slot: 'after', staticClass: 'q-if-control', props: { name: this.$q.icon.input[this.showNumber ? 'showNumber' : 'hideNumber'] }, nativeOn: { mousedown: this.__clearTimer, touchstart: this.__clearTimer, click: this.toggleNumber } })) || void 0, (this.isClearable && h(QIcon, { slot: 'after', staticClass: 'q-if-control', props: { name: this.$q.icon.input[`clear${this.isInverted ? 'Inverted' : ''}`] }, nativeOn: { mousedown: this.__clearTimer, touchstart: this.__clearTimer, click: this.clear } })) || void 0, (this.isLoading && (this.$slots.loading ? h('div', { staticClass: 'q-if-control', slot: 'after' }, this.$slots.loading) : h(QSpinner, { slot: 'after', staticClass: 'q-if-control', props: { size: '24px' } }))) || void 0 ]).concat(this.$slots.after).concat(this.$slots.default ? h('div', { staticClass: 'absolute-full no-pointer-events', slot: 'after' }, this.$slots.default) : void 0 )) } }