Stepper.mjs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import { ref, watch, computed, nextTick, defineComponent, vShow as _vShow, mergeProps as _mergeProps, createVNode as _createVNode, withDirectives as _withDirectives } from "vue";
  2. import { isDef, addUnit, addNumber, truthProp, resetScroll, numericProp, formatNumber, getSizeStyle, preventDefault, createNamespace, callInterceptor, makeNumericProp, HAPTICS_FEEDBACK, LONG_PRESS_START_TIME } from "../utils/index.mjs";
  3. import { useCustomFieldValue } from "@vant/use";
  4. const [name, bem] = createNamespace("stepper");
  5. const LONG_PRESS_INTERVAL = 200;
  6. const isEqual = (value1, value2) => String(value1) === String(value2);
  7. const stepperProps = {
  8. min: makeNumericProp(1),
  9. max: makeNumericProp(Infinity),
  10. name: makeNumericProp(""),
  11. step: makeNumericProp(1),
  12. theme: String,
  13. integer: Boolean,
  14. disabled: Boolean,
  15. showPlus: truthProp,
  16. showMinus: truthProp,
  17. showInput: truthProp,
  18. longPress: truthProp,
  19. autoFixed: truthProp,
  20. allowEmpty: Boolean,
  21. modelValue: numericProp,
  22. inputWidth: numericProp,
  23. buttonSize: numericProp,
  24. placeholder: String,
  25. disablePlus: Boolean,
  26. disableMinus: Boolean,
  27. disableInput: Boolean,
  28. beforeChange: Function,
  29. defaultValue: makeNumericProp(1),
  30. decimalLength: numericProp
  31. };
  32. var stdin_default = defineComponent({
  33. name,
  34. props: stepperProps,
  35. emits: ["plus", "blur", "minus", "focus", "change", "overlimit", "update:modelValue"],
  36. setup(props, {
  37. emit
  38. }) {
  39. const format = (value, autoFixed = true) => {
  40. const {
  41. min,
  42. max,
  43. allowEmpty,
  44. decimalLength
  45. } = props;
  46. if (allowEmpty && value === "") {
  47. return value;
  48. }
  49. value = formatNumber(String(value), !props.integer);
  50. value = value === "" ? 0 : +value;
  51. value = Number.isNaN(value) ? +min : value;
  52. value = autoFixed ? Math.max(Math.min(+max, value), +min) : value;
  53. if (isDef(decimalLength)) {
  54. value = value.toFixed(+decimalLength);
  55. }
  56. return value;
  57. };
  58. const getInitialValue = () => {
  59. var _a;
  60. const defaultValue = (_a = props.modelValue) != null ? _a : props.defaultValue;
  61. const value = format(defaultValue);
  62. if (!isEqual(value, props.modelValue)) {
  63. emit("update:modelValue", value);
  64. }
  65. return value;
  66. };
  67. let actionType;
  68. const inputRef = ref();
  69. const current = ref(getInitialValue());
  70. const minusDisabled = computed(() => props.disabled || props.disableMinus || +current.value <= +props.min);
  71. const plusDisabled = computed(() => props.disabled || props.disablePlus || +current.value >= +props.max);
  72. const inputStyle = computed(() => ({
  73. width: addUnit(props.inputWidth),
  74. height: addUnit(props.buttonSize)
  75. }));
  76. const buttonStyle = computed(() => getSizeStyle(props.buttonSize));
  77. const check = () => {
  78. const value = format(current.value);
  79. if (!isEqual(value, current.value)) {
  80. current.value = value;
  81. }
  82. };
  83. const setValue = (value) => {
  84. if (props.beforeChange) {
  85. callInterceptor(props.beforeChange, {
  86. args: [value],
  87. done() {
  88. current.value = value;
  89. }
  90. });
  91. } else {
  92. current.value = value;
  93. }
  94. };
  95. const onChange = () => {
  96. if (actionType === "plus" && plusDisabled.value || actionType === "minus" && minusDisabled.value) {
  97. emit("overlimit", actionType);
  98. return;
  99. }
  100. const diff = actionType === "minus" ? -props.step : +props.step;
  101. const value = format(addNumber(+current.value, diff));
  102. setValue(value);
  103. emit(actionType);
  104. };
  105. const onInput = (event) => {
  106. const input = event.target;
  107. const {
  108. value
  109. } = input;
  110. const {
  111. decimalLength
  112. } = props;
  113. let formatted = formatNumber(String(value), !props.integer);
  114. if (isDef(decimalLength) && formatted.includes(".")) {
  115. const pair = formatted.split(".");
  116. formatted = `${pair[0]}.${pair[1].slice(0, +decimalLength)}`;
  117. }
  118. if (props.beforeChange) {
  119. input.value = String(current.value);
  120. } else if (!isEqual(value, formatted)) {
  121. input.value = formatted;
  122. }
  123. const isNumeric = formatted === String(+formatted);
  124. setValue(isNumeric ? +formatted : formatted);
  125. };
  126. const onFocus = (event) => {
  127. var _a;
  128. if (props.disableInput) {
  129. (_a = inputRef.value) == null ? void 0 : _a.blur();
  130. } else {
  131. emit("focus", event);
  132. }
  133. };
  134. const onBlur = (event) => {
  135. const input = event.target;
  136. const value = format(input.value, props.autoFixed);
  137. input.value = String(value);
  138. current.value = value;
  139. nextTick(() => {
  140. emit("blur", event);
  141. resetScroll();
  142. });
  143. };
  144. let isLongPress;
  145. let longPressTimer;
  146. const longPressStep = () => {
  147. longPressTimer = setTimeout(() => {
  148. onChange();
  149. longPressStep();
  150. }, LONG_PRESS_INTERVAL);
  151. };
  152. const onTouchStart = () => {
  153. if (props.longPress) {
  154. isLongPress = false;
  155. clearTimeout(longPressTimer);
  156. longPressTimer = setTimeout(() => {
  157. isLongPress = true;
  158. onChange();
  159. longPressStep();
  160. }, LONG_PRESS_START_TIME);
  161. }
  162. };
  163. const onTouchEnd = (event) => {
  164. if (props.longPress) {
  165. clearTimeout(longPressTimer);
  166. if (isLongPress) {
  167. preventDefault(event);
  168. }
  169. }
  170. };
  171. const onMousedown = (event) => {
  172. if (props.disableInput) {
  173. preventDefault(event);
  174. }
  175. };
  176. const createListeners = (type) => ({
  177. onClick: (event) => {
  178. preventDefault(event);
  179. actionType = type;
  180. onChange();
  181. },
  182. onTouchstartPassive: () => {
  183. actionType = type;
  184. onTouchStart();
  185. },
  186. onTouchend: onTouchEnd,
  187. onTouchcancel: onTouchEnd
  188. });
  189. watch(() => [props.max, props.min, props.integer, props.decimalLength], check);
  190. watch(() => props.modelValue, (value) => {
  191. if (!isEqual(value, current.value)) {
  192. current.value = format(value);
  193. }
  194. });
  195. watch(current, (value) => {
  196. emit("update:modelValue", value);
  197. emit("change", value, {
  198. name: props.name
  199. });
  200. });
  201. useCustomFieldValue(() => props.modelValue);
  202. return () => _createVNode("div", {
  203. "role": "group",
  204. "class": bem([props.theme])
  205. }, [_withDirectives(_createVNode("button", _mergeProps({
  206. "type": "button",
  207. "style": buttonStyle.value,
  208. "class": [bem("minus", {
  209. disabled: minusDisabled.value
  210. }), {
  211. [HAPTICS_FEEDBACK]: !minusDisabled.value
  212. }],
  213. "aria-disabled": minusDisabled.value || void 0
  214. }, createListeners("minus")), null), [[_vShow, props.showMinus]]), _withDirectives(_createVNode("input", {
  215. "ref": inputRef,
  216. "type": props.integer ? "tel" : "text",
  217. "role": "spinbutton",
  218. "class": bem("input"),
  219. "value": current.value,
  220. "style": inputStyle.value,
  221. "disabled": props.disabled,
  222. "readonly": props.disableInput,
  223. "inputmode": props.integer ? "numeric" : "decimal",
  224. "placeholder": props.placeholder,
  225. "autocomplete": "off",
  226. "aria-valuemax": props.max,
  227. "aria-valuemin": props.min,
  228. "aria-valuenow": current.value,
  229. "onBlur": onBlur,
  230. "onInput": onInput,
  231. "onFocus": onFocus,
  232. "onMousedown": onMousedown
  233. }, null), [[_vShow, props.showInput]]), _withDirectives(_createVNode("button", _mergeProps({
  234. "type": "button",
  235. "style": buttonStyle.value,
  236. "class": [bem("plus", {
  237. disabled: plusDisabled.value
  238. }), {
  239. [HAPTICS_FEEDBACK]: !plusDisabled.value
  240. }],
  241. "aria-disabled": plusDisabled.value || void 0
  242. }, createListeners("plus")), null), [[_vShow, props.showPlus]])]);
  243. }
  244. });
  245. export {
  246. stdin_default as default,
  247. stepperProps
  248. };