Stepper.mjs 7.7 KB

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