input-number.vue 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. <template>
  2. <div
  3. @dragstart.prevent
  4. :class="[
  5. 'el-input-number',
  6. inputNumberSize ? 'el-input-number--' + inputNumberSize : '',
  7. { 'is-disabled': inputNumberDisabled },
  8. { 'is-without-controls': !controls },
  9. { 'is-controls-right': controlsAtRight }
  10. ]">
  11. <span
  12. class="el-input-number__decrease"
  13. role="button"
  14. v-if="controls"
  15. v-repeat-click="decrease"
  16. :class="{'is-disabled': minDisabled}"
  17. @keydown.enter="decrease">
  18. <i :class="`el-icon-${controlsAtRight ? 'arrow-down' : 'minus'}`"></i>
  19. </span>
  20. <span
  21. class="el-input-number__increase"
  22. role="button"
  23. v-if="controls"
  24. v-repeat-click="increase"
  25. :class="{'is-disabled': maxDisabled}"
  26. @keydown.enter="increase">
  27. <i :class="`el-icon-${controlsAtRight ? 'arrow-up' : 'plus'}`"></i>
  28. </span>
  29. <el-input
  30. ref="input"
  31. :value="displayValue"
  32. :placeholder="placeholder"
  33. :disabled="inputNumberDisabled"
  34. :size="inputNumberSize"
  35. :max="max"
  36. :min="min"
  37. :name="name"
  38. :label="label"
  39. @keydown.up.native.prevent="increase"
  40. @keydown.down.native.prevent="decrease"
  41. @blur="handleBlur"
  42. @focus="handleFocus"
  43. @input="handleInput"
  44. @change="handleInputChange">
  45. </el-input>
  46. </div>
  47. </template>
  48. <script>
  49. import ElInput from 'element-ui/packages/input';
  50. import Focus from 'element-ui/src/mixins/focus';
  51. import RepeatClick from 'element-ui/src/directives/repeat-click';
  52. export default {
  53. name: 'ElInputNumber',
  54. mixins: [Focus('input')],
  55. inject: {
  56. elForm: {
  57. default: ''
  58. },
  59. elFormItem: {
  60. default: ''
  61. }
  62. },
  63. directives: {
  64. repeatClick: RepeatClick
  65. },
  66. components: {
  67. ElInput
  68. },
  69. props: {
  70. step: {
  71. type: Number,
  72. default: 1
  73. },
  74. stepStrictly: {
  75. type: Boolean,
  76. default: false
  77. },
  78. max: {
  79. type: Number,
  80. default: Infinity
  81. },
  82. min: {
  83. type: Number,
  84. default: -Infinity
  85. },
  86. value: {},
  87. disabled: Boolean,
  88. size: String,
  89. controls: {
  90. type: Boolean,
  91. default: true
  92. },
  93. controlsPosition: {
  94. type: String,
  95. default: ''
  96. },
  97. name: String,
  98. label: String,
  99. placeholder: String,
  100. precision: {
  101. type: Number,
  102. validator(val) {
  103. return val >= 0 && val === parseInt(val, 10);
  104. }
  105. }
  106. },
  107. data() {
  108. return {
  109. currentValue: 0,
  110. userInput: null
  111. };
  112. },
  113. watch: {
  114. value: {
  115. immediate: true,
  116. handler(value) {
  117. let newVal = value === undefined ? value : Number(value);
  118. if (newVal !== undefined) {
  119. if (isNaN(newVal)) {
  120. return;
  121. }
  122. if (this.stepStrictly) {
  123. const stepPrecision = this.getPrecision(this.step);
  124. const precisionFactor = Math.pow(10, stepPrecision);
  125. newVal = Math.round(newVal / this.step) * precisionFactor * this.step / precisionFactor;
  126. }
  127. if (this.precision !== undefined) {
  128. newVal = this.toPrecision(newVal, this.precision);
  129. }
  130. }
  131. if (newVal >= this.max) newVal = this.max;
  132. if (newVal <= this.min) newVal = this.min;
  133. this.currentValue = newVal;
  134. this.userInput = null;
  135. this.$emit('input', newVal);
  136. }
  137. }
  138. },
  139. computed: {
  140. minDisabled() {
  141. return this._decrease(this.value, this.step) < this.min;
  142. },
  143. maxDisabled() {
  144. return this._increase(this.value, this.step) > this.max;
  145. },
  146. numPrecision() {
  147. const { value, step, getPrecision, precision } = this;
  148. const stepPrecision = getPrecision(step);
  149. if (precision !== undefined) {
  150. if (stepPrecision > precision) {
  151. console.warn('[Element Warn][InputNumber]precision should not be less than the decimal places of step');
  152. }
  153. return precision;
  154. } else {
  155. return Math.max(getPrecision(value), stepPrecision);
  156. }
  157. },
  158. controlsAtRight() {
  159. return this.controls && this.controlsPosition === 'right';
  160. },
  161. _elFormItemSize() {
  162. return (this.elFormItem || {}).elFormItemSize;
  163. },
  164. inputNumberSize() {
  165. return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
  166. },
  167. inputNumberDisabled() {
  168. return this.disabled || !!(this.elForm || {}).disabled;
  169. },
  170. displayValue() {
  171. if (this.userInput !== null) {
  172. return this.userInput;
  173. }
  174. let currentValue = this.currentValue;
  175. if (typeof currentValue === 'number') {
  176. if (this.stepStrictly) {
  177. const stepPrecision = this.getPrecision(this.step);
  178. const precisionFactor = Math.pow(10, stepPrecision);
  179. currentValue = Math.round(currentValue / this.step) * precisionFactor * this.step / precisionFactor;
  180. }
  181. if (this.precision !== undefined) {
  182. currentValue = currentValue.toFixed(this.precision);
  183. }
  184. }
  185. return currentValue;
  186. }
  187. },
  188. methods: {
  189. toPrecision(num, precision) {
  190. if (precision === undefined) precision = this.numPrecision;
  191. return parseFloat(Math.round(num * Math.pow(10, precision)) / Math.pow(10, precision));
  192. },
  193. getPrecision(value) {
  194. if (value === undefined) return 0;
  195. const valueString = value.toString();
  196. const dotPosition = valueString.indexOf('.');
  197. let precision = 0;
  198. if (dotPosition !== -1) {
  199. precision = valueString.length - dotPosition - 1;
  200. }
  201. return precision;
  202. },
  203. _increase(val, step) {
  204. if (typeof val !== 'number' && val !== undefined) return this.currentValue;
  205. const precisionFactor = Math.pow(10, this.numPrecision);
  206. // Solve the accuracy problem of JS decimal calculation by converting the value to integer.
  207. return this.toPrecision((precisionFactor * val + precisionFactor * step) / precisionFactor);
  208. },
  209. _decrease(val, step) {
  210. if (typeof val !== 'number' && val !== undefined) return this.currentValue;
  211. const precisionFactor = Math.pow(10, this.numPrecision);
  212. return this.toPrecision((precisionFactor * val - precisionFactor * step) / precisionFactor);
  213. },
  214. increase() {
  215. if (this.inputNumberDisabled || this.maxDisabled) return;
  216. const value = this.value || 0;
  217. const newVal = this._increase(value, this.step);
  218. this.setCurrentValue(newVal);
  219. },
  220. decrease() {
  221. if (this.inputNumberDisabled || this.minDisabled) return;
  222. const value = this.value || 0;
  223. const newVal = this._decrease(value, this.step);
  224. this.setCurrentValue(newVal);
  225. },
  226. handleBlur(event) {
  227. this.$emit('blur', event);
  228. },
  229. handleFocus(event) {
  230. this.$emit('focus', event);
  231. },
  232. setCurrentValue(newVal) {
  233. const oldVal = this.currentValue;
  234. if (typeof newVal === 'number' && this.precision !== undefined) {
  235. newVal = this.toPrecision(newVal, this.precision);
  236. }
  237. if (newVal >= this.max) newVal = this.max;
  238. if (newVal <= this.min) newVal = this.min;
  239. if (oldVal === newVal) return;
  240. this.userInput = null;
  241. this.$emit('input', newVal);
  242. this.$emit('change', newVal, oldVal);
  243. this.currentValue = newVal;
  244. },
  245. handleInput(value) {
  246. this.userInput = value;
  247. },
  248. handleInputChange(value) {
  249. const newVal = value === '' ? undefined : Number(value);
  250. if (!isNaN(newVal) || value === '') {
  251. this.setCurrentValue(newVal);
  252. }
  253. this.userInput = null;
  254. },
  255. select() {
  256. this.$refs.input.select();
  257. }
  258. },
  259. mounted() {
  260. let innerInput = this.$refs.input.$refs.input;
  261. innerInput.setAttribute('role', 'spinbutton');
  262. innerInput.setAttribute('aria-valuemax', this.max);
  263. innerInput.setAttribute('aria-valuemin', this.min);
  264. innerInput.setAttribute('aria-valuenow', this.currentValue);
  265. innerInput.setAttribute('aria-disabled', this.inputNumberDisabled);
  266. },
  267. updated() {
  268. if (!this.$refs || !this.$refs.input) return;
  269. const innerInput = this.$refs.input.$refs.input;
  270. innerInput.setAttribute('aria-valuenow', this.currentValue);
  271. }
  272. };
  273. </script>