WInput.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. <template>
  2. <div :class="wrapClasses">
  3. <template v-if="type !== 'textarea'">
  4. <div :class="[prefixCls + '-group-prepend']" v-if="prepend" v-show="slotReady"><slot name="prepend"></slot></div>
  5. <i class="ivu-icon" :class="['ivu-icon-ios-close-circle', prefixCls + '-icon', prefixCls + '-icon-clear' , prefixCls + '-icon-normal']" v-if="clearable && currentValue && !itemDisabled" @click="handleClear"></i>
  6. <i class="ivu-icon" :class="['ivu-icon-' + icon, prefixCls + '-icon', prefixCls + '-icon-normal']" v-else-if="icon" @click="handleIconClick"></i>
  7. <i class="ivu-icon ivu-icon-ios-search" :class="[prefixCls + '-icon', prefixCls + '-icon-normal', prefixCls + '-search-icon']" v-else-if="search && enterButton === false" @click="handleSearch"></i>
  8. <span class="ivu-input-suffix" v-else-if="showSuffix"><slot name="suffix"><i class="ivu-icon" :class="['ivu-icon-' + suffix]" v-if="suffix"></i></slot></span>
  9. <span class="ivu-input-word-count" v-else-if="showWordLimit">{{ textLength }}/{{ upperLimit }}</span>
  10. <span class="ivu-input-suffix" v-else-if="password" @click="handleToggleShowPassword">
  11. <i class="ivu-icon ivu-icon-ios-eye-off-outline" v-if="showPassword"></i>
  12. <i class="ivu-icon ivu-icon-ios-eye-outline" v-else></i>
  13. </span>
  14. <transition name="fade">
  15. <i class="ivu-icon ivu-icon-ios-loading ivu-load-loop" :class="[prefixCls + '-icon', prefixCls + '-icon-validate']" v-if="!icon"></i>
  16. </transition>
  17. <input
  18. :id="elementId"
  19. :autocomplete="autocomplete"
  20. :spellcheck="spellcheck"
  21. ref="input"
  22. :type="currentType"
  23. :class="inputClasses"
  24. :placeholder="placeholder"
  25. :disabled="itemDisabled"
  26. :maxlength="maxlength"
  27. :readonly="readonly"
  28. :name="name"
  29. :value="currentValue"
  30. :number="number"
  31. :autofocus="autofocus"
  32. @keyup.enter="handleEnter"
  33. @keyup="handleKeyup"
  34. @keypress="handleKeypress"
  35. @keydown="handleKeydown"
  36. @focus="handleFocus"
  37. @blur="handleBlur"
  38. @compositionstart="handleComposition"
  39. @compositionupdate="handleComposition"
  40. @compositionend="handleComposition"
  41. @input="handleInput"
  42. @change="handleChange"
  43. @paste="handlePaste">
  44. <div :class="[prefixCls + '-group-append']" v-if="append" v-show="slotReady"><slot name="append"></slot></div>
  45. <div :class="[prefixCls + '-group-append', prefixCls + '-search']" v-else-if="search && enterButton" @click="handleSearch">
  46. <i class="ivu-icon ivu-icon-ios-search" v-if="enterButton === true"></i>
  47. <template v-else>{{ enterButton }}</template>
  48. </div>
  49. <span class="ivu-input-prefix" v-else-if="showPrefix"><slot name="prefix"><i class="ivu-icon" :class="['ivu-icon-' + prefix]" v-if="prefix"></i></slot></span>
  50. </template>
  51. <template v-else>
  52. <textarea
  53. :id="elementId"
  54. :wrap="wrap"
  55. :autocomplete="autocomplete"
  56. :spellcheck="spellcheck"
  57. ref="textarea"
  58. :class="textareaClasses"
  59. :style="textareaStyles"
  60. :placeholder="placeholder"
  61. :disabled="itemDisabled"
  62. :rows="rows"
  63. :maxlength="maxlength"
  64. :readonly="readonly"
  65. :name="name"
  66. :value="currentValue"
  67. :autofocus="autofocus"
  68. @keyup.enter="handleEnter"
  69. @keyup="handleKeyup"
  70. @keypress="handleKeypress"
  71. @keydown="handleKeydown"
  72. @focus="handleFocus"
  73. @blur="handleBlur"
  74. @compositionstart="handleComposition"
  75. @compositionupdate="handleComposition"
  76. @compositionend="handleComposition"
  77. @input="handleInput"
  78. @paste="handlePaste">
  79. </textarea>
  80. <span class="ivu-input-word-count" v-if="showWordLimit">{{ textLength }}/{{ upperLimit }}</span>
  81. </template>
  82. </div>
  83. </template>
  84. <script>
  85. import { oneOf, findComponentUpward } from 'view-design/src/utils/assist';
  86. import calcTextareaHeight from 'view-design/src/utils/calcTextareaHeight';
  87. import Emitter from 'view-design/src/mixins/emitter';
  88. import mixinsForm from 'view-design/src/mixins/form';
  89. const prefixCls = 'ivu-input';
  90. export default {
  91. name: 'WInput',
  92. mixins: [ Emitter, mixinsForm ],
  93. props: {
  94. type: {
  95. validator (value) {
  96. return oneOf(value, ['text', 'textarea', 'password', 'url', 'email', 'date', 'number', 'tel']);
  97. },
  98. default: 'text'
  99. },
  100. value: {
  101. type: [String, Number],
  102. default: ''
  103. },
  104. size: {
  105. validator (value) {
  106. return oneOf(value, ['small', 'large', 'default']);
  107. },
  108. default () {
  109. return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
  110. }
  111. },
  112. placeholder: {
  113. type: String,
  114. default: ''
  115. },
  116. maxlength: {
  117. type: [String, Number]
  118. },
  119. disabled: {
  120. type: Boolean,
  121. default: false
  122. },
  123. icon: String,
  124. autosize: {
  125. type: [Boolean, Object],
  126. default: false
  127. },
  128. rows: {
  129. type: Number,
  130. default: 2
  131. },
  132. readonly: {
  133. type: Boolean,
  134. default: false
  135. },
  136. name: {
  137. type: String
  138. },
  139. number: {
  140. type: Boolean,
  141. default: false
  142. },
  143. autofocus: {
  144. type: Boolean,
  145. default: false
  146. },
  147. spellcheck: {
  148. type: Boolean,
  149. default: false
  150. },
  151. autocomplete: {
  152. type: String,
  153. default: 'off'
  154. },
  155. clearable: {
  156. type: Boolean,
  157. default: false
  158. },
  159. elementId: {
  160. type: String
  161. },
  162. wrap: {
  163. validator (value) {
  164. return oneOf(value, ['hard', 'soft']);
  165. },
  166. default: 'soft'
  167. },
  168. prefix: {
  169. type: String,
  170. default: ''
  171. },
  172. suffix: {
  173. type: String,
  174. default: ''
  175. },
  176. search: {
  177. type: Boolean,
  178. default: false
  179. },
  180. enterButton: {
  181. type: [Boolean, String],
  182. default: false
  183. },
  184. // 4.0.0
  185. showWordLimit: {
  186. type: Boolean,
  187. default: false
  188. },
  189. // 4.0.0
  190. password: {
  191. type: Boolean,
  192. default: false
  193. }
  194. },
  195. data () {
  196. return {
  197. currentValue: this.value,
  198. prefixCls: prefixCls,
  199. slotReady: false,
  200. textareaStyles: {},
  201. isOnComposition: false,
  202. showPassword: false
  203. };
  204. },
  205. computed: {
  206. currentType () {
  207. let type = this.type;
  208. if (type === 'password' && this.password && this.showPassword) type = 'text';
  209. return type;
  210. },
  211. prepend () {
  212. let state = false;
  213. if (this.type !== 'textarea') state = this.$slots.prepend !== undefined;
  214. return state;
  215. },
  216. append () {
  217. let state = false;
  218. if (this.type !== 'textarea') state = this.$slots.append !== undefined;
  219. return state;
  220. },
  221. showPrefix () {
  222. let state = false;
  223. if (this.type !== 'textarea') state = this.prefix !== '' || this.$slots.prefix !== undefined;
  224. return state;
  225. },
  226. showSuffix () {
  227. let state = false;
  228. if (this.type !== 'textarea') state = this.suffix !== '' || this.$slots.suffix !== undefined;
  229. return state;
  230. },
  231. wrapClasses () {
  232. return [
  233. `${prefixCls}-wrapper`,
  234. {
  235. [`${prefixCls}-wrapper-${this.size}`]: !!this.size,
  236. [`${prefixCls}-type-${this.type}`]: this.type,
  237. [`${prefixCls}-group`]: this.prepend || this.append || (this.search && this.enterButton),
  238. [`${prefixCls}-group-${this.size}`]: (this.prepend || this.append || (this.search && this.enterButton)) && !!this.size,
  239. [`${prefixCls}-group-with-prepend`]: this.prepend,
  240. [`${prefixCls}-group-with-append`]: this.append || (this.search && this.enterButton),
  241. [`${prefixCls}-hide-icon`]: this.append, // #554
  242. [`${prefixCls}-with-search`]: (this.search && this.enterButton)
  243. }
  244. ];
  245. },
  246. inputClasses () {
  247. return [
  248. `${prefixCls}`,
  249. {
  250. [`${prefixCls}-${this.size}`]: !!this.size,
  251. [`${prefixCls}-disabled`]: this.itemDisabled,
  252. [`${prefixCls}-with-prefix`]: this.showPrefix,
  253. [`${prefixCls}-with-suffix`]: this.showSuffix || (this.search && this.enterButton === false)
  254. }
  255. ];
  256. },
  257. textareaClasses () {
  258. return [
  259. `${prefixCls}`,
  260. {
  261. [`${prefixCls}-disabled`]: this.itemDisabled
  262. }
  263. ];
  264. },
  265. upperLimit () {
  266. return this.maxlength;
  267. },
  268. textLength () {
  269. if (typeof this.value === 'number') {
  270. return String(this.value).length;
  271. }
  272. return (this.value || '').length;
  273. }
  274. },
  275. methods: {
  276. handleEnter (event) {
  277. this.$emit('on-enter', event);
  278. if (this.search) this.$emit('on-search', this.currentValue);
  279. },
  280. handleKeydown (event) {
  281. this.$emit('on-keydown', event);
  282. },
  283. handleKeypress(event) {
  284. this.$emit('on-keypress', event);
  285. },
  286. handleKeyup (event) {
  287. this.$emit('on-keyup', event);
  288. },
  289. handleIconClick (event) {
  290. this.$emit('on-click', event);
  291. },
  292. handleFocus (event) {
  293. this.$emit('on-focus', event);
  294. },
  295. handleBlur (event) {
  296. this.$emit('on-blur', event);
  297. if (!findComponentUpward(this, ['DatePicker', 'TimePicker', 'Cascader', 'Search'])) {
  298. this.dispatch('FormItem', 'on-form-blur', this.currentValue);
  299. }
  300. },
  301. handleComposition(event) {
  302. if (event.type === 'compositionstart') {
  303. this.isOnComposition = true;
  304. }
  305. if (event.type === 'compositionend') {
  306. this.isOnComposition = false;
  307. this.handleInput(event);
  308. }
  309. },
  310. handleInput (event) {
  311. if (this.isOnComposition) return;
  312. let value = event.target.value;
  313. if (this.number && value !== '') value = Number.isNaN(Number(value)) ? value : Number(value);
  314. this.$emit('input', value);
  315. this.setCurrentValue(value);
  316. this.$emit('on-change', event);
  317. },
  318. handleChange (event) {
  319. this.$emit('on-input-change', event);
  320. },
  321. handlePaste (event) {
  322. this.$emit('on-input-paste', event);
  323. },
  324. setCurrentValue (value) {
  325. if (value === this.currentValue) return;
  326. this.$nextTick(() => {
  327. this.resizeTextarea();
  328. });
  329. this.currentValue = value;
  330. if (!findComponentUpward(this, ['DatePicker', 'TimePicker', 'Cascader', 'Search'])) {
  331. this.dispatch('FormItem', 'on-form-change', value);
  332. }
  333. },
  334. resizeTextarea () {
  335. const autosize = this.autosize;
  336. if (!autosize || this.type !== 'textarea') {
  337. return false;
  338. }
  339. const minRows = autosize.minRows;
  340. const maxRows = autosize.maxRows;
  341. this.textareaStyles = calcTextareaHeight(this.$refs.textarea, minRows, maxRows);
  342. },
  343. focus () {
  344. if (this.type === 'textarea') {
  345. this.$refs.textarea.focus();
  346. } else {
  347. this.$refs.input.focus();
  348. }
  349. },
  350. blur () {
  351. if (this.type === 'textarea') {
  352. this.$refs.textarea.blur();
  353. } else {
  354. this.$refs.input.blur();
  355. }
  356. },
  357. handleClear () {
  358. const e = { target: { value: '' } };
  359. this.$emit('input', '');
  360. this.setCurrentValue('');
  361. this.$emit('on-change', e);
  362. this.$emit('on-clear');
  363. },
  364. handleSearch () {
  365. if (this.itemDisabled) return false;
  366. this.$refs.input.focus();
  367. this.$emit('on-search', this.currentValue);
  368. },
  369. handleToggleShowPassword () {
  370. if (this.itemDisabled) return false;
  371. this.showPassword = !this.showPassword;
  372. this.focus();
  373. const len = this.currentValue.length;
  374. setTimeout(() => {
  375. this.$refs.input.setSelectionRange(len, len);
  376. }, 0);
  377. }
  378. },
  379. watch: {
  380. value (val) {
  381. this.setCurrentValue(val);
  382. }
  383. },
  384. mounted () {
  385. this.slotReady = true;
  386. this.resizeTextarea();
  387. }
  388. };
  389. </script>