index.js 16 KB

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