input-number2.mjs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. import { defineComponent, ref, reactive, computed, watch, onMounted, onUpdated, openBlock, createElementBlock, normalizeClass, unref, withModifiers, withDirectives, withKeys, renderSlot, createVNode, withCtx, createBlock, createCommentVNode, createSlots } from 'vue';
  2. import { isNil } from 'lodash-unified';
  3. import { ElInput } from '../../input/index.mjs';
  4. import { ElIcon } from '../../icon/index.mjs';
  5. import { ArrowDown, Minus, ArrowUp, Plus } from '@element-plus/icons-vue';
  6. import { inputNumberProps, inputNumberEmits } from './input-number.mjs';
  7. import _export_sfc from '../../../_virtual/plugin-vue_export-helper.mjs';
  8. import { vRepeatClick } from '../../../directives/repeat-click/index.mjs';
  9. import { getEventCode, getEventKey } from '../../../utils/dom/event.mjs';
  10. import { useLocale } from '../../../hooks/use-locale/index.mjs';
  11. import { useNamespace } from '../../../hooks/use-namespace/index.mjs';
  12. import { useFormItem } from '../../form/src/hooks/use-form-item.mjs';
  13. import { isNumber, isUndefined } from '../../../utils/types.mjs';
  14. import { debugWarn, throwError } from '../../../utils/error.mjs';
  15. import { useFormSize, useFormDisabled } from '../../form/src/hooks/use-form-common-props.mjs';
  16. import { UPDATE_MODEL_EVENT, INPUT_EVENT, CHANGE_EVENT } from '../../../constants/event.mjs';
  17. import { EVENT_CODE } from '../../../constants/aria.mjs';
  18. import { isString } from '@vue/shared';
  19. const _hoisted_1 = ["aria-label"];
  20. const _hoisted_2 = ["aria-label"];
  21. const _sfc_main = defineComponent({
  22. ...{
  23. name: "ElInputNumber"
  24. },
  25. __name: "input-number",
  26. props: inputNumberProps,
  27. emits: inputNumberEmits,
  28. setup(__props, { expose: __expose, emit: __emit }) {
  29. const props = __props;
  30. const emit = __emit;
  31. const { t } = useLocale();
  32. const ns = useNamespace("input-number");
  33. const input = ref();
  34. const data = reactive({
  35. currentValue: props.modelValue,
  36. userInput: null
  37. });
  38. const { formItem } = useFormItem();
  39. const minDisabled = computed(
  40. () => isNumber(props.modelValue) && props.modelValue <= props.min
  41. );
  42. const maxDisabled = computed(
  43. () => isNumber(props.modelValue) && props.modelValue >= props.max
  44. );
  45. const numPrecision = computed(() => {
  46. const stepPrecision = getPrecision(props.step);
  47. if (!isUndefined(props.precision)) {
  48. if (stepPrecision > props.precision) {
  49. debugWarn(
  50. "InputNumber",
  51. "precision should not be less than the decimal places of step"
  52. );
  53. }
  54. return props.precision;
  55. } else {
  56. return Math.max(getPrecision(props.modelValue), stepPrecision);
  57. }
  58. });
  59. const controlsAtRight = computed(() => {
  60. return props.controls && props.controlsPosition === "right";
  61. });
  62. const inputNumberSize = useFormSize();
  63. const inputNumberDisabled = useFormDisabled();
  64. const displayValue = computed(() => {
  65. if (data.userInput !== null) {
  66. return data.userInput;
  67. }
  68. let currentValue = data.currentValue;
  69. if (isNil(currentValue))
  70. return "";
  71. if (isNumber(currentValue)) {
  72. if (Number.isNaN(currentValue))
  73. return "";
  74. if (!isUndefined(props.precision)) {
  75. currentValue = currentValue.toFixed(props.precision);
  76. }
  77. }
  78. return currentValue;
  79. });
  80. const toPrecision = (num, pre) => {
  81. if (isUndefined(pre))
  82. pre = numPrecision.value;
  83. if (pre === 0)
  84. return Math.round(num);
  85. let snum = String(num);
  86. const pointPos = snum.indexOf(".");
  87. if (pointPos === -1)
  88. return num;
  89. const nums = snum.replace(".", "").split("");
  90. const datum = nums[pointPos + pre];
  91. if (!datum)
  92. return num;
  93. const length = snum.length;
  94. if (snum.charAt(length - 1) === "5") {
  95. snum = `${snum.slice(0, Math.max(0, length - 1))}6`;
  96. }
  97. return Number.parseFloat(Number(snum).toFixed(pre));
  98. };
  99. const getPrecision = (value) => {
  100. if (isNil(value))
  101. return 0;
  102. const valueString = value.toString();
  103. const dotPosition = valueString.indexOf(".");
  104. let precision = 0;
  105. if (dotPosition !== -1) {
  106. precision = valueString.length - dotPosition - 1;
  107. }
  108. return precision;
  109. };
  110. const ensurePrecision = (val, coefficient = 1) => {
  111. if (!isNumber(val))
  112. return data.currentValue;
  113. if (val >= Number.MAX_SAFE_INTEGER && coefficient === 1) {
  114. debugWarn(
  115. "InputNumber",
  116. "The value has reached the maximum safe integer limit."
  117. );
  118. return val;
  119. } else if (val <= Number.MIN_SAFE_INTEGER && coefficient === -1) {
  120. debugWarn(
  121. "InputNumber",
  122. "The value has reached the minimum safe integer limit."
  123. );
  124. return val;
  125. }
  126. return toPrecision(val + props.step * coefficient);
  127. };
  128. const handleKeydown = (event) => {
  129. const code = getEventCode(event);
  130. const key = getEventKey(event);
  131. if (props.disabledScientific && ["e", "E"].includes(key)) {
  132. event.preventDefault();
  133. return;
  134. }
  135. switch (code) {
  136. case EVENT_CODE.up: {
  137. event.preventDefault();
  138. increase();
  139. break;
  140. }
  141. case EVENT_CODE.down: {
  142. event.preventDefault();
  143. decrease();
  144. break;
  145. }
  146. }
  147. };
  148. const increase = () => {
  149. if (props.readonly || inputNumberDisabled.value || maxDisabled.value)
  150. return;
  151. const value = Number(displayValue.value) || 0;
  152. const newVal = ensurePrecision(value);
  153. setCurrentValue(newVal);
  154. emit(INPUT_EVENT, data.currentValue);
  155. setCurrentValueToModelValue();
  156. };
  157. const decrease = () => {
  158. if (props.readonly || inputNumberDisabled.value || minDisabled.value)
  159. return;
  160. const value = Number(displayValue.value) || 0;
  161. const newVal = ensurePrecision(value, -1);
  162. setCurrentValue(newVal);
  163. emit(INPUT_EVENT, data.currentValue);
  164. setCurrentValueToModelValue();
  165. };
  166. const verifyValue = (value, update) => {
  167. const { max, min, step, precision, stepStrictly, valueOnClear } = props;
  168. if (max < min) {
  169. throwError("InputNumber", "min should not be greater than max.");
  170. }
  171. let newVal = Number(value);
  172. if (isNil(value) || Number.isNaN(newVal)) {
  173. return null;
  174. }
  175. if (value === "") {
  176. if (valueOnClear === null) {
  177. return null;
  178. }
  179. newVal = isString(valueOnClear) ? { min, max }[valueOnClear] : valueOnClear;
  180. }
  181. if (stepStrictly) {
  182. newVal = toPrecision(
  183. Math.round(toPrecision(newVal / step)) * step,
  184. precision
  185. );
  186. if (newVal !== value) {
  187. update && emit(UPDATE_MODEL_EVENT, newVal);
  188. }
  189. }
  190. if (!isUndefined(precision)) {
  191. newVal = toPrecision(newVal, precision);
  192. }
  193. if (newVal > max || newVal < min) {
  194. newVal = newVal > max ? max : min;
  195. update && emit(UPDATE_MODEL_EVENT, newVal);
  196. }
  197. return newVal;
  198. };
  199. const setCurrentValue = (value, emitChange = true) => {
  200. var _a;
  201. const oldVal = data.currentValue;
  202. const newVal = verifyValue(value);
  203. if (!emitChange) {
  204. emit(UPDATE_MODEL_EVENT, newVal);
  205. return;
  206. }
  207. data.userInput = null;
  208. if (oldVal === newVal && value)
  209. return;
  210. emit(UPDATE_MODEL_EVENT, newVal);
  211. if (oldVal !== newVal) {
  212. emit(CHANGE_EVENT, newVal, oldVal);
  213. }
  214. if (props.validateEvent) {
  215. (_a = formItem == null ? void 0 : formItem.validate) == null ? void 0 : _a.call(formItem, "change").catch((err) => debugWarn(err));
  216. }
  217. data.currentValue = newVal;
  218. };
  219. const handleInput = (value) => {
  220. data.userInput = value;
  221. const newVal = value === "" ? null : Number(value);
  222. emit(INPUT_EVENT, newVal);
  223. setCurrentValue(newVal, false);
  224. };
  225. const handleInputChange = (value) => {
  226. const newVal = value !== "" ? Number(value) : "";
  227. if (isNumber(newVal) && !Number.isNaN(newVal) || value === "") {
  228. setCurrentValue(newVal);
  229. }
  230. setCurrentValueToModelValue();
  231. data.userInput = null;
  232. };
  233. const focus = () => {
  234. var _a, _b;
  235. (_b = (_a = input.value) == null ? void 0 : _a.focus) == null ? void 0 : _b.call(_a);
  236. };
  237. const blur = () => {
  238. var _a, _b;
  239. (_b = (_a = input.value) == null ? void 0 : _a.blur) == null ? void 0 : _b.call(_a);
  240. };
  241. const handleFocus = (event) => {
  242. emit("focus", event);
  243. };
  244. const handleBlur = (event) => {
  245. var _a, _b;
  246. data.userInput = null;
  247. if (data.currentValue === null && ((_a = input.value) == null ? void 0 : _a.input)) {
  248. input.value.input.value = "";
  249. }
  250. emit("blur", event);
  251. if (props.validateEvent) {
  252. (_b = formItem == null ? void 0 : formItem.validate) == null ? void 0 : _b.call(formItem, "blur").catch((err) => debugWarn(err));
  253. }
  254. };
  255. const setCurrentValueToModelValue = () => {
  256. if (data.currentValue !== props.modelValue) {
  257. data.currentValue = props.modelValue;
  258. }
  259. };
  260. const handleWheel = (e) => {
  261. if (document.activeElement === e.target)
  262. e.preventDefault();
  263. };
  264. watch(
  265. () => props.modelValue,
  266. (value, oldValue) => {
  267. const newValue = verifyValue(value, true);
  268. if (data.userInput === null && newValue !== oldValue) {
  269. data.currentValue = newValue;
  270. }
  271. },
  272. { immediate: true }
  273. );
  274. watch(
  275. () => props.precision,
  276. () => {
  277. data.currentValue = verifyValue(props.modelValue);
  278. }
  279. );
  280. onMounted(() => {
  281. var _a;
  282. const { min, max, modelValue } = props;
  283. const innerInput = (_a = input.value) == null ? void 0 : _a.input;
  284. innerInput.setAttribute("role", "spinbutton");
  285. if (Number.isFinite(max)) {
  286. innerInput.setAttribute("aria-valuemax", String(max));
  287. } else {
  288. innerInput.removeAttribute("aria-valuemax");
  289. }
  290. if (Number.isFinite(min)) {
  291. innerInput.setAttribute("aria-valuemin", String(min));
  292. } else {
  293. innerInput.removeAttribute("aria-valuemin");
  294. }
  295. innerInput.setAttribute(
  296. "aria-valuenow",
  297. data.currentValue || data.currentValue === 0 ? String(data.currentValue) : ""
  298. );
  299. innerInput.setAttribute("aria-disabled", String(inputNumberDisabled.value));
  300. if (!isNumber(modelValue) && modelValue != null) {
  301. let val = Number(modelValue);
  302. if (Number.isNaN(val)) {
  303. val = null;
  304. }
  305. emit(UPDATE_MODEL_EVENT, val);
  306. }
  307. innerInput.addEventListener("wheel", handleWheel, { passive: false });
  308. });
  309. onUpdated(() => {
  310. var _a, _b;
  311. const innerInput = (_a = input.value) == null ? void 0 : _a.input;
  312. innerInput == null ? void 0 : innerInput.setAttribute("aria-valuenow", `${(_b = data.currentValue) != null ? _b : ""}`);
  313. });
  314. __expose({
  315. focus,
  316. blur
  317. });
  318. return (_ctx, _cache) => {
  319. return openBlock(), createElementBlock(
  320. "div",
  321. {
  322. class: normalizeClass([
  323. unref(ns).b(),
  324. unref(ns).m(unref(inputNumberSize)),
  325. unref(ns).is("disabled", unref(inputNumberDisabled)),
  326. unref(ns).is("without-controls", !_ctx.controls),
  327. unref(ns).is("controls-right", controlsAtRight.value),
  328. unref(ns).is(_ctx.align, !!_ctx.align)
  329. ]),
  330. onDragstart: _cache[0] || (_cache[0] = withModifiers(() => {
  331. }, ["prevent"]))
  332. },
  333. [
  334. _ctx.controls ? withDirectives((openBlock(), createElementBlock("span", {
  335. key: 0,
  336. role: "button",
  337. "aria-label": unref(t)("el.inputNumber.decrease"),
  338. class: normalizeClass([unref(ns).e("decrease"), unref(ns).is("disabled", minDisabled.value)]),
  339. onKeydown: withKeys(decrease, ["enter"])
  340. }, [
  341. renderSlot(_ctx.$slots, "decrease-icon", {}, () => [
  342. createVNode(unref(ElIcon), null, {
  343. default: withCtx(() => [
  344. controlsAtRight.value ? (openBlock(), createBlock(unref(ArrowDown), { key: 0 })) : (openBlock(), createBlock(unref(Minus), { key: 1 }))
  345. ]),
  346. _: 1
  347. })
  348. ])
  349. ], 42, _hoisted_1)), [
  350. [unref(vRepeatClick), decrease]
  351. ]) : createCommentVNode("v-if", true),
  352. _ctx.controls ? withDirectives((openBlock(), createElementBlock("span", {
  353. key: 1,
  354. role: "button",
  355. "aria-label": unref(t)("el.inputNumber.increase"),
  356. class: normalizeClass([unref(ns).e("increase"), unref(ns).is("disabled", maxDisabled.value)]),
  357. onKeydown: withKeys(increase, ["enter"])
  358. }, [
  359. renderSlot(_ctx.$slots, "increase-icon", {}, () => [
  360. createVNode(unref(ElIcon), null, {
  361. default: withCtx(() => [
  362. controlsAtRight.value ? (openBlock(), createBlock(unref(ArrowUp), { key: 0 })) : (openBlock(), createBlock(unref(Plus), { key: 1 }))
  363. ]),
  364. _: 1
  365. })
  366. ])
  367. ], 42, _hoisted_2)), [
  368. [unref(vRepeatClick), increase]
  369. ]) : createCommentVNode("v-if", true),
  370. createVNode(unref(ElInput), {
  371. id: _ctx.id,
  372. ref_key: "input",
  373. ref: input,
  374. type: "number",
  375. step: _ctx.step,
  376. "model-value": displayValue.value,
  377. placeholder: _ctx.placeholder,
  378. readonly: _ctx.readonly,
  379. disabled: unref(inputNumberDisabled),
  380. size: unref(inputNumberSize),
  381. max: _ctx.max,
  382. min: _ctx.min,
  383. name: _ctx.name,
  384. "aria-label": _ctx.ariaLabel,
  385. "validate-event": false,
  386. inputmode: _ctx.inputmode,
  387. onKeydown: handleKeydown,
  388. onBlur: handleBlur,
  389. onFocus: handleFocus,
  390. onInput: handleInput,
  391. onChange: handleInputChange
  392. }, createSlots({
  393. _: 2
  394. }, [
  395. _ctx.$slots.prefix ? {
  396. name: "prefix",
  397. fn: withCtx(() => [
  398. renderSlot(_ctx.$slots, "prefix")
  399. ]),
  400. key: "0"
  401. } : void 0,
  402. _ctx.$slots.suffix ? {
  403. name: "suffix",
  404. fn: withCtx(() => [
  405. renderSlot(_ctx.$slots, "suffix")
  406. ]),
  407. key: "1"
  408. } : void 0
  409. ]), 1032, ["id", "step", "model-value", "placeholder", "readonly", "disabled", "size", "max", "min", "name", "aria-label", "inputmode"])
  410. ],
  411. 34
  412. );
  413. };
  414. }
  415. });
  416. var InputNumber = /* @__PURE__ */ _export_sfc(_sfc_main, [["__file", "/home/runner/work/element-plus/element-plus/packages/components/input-number/src/input-number.vue"]]);
  417. export { InputNumber as default };
  418. //# sourceMappingURL=input-number2.mjs.map