index.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. import _mergeJSXProps2 from "@vue/babel-helper-vue-jsx-merge-props";
  2. import _mergeJSXProps from "@vue/babel-helper-vue-jsx-merge-props";
  3. import { createNamespace, isDef, addUnit } from '../utils';
  4. import { resetScroll } from '../utils/dom/reset-scroll';
  5. import { preventDefault } from '../utils/dom/event';
  6. import { formatNumber as _formatNumber } from '../utils/format/number';
  7. import { isNaN } from '../utils/validate/number';
  8. import { FieldMixin } from '../mixins/field';
  9. var _createNamespace = createNamespace('stepper'),
  10. createComponent = _createNamespace[0],
  11. bem = _createNamespace[1];
  12. var LONG_PRESS_START_TIME = 600;
  13. var LONG_PRESS_INTERVAL = 200;
  14. function equal(value1, value2) {
  15. return String(value1) === String(value2);
  16. } // add num and avoid float number
  17. function add(num1, num2) {
  18. var cardinal = Math.pow(10, 10);
  19. return Math.round((num1 + num2) * cardinal) / cardinal;
  20. }
  21. export default createComponent({
  22. mixins: [FieldMixin],
  23. props: {
  24. value: null,
  25. theme: String,
  26. integer: Boolean,
  27. disabled: Boolean,
  28. allowEmpty: Boolean,
  29. inputWidth: [Number, String],
  30. buttonSize: [Number, String],
  31. asyncChange: Boolean,
  32. placeholder: String,
  33. disablePlus: Boolean,
  34. disableMinus: Boolean,
  35. disableInput: Boolean,
  36. decimalLength: [Number, String],
  37. name: {
  38. type: [Number, String],
  39. default: ''
  40. },
  41. min: {
  42. type: [Number, String],
  43. default: 1
  44. },
  45. max: {
  46. type: [Number, String],
  47. default: Infinity
  48. },
  49. step: {
  50. type: [Number, String],
  51. default: 1
  52. },
  53. defaultValue: {
  54. type: [Number, String],
  55. default: 1
  56. },
  57. showPlus: {
  58. type: Boolean,
  59. default: true
  60. },
  61. showMinus: {
  62. type: Boolean,
  63. default: true
  64. },
  65. showInput: {
  66. type: Boolean,
  67. default: true
  68. },
  69. longPress: {
  70. type: Boolean,
  71. default: true
  72. }
  73. },
  74. data: function data() {
  75. var _this$value;
  76. var defaultValue = (_this$value = this.value) != null ? _this$value : this.defaultValue;
  77. var value = this.format(defaultValue);
  78. if (!equal(value, this.value)) {
  79. this.$emit('input', value);
  80. }
  81. return {
  82. currentValue: value
  83. };
  84. },
  85. computed: {
  86. minusDisabled: function minusDisabled() {
  87. return this.disabled || this.disableMinus || this.currentValue <= +this.min;
  88. },
  89. plusDisabled: function plusDisabled() {
  90. return this.disabled || this.disablePlus || this.currentValue >= +this.max;
  91. },
  92. inputStyle: function inputStyle() {
  93. var style = {};
  94. if (this.inputWidth) {
  95. style.width = addUnit(this.inputWidth);
  96. }
  97. if (this.buttonSize) {
  98. style.height = addUnit(this.buttonSize);
  99. }
  100. return style;
  101. },
  102. buttonStyle: function buttonStyle() {
  103. if (this.buttonSize) {
  104. var size = addUnit(this.buttonSize);
  105. return {
  106. width: size,
  107. height: size
  108. };
  109. }
  110. }
  111. },
  112. watch: {
  113. max: 'check',
  114. min: 'check',
  115. integer: 'check',
  116. decimalLength: 'check',
  117. value: function value(val) {
  118. if (!equal(val, this.currentValue)) {
  119. this.currentValue = this.format(val);
  120. }
  121. },
  122. currentValue: function currentValue(val) {
  123. this.$emit('input', val);
  124. this.$emit('change', val, {
  125. name: this.name
  126. });
  127. }
  128. },
  129. methods: {
  130. check: function check() {
  131. var val = this.format(this.currentValue);
  132. if (!equal(val, this.currentValue)) {
  133. this.currentValue = val;
  134. }
  135. },
  136. // formatNumber illegal characters
  137. formatNumber: function formatNumber(value) {
  138. return _formatNumber(String(value), !this.integer);
  139. },
  140. format: function format(value) {
  141. if (this.allowEmpty && value === '') {
  142. return value;
  143. }
  144. value = this.formatNumber(value); // format range
  145. value = value === '' ? 0 : +value;
  146. value = isNaN(value) ? this.min : value;
  147. value = Math.max(Math.min(this.max, value), this.min); // format decimal
  148. if (isDef(this.decimalLength)) {
  149. value = value.toFixed(this.decimalLength);
  150. }
  151. return value;
  152. },
  153. onInput: function onInput(event) {
  154. var value = event.target.value;
  155. var formatted = this.formatNumber(value); // limit max decimal length
  156. if (isDef(this.decimalLength) && formatted.indexOf('.') !== -1) {
  157. var pair = formatted.split('.');
  158. formatted = pair[0] + "." + pair[1].slice(0, this.decimalLength);
  159. }
  160. if (!equal(value, formatted)) {
  161. event.target.value = formatted;
  162. } // perfer number type
  163. if (formatted === String(+formatted)) {
  164. formatted = +formatted;
  165. }
  166. this.emitChange(formatted);
  167. },
  168. emitChange: function emitChange(value) {
  169. if (this.asyncChange) {
  170. this.$emit('input', value);
  171. this.$emit('change', value, {
  172. name: this.name
  173. });
  174. } else {
  175. this.currentValue = value;
  176. }
  177. },
  178. onChange: function onChange() {
  179. var type = this.type;
  180. if (this[type + "Disabled"]) {
  181. this.$emit('overlimit', type);
  182. return;
  183. }
  184. var diff = type === 'minus' ? -this.step : +this.step;
  185. var value = this.format(add(+this.currentValue, diff));
  186. this.emitChange(value);
  187. this.$emit(type);
  188. },
  189. onFocus: function onFocus(event) {
  190. // readonly not work in lagacy mobile safari
  191. if (this.disableInput && this.$refs.input) {
  192. this.$refs.input.blur();
  193. } else {
  194. this.$emit('focus', event);
  195. }
  196. },
  197. onBlur: function onBlur(event) {
  198. var value = this.format(event.target.value);
  199. event.target.value = value;
  200. this.currentValue = value;
  201. this.$emit('blur', event);
  202. resetScroll();
  203. },
  204. longPressStep: function longPressStep() {
  205. var _this = this;
  206. this.longPressTimer = setTimeout(function () {
  207. _this.onChange();
  208. _this.longPressStep(_this.type);
  209. }, LONG_PRESS_INTERVAL);
  210. },
  211. onTouchStart: function onTouchStart() {
  212. var _this2 = this;
  213. if (!this.longPress) {
  214. return;
  215. }
  216. clearTimeout(this.longPressTimer);
  217. this.isLongPress = false;
  218. this.longPressTimer = setTimeout(function () {
  219. _this2.isLongPress = true;
  220. _this2.onChange();
  221. _this2.longPressStep();
  222. }, LONG_PRESS_START_TIME);
  223. },
  224. onTouchEnd: function onTouchEnd(event) {
  225. if (!this.longPress) {
  226. return;
  227. }
  228. clearTimeout(this.longPressTimer);
  229. if (this.isLongPress) {
  230. preventDefault(event);
  231. }
  232. },
  233. onMousedown: function onMousedown(event) {
  234. // fix mobile safari page scroll down issue
  235. // see: https://github.com/youzan/vant/issues/7690
  236. if (this.disableInput) {
  237. event.preventDefault();
  238. }
  239. }
  240. },
  241. render: function render() {
  242. var _this3 = this;
  243. var h = arguments[0];
  244. var createListeners = function createListeners(type) {
  245. return {
  246. on: {
  247. click: function click(e) {
  248. // disable double tap scrolling on mobile safari
  249. e.preventDefault();
  250. _this3.type = type;
  251. _this3.onChange();
  252. },
  253. touchstart: function touchstart() {
  254. _this3.type = type;
  255. _this3.onTouchStart();
  256. },
  257. touchend: _this3.onTouchEnd,
  258. touchcancel: _this3.onTouchEnd
  259. }
  260. };
  261. };
  262. return h("div", {
  263. "class": bem([this.theme])
  264. }, [h("button", _mergeJSXProps([{
  265. "directives": [{
  266. name: "show",
  267. value: this.showMinus
  268. }],
  269. "attrs": {
  270. "type": "button"
  271. },
  272. "style": this.buttonStyle,
  273. "class": bem('minus', {
  274. disabled: this.minusDisabled
  275. })
  276. }, createListeners('minus')])), h("input", {
  277. "directives": [{
  278. name: "show",
  279. value: this.showInput
  280. }],
  281. "ref": "input",
  282. "attrs": {
  283. "type": this.integer ? 'tel' : 'text',
  284. "role": "spinbutton",
  285. "disabled": this.disabled,
  286. "readonly": this.disableInput,
  287. "inputmode": this.integer ? 'numeric' : 'decimal',
  288. "placeholder": this.placeholder,
  289. "aria-valuemax": this.max,
  290. "aria-valuemin": this.min,
  291. "aria-valuenow": this.currentValue
  292. },
  293. "class": bem('input'),
  294. "domProps": {
  295. "value": this.currentValue
  296. },
  297. "style": this.inputStyle,
  298. "on": {
  299. "input": this.onInput,
  300. "focus": this.onFocus,
  301. "blur": this.onBlur,
  302. "mousedown": this.onMousedown
  303. }
  304. }), h("button", _mergeJSXProps2([{
  305. "directives": [{
  306. name: "show",
  307. value: this.showPlus
  308. }],
  309. "attrs": {
  310. "type": "button"
  311. },
  312. "style": this.buttonStyle,
  313. "class": bem('plus', {
  314. disabled: this.plusDisabled
  315. })
  316. }, createListeners('plus')]))]);
  317. }
  318. });