index.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. import _mergeJSXProps2 from "@vue/babel-helper-vue-jsx-merge-props";
  2. import _mergeJSXProps from "@vue/babel-helper-vue-jsx-merge-props";
  3. import _extends from "@babel/runtime/helpers/esm/extends";
  4. // Utils
  5. import { resetScroll } from '../utils/dom/reset-scroll';
  6. import { formatNumber } from '../utils/format/number';
  7. import { preventDefault } from '../utils/dom/event';
  8. import { isDef, addUnit, isObject, isPromise, isFunction, createNamespace } from '../utils'; // Components
  9. import Icon from '../icon';
  10. import Cell from '../cell';
  11. import { cellProps } from '../cell/shared';
  12. var _createNamespace = createNamespace('field'),
  13. createComponent = _createNamespace[0],
  14. bem = _createNamespace[1];
  15. export default createComponent({
  16. inheritAttrs: false,
  17. provide: function provide() {
  18. return {
  19. vanField: this
  20. };
  21. },
  22. inject: {
  23. vanForm: {
  24. default: null
  25. }
  26. },
  27. props: _extends({}, cellProps, {
  28. name: String,
  29. rules: Array,
  30. disabled: {
  31. type: Boolean,
  32. default: null
  33. },
  34. readonly: {
  35. type: Boolean,
  36. default: null
  37. },
  38. autosize: [Boolean, Object],
  39. leftIcon: String,
  40. rightIcon: String,
  41. clearable: Boolean,
  42. formatter: Function,
  43. maxlength: [Number, String],
  44. labelWidth: [Number, String],
  45. labelClass: null,
  46. labelAlign: String,
  47. inputAlign: String,
  48. placeholder: String,
  49. errorMessage: String,
  50. errorMessageAlign: String,
  51. showWordLimit: Boolean,
  52. value: {
  53. type: [Number, String],
  54. default: ''
  55. },
  56. type: {
  57. type: String,
  58. default: 'text'
  59. },
  60. error: {
  61. type: Boolean,
  62. default: null
  63. },
  64. colon: {
  65. type: Boolean,
  66. default: null
  67. },
  68. clearTrigger: {
  69. type: String,
  70. default: 'focus'
  71. },
  72. formatTrigger: {
  73. type: String,
  74. default: 'onChange'
  75. }
  76. }),
  77. data: function data() {
  78. return {
  79. focused: false,
  80. validateFailed: false,
  81. validateMessage: ''
  82. };
  83. },
  84. watch: {
  85. value: function value() {
  86. this.updateValue(this.value);
  87. this.resetValidation();
  88. this.validateWithTrigger('onChange');
  89. this.$nextTick(this.adjustSize);
  90. }
  91. },
  92. mounted: function mounted() {
  93. this.updateValue(this.value, this.formatTrigger);
  94. this.$nextTick(this.adjustSize);
  95. if (this.vanForm) {
  96. this.vanForm.addField(this);
  97. }
  98. },
  99. beforeDestroy: function beforeDestroy() {
  100. if (this.vanForm) {
  101. this.vanForm.removeField(this);
  102. }
  103. },
  104. computed: {
  105. showClear: function showClear() {
  106. var readonly = this.getProp('readonly');
  107. if (this.clearable && !readonly) {
  108. var hasValue = isDef(this.value) && this.value !== '';
  109. var trigger = this.clearTrigger === 'always' || this.clearTrigger === 'focus' && this.focused;
  110. return hasValue && trigger;
  111. }
  112. },
  113. showError: function showError() {
  114. if (this.error !== null) {
  115. return this.error;
  116. }
  117. if (this.vanForm && this.vanForm.showError && this.validateFailed) {
  118. return true;
  119. }
  120. },
  121. listeners: function listeners() {
  122. return _extends({}, this.$listeners, {
  123. blur: this.onBlur,
  124. focus: this.onFocus,
  125. input: this.onInput,
  126. click: this.onClickInput,
  127. keypress: this.onKeypress
  128. });
  129. },
  130. labelStyle: function labelStyle() {
  131. var labelWidth = this.getProp('labelWidth');
  132. if (labelWidth) {
  133. return {
  134. width: addUnit(labelWidth)
  135. };
  136. }
  137. },
  138. formValue: function formValue() {
  139. if (this.children && (this.$scopedSlots.input || this.$slots.input)) {
  140. return this.children.value;
  141. }
  142. return this.value;
  143. }
  144. },
  145. methods: {
  146. // @exposed-api
  147. focus: function focus() {
  148. if (this.$refs.input) {
  149. this.$refs.input.focus();
  150. }
  151. },
  152. // @exposed-api
  153. blur: function blur() {
  154. if (this.$refs.input) {
  155. this.$refs.input.blur();
  156. }
  157. },
  158. runValidator: function runValidator(value, rule) {
  159. return new Promise(function (resolve) {
  160. var returnVal = rule.validator(value, rule);
  161. if (isPromise(returnVal)) {
  162. return returnVal.then(resolve);
  163. }
  164. resolve(returnVal);
  165. });
  166. },
  167. isEmptyValue: function isEmptyValue(value) {
  168. if (Array.isArray(value)) {
  169. return !value.length;
  170. }
  171. if (value === 0) {
  172. return false;
  173. }
  174. return !value;
  175. },
  176. runSyncRule: function runSyncRule(value, rule) {
  177. if (rule.required && this.isEmptyValue(value)) {
  178. return false;
  179. }
  180. if (rule.pattern && !rule.pattern.test(value)) {
  181. return false;
  182. }
  183. return true;
  184. },
  185. getRuleMessage: function getRuleMessage(value, rule) {
  186. var message = rule.message;
  187. if (isFunction(message)) {
  188. return message(value, rule);
  189. }
  190. return message;
  191. },
  192. runRules: function runRules(rules) {
  193. var _this = this;
  194. return rules.reduce(function (promise, rule) {
  195. return promise.then(function () {
  196. if (_this.validateFailed) {
  197. return;
  198. }
  199. var value = _this.formValue;
  200. if (rule.formatter) {
  201. value = rule.formatter(value, rule);
  202. }
  203. if (!_this.runSyncRule(value, rule)) {
  204. _this.validateFailed = true;
  205. _this.validateMessage = _this.getRuleMessage(value, rule);
  206. return;
  207. }
  208. if (rule.validator) {
  209. return _this.runValidator(value, rule).then(function (result) {
  210. if (result === false) {
  211. _this.validateFailed = true;
  212. _this.validateMessage = _this.getRuleMessage(value, rule);
  213. }
  214. });
  215. }
  216. });
  217. }, Promise.resolve());
  218. },
  219. validate: function validate(rules) {
  220. var _this2 = this;
  221. if (rules === void 0) {
  222. rules = this.rules;
  223. }
  224. return new Promise(function (resolve) {
  225. if (!rules) {
  226. resolve();
  227. }
  228. _this2.resetValidation();
  229. _this2.runRules(rules).then(function () {
  230. if (_this2.validateFailed) {
  231. resolve({
  232. name: _this2.name,
  233. message: _this2.validateMessage
  234. });
  235. } else {
  236. resolve();
  237. }
  238. });
  239. });
  240. },
  241. validateWithTrigger: function validateWithTrigger(trigger) {
  242. if (this.vanForm && this.rules) {
  243. var defaultTrigger = this.vanForm.validateTrigger === trigger;
  244. var rules = this.rules.filter(function (rule) {
  245. if (rule.trigger) {
  246. return rule.trigger === trigger;
  247. }
  248. return defaultTrigger;
  249. });
  250. if (rules.length) {
  251. this.validate(rules);
  252. }
  253. }
  254. },
  255. resetValidation: function resetValidation() {
  256. if (this.validateFailed) {
  257. this.validateFailed = false;
  258. this.validateMessage = '';
  259. }
  260. },
  261. updateValue: function updateValue(value, trigger) {
  262. if (trigger === void 0) {
  263. trigger = 'onChange';
  264. }
  265. value = isDef(value) ? String(value) : ''; // native maxlength have incorrect line-break counting
  266. // see: https://github.com/youzan/vant/issues/5033
  267. var maxlength = this.maxlength;
  268. if (isDef(maxlength) && value.length > maxlength) {
  269. if (this.value && this.value.length === +maxlength) {
  270. value = this.value;
  271. } else {
  272. value = value.slice(0, maxlength);
  273. }
  274. }
  275. if (this.type === 'number' || this.type === 'digit') {
  276. var isNumber = this.type === 'number';
  277. value = formatNumber(value, isNumber, isNumber);
  278. }
  279. if (this.formatter && trigger === this.formatTrigger) {
  280. value = this.formatter(value);
  281. }
  282. var input = this.$refs.input;
  283. if (input && value !== input.value) {
  284. input.value = value;
  285. }
  286. if (value !== this.value) {
  287. this.$emit('input', value);
  288. }
  289. },
  290. onInput: function onInput(event) {
  291. // not update v-model when composing
  292. if (event.target.composing) {
  293. return;
  294. }
  295. this.updateValue(event.target.value);
  296. },
  297. onFocus: function onFocus(event) {
  298. this.focused = true;
  299. this.$emit('focus', event); // readonly not work in lagacy mobile safari
  300. /* istanbul ignore if */
  301. var readonly = this.getProp('readonly');
  302. if (readonly) {
  303. this.blur();
  304. }
  305. },
  306. onBlur: function onBlur(event) {
  307. this.focused = false;
  308. this.updateValue(this.value, 'onBlur');
  309. this.$emit('blur', event);
  310. this.validateWithTrigger('onBlur');
  311. resetScroll();
  312. },
  313. onClick: function onClick(event) {
  314. this.$emit('click', event);
  315. },
  316. onClickInput: function onClickInput(event) {
  317. this.$emit('click-input', event);
  318. },
  319. onClickLeftIcon: function onClickLeftIcon(event) {
  320. this.$emit('click-left-icon', event);
  321. },
  322. onClickRightIcon: function onClickRightIcon(event) {
  323. this.$emit('click-right-icon', event);
  324. },
  325. onClear: function onClear(event) {
  326. preventDefault(event);
  327. this.$emit('input', '');
  328. this.$emit('clear', event);
  329. },
  330. onKeypress: function onKeypress(event) {
  331. var ENTER_CODE = 13;
  332. if (event.keyCode === ENTER_CODE) {
  333. var submitOnEnter = this.getProp('submitOnEnter');
  334. if (!submitOnEnter && this.type !== 'textarea') {
  335. preventDefault(event);
  336. } // trigger blur after click keyboard search button
  337. if (this.type === 'search') {
  338. this.blur();
  339. }
  340. }
  341. this.$emit('keypress', event);
  342. },
  343. adjustSize: function adjustSize() {
  344. var input = this.$refs.input;
  345. if (!(this.type === 'textarea' && this.autosize) || !input) {
  346. return;
  347. }
  348. input.style.height = 'auto';
  349. var height = input.scrollHeight;
  350. if (isObject(this.autosize)) {
  351. var _this$autosize = this.autosize,
  352. maxHeight = _this$autosize.maxHeight,
  353. minHeight = _this$autosize.minHeight;
  354. if (maxHeight) {
  355. height = Math.min(height, maxHeight);
  356. }
  357. if (minHeight) {
  358. height = Math.max(height, minHeight);
  359. }
  360. }
  361. if (height) {
  362. input.style.height = height + 'px';
  363. }
  364. },
  365. genInput: function genInput() {
  366. var h = this.$createElement;
  367. var type = this.type;
  368. var disabled = this.getProp('disabled');
  369. var readonly = this.getProp('readonly');
  370. var inputSlot = this.slots('input');
  371. var inputAlign = this.getProp('inputAlign');
  372. if (inputSlot) {
  373. return h("div", {
  374. "class": bem('control', [inputAlign, 'custom']),
  375. "on": {
  376. "click": this.onClickInput
  377. }
  378. }, [inputSlot]);
  379. }
  380. var inputProps = {
  381. ref: 'input',
  382. class: bem('control', inputAlign),
  383. domProps: {
  384. value: this.value
  385. },
  386. attrs: _extends({}, this.$attrs, {
  387. name: this.name,
  388. disabled: disabled,
  389. readonly: readonly,
  390. placeholder: this.placeholder
  391. }),
  392. on: this.listeners,
  393. // add model directive to skip IME composition
  394. directives: [{
  395. name: 'model',
  396. value: this.value
  397. }]
  398. };
  399. if (type === 'textarea') {
  400. return h("textarea", _mergeJSXProps([{}, inputProps]));
  401. }
  402. var inputType = type;
  403. var inputMode; // type="number" is weired in iOS, and can't prevent dot in Android
  404. // so use inputmode to set keyboard in mordern browers
  405. if (type === 'number') {
  406. inputType = 'text';
  407. inputMode = 'decimal';
  408. }
  409. if (type === 'digit') {
  410. inputType = 'tel';
  411. inputMode = 'numeric';
  412. }
  413. return h("input", _mergeJSXProps2([{
  414. "attrs": {
  415. "type": inputType,
  416. "inputmode": inputMode
  417. }
  418. }, inputProps]));
  419. },
  420. genLeftIcon: function genLeftIcon() {
  421. var h = this.$createElement;
  422. var showLeftIcon = this.slots('left-icon') || this.leftIcon;
  423. if (showLeftIcon) {
  424. return h("div", {
  425. "class": bem('left-icon'),
  426. "on": {
  427. "click": this.onClickLeftIcon
  428. }
  429. }, [this.slots('left-icon') || h(Icon, {
  430. "attrs": {
  431. "name": this.leftIcon,
  432. "classPrefix": this.iconPrefix
  433. }
  434. })]);
  435. }
  436. },
  437. genRightIcon: function genRightIcon() {
  438. var h = this.$createElement;
  439. var slots = this.slots;
  440. var showRightIcon = slots('right-icon') || this.rightIcon;
  441. if (showRightIcon) {
  442. return h("div", {
  443. "class": bem('right-icon'),
  444. "on": {
  445. "click": this.onClickRightIcon
  446. }
  447. }, [slots('right-icon') || h(Icon, {
  448. "attrs": {
  449. "name": this.rightIcon,
  450. "classPrefix": this.iconPrefix
  451. }
  452. })]);
  453. }
  454. },
  455. genWordLimit: function genWordLimit() {
  456. var h = this.$createElement;
  457. if (this.showWordLimit && this.maxlength) {
  458. var count = (this.value || '').length;
  459. return h("div", {
  460. "class": bem('word-limit')
  461. }, [h("span", {
  462. "class": bem('word-num')
  463. }, [count]), "/", this.maxlength]);
  464. }
  465. },
  466. genMessage: function genMessage() {
  467. var h = this.$createElement;
  468. if (this.vanForm && this.vanForm.showErrorMessage === false) {
  469. return;
  470. }
  471. var message = this.errorMessage || this.validateMessage;
  472. if (message) {
  473. var errorMessageAlign = this.getProp('errorMessageAlign');
  474. return h("div", {
  475. "class": bem('error-message', errorMessageAlign)
  476. }, [message]);
  477. }
  478. },
  479. getProp: function getProp(key) {
  480. if (isDef(this[key])) {
  481. return this[key];
  482. }
  483. if (this.vanForm && isDef(this.vanForm[key])) {
  484. return this.vanForm[key];
  485. }
  486. },
  487. genLabel: function genLabel() {
  488. var h = this.$createElement;
  489. var colon = this.getProp('colon') ? ':' : '';
  490. if (this.slots('label')) {
  491. return [this.slots('label'), colon];
  492. }
  493. if (this.label) {
  494. return h("span", [this.label + colon]);
  495. }
  496. }
  497. },
  498. render: function render() {
  499. var _bem;
  500. var h = arguments[0];
  501. var slots = this.slots;
  502. var disabled = this.getProp('disabled');
  503. var labelAlign = this.getProp('labelAlign');
  504. var scopedSlots = {
  505. icon: this.genLeftIcon
  506. };
  507. var Label = this.genLabel();
  508. if (Label) {
  509. scopedSlots.title = function () {
  510. return Label;
  511. };
  512. }
  513. var extra = this.slots('extra');
  514. if (extra) {
  515. scopedSlots.extra = function () {
  516. return extra;
  517. };
  518. }
  519. return h(Cell, {
  520. "attrs": {
  521. "icon": this.leftIcon,
  522. "size": this.size,
  523. "center": this.center,
  524. "border": this.border,
  525. "isLink": this.isLink,
  526. "required": this.required,
  527. "clickable": this.clickable,
  528. "titleStyle": this.labelStyle,
  529. "valueClass": bem('value'),
  530. "titleClass": [bem('label', labelAlign), this.labelClass],
  531. "arrowDirection": this.arrowDirection
  532. },
  533. "scopedSlots": scopedSlots,
  534. "class": bem((_bem = {
  535. error: this.showError,
  536. disabled: disabled
  537. }, _bem["label-" + labelAlign] = labelAlign, _bem['min-height'] = this.type === 'textarea' && !this.autosize, _bem)),
  538. "on": {
  539. "click": this.onClick
  540. }
  541. }, [h("div", {
  542. "class": bem('body')
  543. }, [this.genInput(), this.showClear && h(Icon, {
  544. "attrs": {
  545. "name": "clear"
  546. },
  547. "class": bem('clear'),
  548. "on": {
  549. "touchstart": this.onClear
  550. }
  551. }), this.genRightIcon(), slots('button') && h("div", {
  552. "class": bem('button')
  553. }, [slots('button')])]), this.genWordLimit(), this.genMessage()]);
  554. }
  555. });