mand-mobile/components/input-item/index.vue

661 lines
16 KiB
Vue
Raw Normal View History

2018-03-26 16:04:04 +08:00
<template>
2018-09-19 01:12:08 +08:00
<md-field-item
2018-03-26 16:04:04 +08:00
class="md-input-item"
:class="[
isHighlight ? 'is-highlight' : '',
2018-03-26 16:04:04 +08:00
isTitleLatent ? 'is-title-latent' : '',
isInputActive ? 'is-active' : '',
isInputFocus ? 'is-focus' : '',
isInputError ? 'is-error' : '',
isInputBrief ? 'with-brief' : '',
2018-09-16 13:05:04 +08:00
isDisabled ? 'is-disabled': '',
2018-03-26 16:04:04 +08:00
clearable ? 'is-clear' : '',
inputEnv,
align,
size
2018-09-19 01:12:08 +08:00
]"
:title="title"
2018-09-28 15:37:15 +08:00
solid
2018-09-19 01:12:08 +08:00
>
<template slot="left">
<slot name="left"></slot>
</template>
2018-09-19 01:12:08 +08:00
<!-- ------------ -->
<!-- INPUT -->
<!-- ------------ -->
<!-- Native Input -->
<template v-if="!isVirtualKeyboard">
<input
class="md-input-item-input"
:type="inputType"
:name="name"
v-model="inputBindValue"
:placeholder="inputPlaceholder"
:disabled="isDisabled"
:readonly="readonly"
:maxlength="isFormative ? '' : maxlength"
autocomplete="off"
@focus="$_onFocus"
@blur="$_onBlur"
@keyup="$_onKeyup"
@keydown="$_onKeydown"
@input="$_onInput"
/>
</template>
<!-- Fake Input -->
<template v-else>
2018-03-26 16:04:04 +08:00
<div
2018-09-19 01:12:08 +08:00
class="md-input-item-fake"
2018-03-26 16:04:04 +08:00
:class="{
2018-09-19 01:12:08 +08:00
'is-focus': isInputFocus,
'disabled': isDisabled,
'readonly': readonly
2018-03-26 16:04:04 +08:00
}"
2018-09-19 01:12:08 +08:00
@click="$_onFakeInputClick"
>
<span v-text="inputValue"></span>
<span
class="md-input-item-fake-placeholder"
v-if="inputValue === '' && inputPlaceholder !== ''"
v-text="inputPlaceholder"></span>
2018-03-26 16:04:04 +08:00
</div>
2018-09-19 01:12:08 +08:00
</template>
<template slot="right">
<!-- ------------ -->
<!-- CLEART BTN -->
<!-- ------------ -->
<div
class="md-input-item-clear"
v-if="clearable && !isDisabled && !readonly"
v-show="!isInputEmpty && isInputFocus"
@click="$_clearInput"
>
2018-10-15 15:41:50 +08:00
<md-icon name="clear"></md-icon>
</div>
2018-09-19 01:12:08 +08:00
<!-- ------------ -->
<!-- RIGHT SLOT -->
<!-- ------------ -->
2018-09-19 01:12:08 +08:00
<slot name="right"></slot>
</template>
2018-09-19 01:12:08 +08:00
<template slot="children">
<!-- -------------------- -->
<!-- BRIEF/ERROR TIP -->
<!-- -------------------- -->
<div
v-if="isInputError"
class="md-input-item-msg"
>
<p v-if="error !== ''" v-text="error"></p>
<slot name="error" v-else></slot>
</div>
<div
v-if="isInputBrief"
class="md-input-item-brief"
>
<p v-if="brief !== ''" v-text="brief"></p>
<slot name="brief" v-else></slot>
</div>
<!-- ------------ -->
<!-- KEYBORARD -->
<!-- ------------ -->
<md-number-keyboard
v-if="isVirtualKeyboard"
ref="number-keyboard"
:id="`${name}-number-keyboard`"
class="md-input-item-number-keyboard"
:ok-text="virtualKeyboardOkText"
:disorder="virtualKeyboardDisorder"
@enter="$_onNumberKeyBoardEnter"
@delete="$_onNumberKeyBoardDelete"
@confirm="$_onNumberKeyBoardConfirm"
></md-number-keyboard>
</template>
2018-09-19 01:12:08 +08:00
</md-field-item>
2018-03-26 16:04:04 +08:00
</template>
<script> import Icon from '../icon'
2018-09-19 01:12:08 +08:00
import FieldItem from '../field-item'
2018-07-12 16:04:49 +08:00
import NumberKeyboard from '../number-keyboard'
2018-03-26 16:04:04 +08:00
import {getCursorsPosition, setCursorsPosition} from './cursor'
import {noop, isIOS, isAndroid, randomId} from '../_util'
import {formatValueByGapRule, formatValueByGapStep, trimValue} from '../_util/formate-value'
export default {
name: 'md-input-item',
components: {
[Icon.name]: Icon,
2018-09-19 01:12:08 +08:00
[FieldItem.name]: FieldItem,
2018-07-12 16:04:49 +08:00
[NumberKeyboard.name]: NumberKeyboard,
2018-03-26 16:04:04 +08:00
},
2018-09-16 13:05:04 +08:00
inject: {
rootField: {
from: 'rootField',
default: () => ({}),
},
},
2018-03-26 16:04:04 +08:00
props: {
type: {
// text, bankCard, password, phone, money, digit
2018-03-26 16:04:04 +08:00
type: String,
default: 'text',
},
name: {
type: [String, Number],
default() {
return randomId('input-item')
},
},
title: {
type: String,
default: '',
},
brief: {
type: String,
default: '',
},
2018-03-26 16:04:04 +08:00
value: {
type: [String, Number],
2018-03-26 16:04:04 +08:00
default: '',
},
placeholder: {
type: String,
default: '',
},
maxlength: {
type: [String, Number],
default: '',
},
size: {
// large, normal
type: String,
default: 'normal',
},
align: {
// left, center, right
type: String,
default: 'left',
},
error: {
type: String,
default: '',
},
readonly: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
clearable: {
type: Boolean,
default: false,
},
isVirtualKeyboard: {
type: Boolean,
default: false,
},
virtualKeyboardDisorder: {
type: Boolean,
},
virtualKeyboardOkText: {
type: String,
},
isTitleLatent: {
type: Boolean,
default: false,
},
isFormative: {
type: Boolean,
default() {
const type = this.type
return type === 'bankCard' || type === 'phone' || type === 'money' || type === 'digit'
2018-03-26 16:04:04 +08:00
},
},
isHighlight: {
type: Boolean,
default: false,
},
formation: {
type: Function,
default: noop,
},
},
data() {
return {
inputValue: '',
inputBindValue: '',
inputNumberKeyboard: '',
isInputFocus: false,
}
},
computed: {
inputEnv() {
/* istanbul ignore next */
if (isIOS) {
return 'is-ios'
} else if (isAndroid) {
return 'is-android'
} else {
return 'is-browser'
}
},
inputType() {
let inputType = this.type || 'text'
if (inputType === 'bankCard' || inputType === 'phone' || inputType === 'digit') {
2018-03-26 16:04:04 +08:00
inputType = 'tel'
}
return inputType
},
inputMaxLength() {
if (this.type === 'phone') {
return 11
} else {
return this.maxlength
}
},
inputPlaceholder() {
return this.isTitleLatent && this.isInputActive ? '' : this.placeholder
},
2018-03-26 16:04:04 +08:00
isInputActive() {
return !this.isInputEmpty || this.isInputFocus
},
isInputEmpty() {
return !this.inputValue.length
},
isInputError() {
return this.$slots.error || this.error !== ''
},
isInputBrief() {
return (this.$slots.brief || this.brief !== '') && !this.isInputError
2018-03-26 16:04:04 +08:00
},
2018-09-16 13:05:04 +08:00
isDisabled() {
return this.rootField.disabled || this.disabled
},
2018-03-26 16:04:04 +08:00
},
watch: {
value(val) {
this.inputValue = this.$_formateValue(this.$_subValue(val + '')).value
2018-03-26 16:04:04 +08:00
},
inputValue(val) {
this.inputBindValue = val
val = this.isFormative ? this.$_trimValue(val) : val
this.$emit('input', val)
this.$emit('change', this.name, val)
2018-03-26 16:04:04 +08:00
},
isInputFocus(val) {
if (!this.isVirtualKeyboard) {
return
}
if (val) {
this.inputNumberKeyboard.show()
this.$emit('focus', this.name)
} else {
this.inputNumberKeyboard.hide()
this.$emit('blur', this.name)
}
},
},
created() {
this.inputValue = this.$_formateValue(this.$_subValue(this.value + '')).value
2018-03-26 16:04:04 +08:00
},
mounted() {
this.isVirtualKeyboard && this.$_initNumberKeyBoard()
},
beforeDestroy() {
const keyboard = this.inputNumberKeyboard
if (keyboard && keyboard.$el) {
document.body.removeChild(keyboard.$el)
}
},
2018-03-26 16:04:04 +08:00
methods: {
// MARK: private methods
$_formateValue(curValue, curPos = 0) {
const type = this.type
const name = this.name
const oldValue = this.inputValue
const isAdd = oldValue.length > curValue.length ? -1 : 1
let formateValue = {value: curValue, range: curPos}
// no format
if (!this.isFormative || curValue === '') {
return formateValue
}
// custom format by user
const customValue = this.formation(name, curValue, curPos)
if (customValue) {
return customValue
}
// default format by component
let gap = ' '
switch (type) {
case 'bankCard':
curValue = this.$_subValue(trimValue(curValue.replace(/\D/g, '')))
formateValue = formatValueByGapStep(4, curValue, gap, 'left', curPos, isAdd, oldValue)
break
case 'phone':
curValue = this.$_subValue(trimValue(curValue.replace(/\D/g, '')))
formateValue = formatValueByGapRule('3|4|4', curValue, gap, curPos, isAdd)
break
case 'money':
gap = ','
curValue = this.$_subValue(trimValue(curValue.replace(/[^\d.]/g, '')))
// curValue = curValue.replace(/\D/g, '')
const dotPos = curValue.indexOf('.')
// format if no dot or new add dot or insert befor dot
const moneyCurValue = curValue.split('.')[0]
const moneyCurDecimal = ~dotPos ? `.${curValue.split('.')[1]}` : ''
formateValue = formatValueByGapStep(
3,
trimValue(moneyCurValue, gap),
gap,
'right',
curPos,
isAdd,
oldValue.split('.')[0],
)
formateValue.value += moneyCurDecimal
break
case 'digit':
curValue = this.$_subValue(trimValue(curValue.replace(/\D/g, '')))
formateValue.value = curValue
break
2018-03-26 16:04:04 +08:00
default:
break
}
return formateValue
},
$_trimValue(val) {
return trimValue(val, '\\s|,')
},
$_subValue(val) {
const len = this.inputMaxLength
if (len !== '') {
return val.substring(0, len)
} else {
return val
}
},
$_clearInput() {
this.inputValue = ''
!this.isTitleLatent && this.focus()
2018-03-26 16:04:04 +08:00
},
$_focusFakeInput() {
this.isInputFocus = true
this.$nextTick(() => {
this.$_addBlurListener()
})
},
$_blurFakeInput() {
this.isInputFocus = false
this.$_removeBlurListener()
},
$_addBlurListener() {
document.addEventListener('click', this.$_blurFakeInput, false)
},
$_removeBlurListener() {
document.removeEventListener('click', this.$_blurFakeInput, false)
},
$_initNumberKeyBoard() {
2018-07-12 16:04:49 +08:00
const keyboard = this.$refs['number-keyboard']
this.inputNumberKeyboard = keyboard
document.body.appendChild(keyboard.$el)
2018-03-26 16:04:04 +08:00
},
// MARK: events handler
$_onInput(event) {
const formateValue = this.$_formateValue(event.target.value, getCursorsPosition(event.target))
this.inputValue = formateValue.value
this.inputBindValue = formateValue.value
if (this.isFormative) {
this.$nextTick(() => {
setCursorsPosition(event.target, formateValue.range)
})
}
},
$_onKeyup(event) {
this.$emit('keyup', this.name, event)
if (+event.keyCode === 13 || +event.keyCode === 108) {
this.$emit('confirm', this.name, this.inputValue)
}
},
$_onKeydown(event) {
this.$emit('keydown', this.name, event)
},
$_onFocus() {
this.isInputFocus = true
this.$emit('focus', this.name)
},
$_onBlur() {
2018-09-03 10:55:46 +08:00
this.isInputFocus = false
2018-03-26 16:04:04 +08:00
this.$emit('blur', this.name)
},
$_onFakeInputClick(event) {
2018-09-16 13:05:04 +08:00
if (this.isDisabled || this.readonly) {
2018-03-26 16:04:04 +08:00
return
}
this.$_blurFakeInput()
if (!this.isInputFocus) {
this.$_focusFakeInput(event)
}
},
$_onNumberKeyBoardEnter(val) {
this.inputValue = this.$_formateValue(this.inputValue + val).value
},
$_onNumberKeyBoardDelete() {
const inputValue = this.inputValue
if (inputValue === '') {
return
}
this.inputValue = this.$_formateValue(inputValue.substring(0, inputValue.length - 1)).value
},
$_onNumberKeyBoardConfirm() {
this.$emit('confirm', this.name, this.inputValue)
},
// MARK: public methods
focus() {
if (this.isVirtualKeyboard) {
this.$_onFakeInputClick()
} else {
this.$el.querySelector('.md-input-item-input').focus()
setTimeout(() => {
this.isInputFocus = true
}, 200)
2018-03-26 16:04:04 +08:00
}
},
blur() {
if (this.isVirtualKeyboard) {
this.$_blurFakeInput()
} else {
this.$el.querySelector('.md-input-item-input').blur()
this.isInputFocus = false
2018-03-26 16:04:04 +08:00
}
},
getValue() {
return this.inputValue
},
},
}
</script>
<style lang="stylus">
.md-input-item
2018-09-19 01:12:08 +08:00
.md-field-item-content
padding-top 0
padding-bottom 0
.md-field-item-control
display flex
align-items center
2018-09-16 13:05:04 +08:00
.md-input-item-clear
color input-item-icon
.md-icon
background color-bg-base
border-radius radius-circle
.md-input-item-input,
.md-input-item-fake
// display flex
width 100%
height input-item-height
color input-item-color
font-size input-item-font-size
font-weight input-item-font-weight
font-family DINPro-Medium
-webkit-appearance none
border none
background transparent
outline none
box-sizing border-box
-webkit-tap-highlight-color transparent
appearance none
.md-input-item-input
&:disabled, &[disabled]
opacity input-item-color-disabled
&::-webkit-input-placeholder
color input-item-placeholder
font-weight font-weight-normal
&::-webkit-outer-spin-button, &::-webkit-inner-spin-button
-webkit-appearance none
.md-input-item-fake
line-height input-item-height
word-ellipsis()
cursor text
&::after
2018-03-26 16:04:04 +08:00
position relative
2018-09-16 13:05:04 +08:00
z-index 2
display none
content " "
height input-item-font-size
border-right solid 1.5px color-text-base
animation keyboard-cursor infinite 1s step-start
&.is-focus:after
display inline
&.disabled
opacity input-item-color-disabled
.md-input-item-fake-placeholder
position absolute
top 0
left 0
width 100%
color input-item-placeholder
font-weight font-weight-normal
.md-input-item-msg,
.md-input-item-brief
word-break()
2018-09-19 01:12:08 +08:00
&:not(:last-child)
margin-bottom 10px
2018-09-16 13:05:04 +08:00
.md-input-item-brief
font-size input-item-font-size-brief
color input-item-color-brief
.md-input-item-msg
font-size input-item-font-size-error
color input-item-color-error
.md-input-item
2018-07-12 16:04:49 +08:00
&.left
2018-09-16 13:05:04 +08:00
.md-input-item-input,
.md-input-item-fake
2018-03-26 16:04:04 +08:00
text-align left
2018-09-19 01:12:08 +08:00
2018-03-26 16:04:04 +08:00
&.center
2018-09-16 13:05:04 +08:00
.md-input-item-input,
.md-input-item-fake
2018-03-26 16:04:04 +08:00
text-align center
2018-09-19 01:12:08 +08:00
2018-03-26 16:04:04 +08:00
&.right
2018-09-16 13:05:04 +08:00
.md-input-item-input,
.md-input-item-fake
2018-03-26 16:04:04 +08:00
text-align right
2018-09-19 01:12:08 +08:00
2018-03-26 16:04:04 +08:00
&.is-title-latent
2018-09-19 01:12:08 +08:00
.md-field-item-title
2018-03-26 16:04:04 +08:00
position absolute
top 50%
left 0
height auto
font-size input-item-title-latent-font-size
color input-item-title-latent-color
transform translate3d(0, -50%, 0)
transition all .3s ease
opacity 0
will-change auto
2018-09-19 01:12:08 +08:00
.md-field-item-control
2018-03-26 16:04:04 +08:00
top 15px
&.is-active
2018-09-19 01:12:08 +08:00
.md-field-item-title
2018-03-26 16:04:04 +08:00
opacity 1
top 10px
transform translate3d(0, 0, 0)
// .md-input-item-input::-webkit-input-placeholder, .md-input-item-fake-placeholder
// color transparent
&.is-highlight
2018-09-16 13:05:04 +08:00
.md-input-item-input::-webkit-input-placeholder,
.md-input-item-fake-placeholder
color input-item-placeholder-highlight
2018-09-19 01:12:08 +08:00
2018-03-26 16:04:04 +08:00
&.large .md-input-item-input
font-size input-item-font-size-large
2018-09-19 01:12:08 +08:00
&.is-error
.md-field-item-content::before
background-color input-item-color-error
2018-07-12 16:04:49 +08:00
&.is-ios
.md-input-item-input::-webkit-input-placeholder
position relative
top -3px
overflow visible
2018-09-16 13:05:04 +08:00
.md-input-item-fake::after
border-right solid 6px #2C6CF5
border-radius 2px
&.is-android
2018-09-16 13:05:04 +08:00
.md-input-item-fake::after
border-right solid 4px color-text-base
2018-07-12 16:04:49 +08:00
2018-09-16 13:05:04 +08:00
@-webkit-keyframes keyboard-cursor
0%
opacity 1
50%
opacity 0
to
opacity 1
@keyframes keyboard-cursor
0%
opacity 1
50%
opacity 0
to
opacity 1
2018-03-26 16:04:04 +08:00
</style>