Slider.mjs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. import { createVNode as _createVNode } from "vue";
  2. import { ref, computed, defineComponent } from "vue";
  3. import { clamp, addUnit, addNumber, numericProp, getSizeStyle, preventDefault, stopPropagation, createNamespace, makeNumericProp } from "../utils/index.mjs";
  4. import { useRect, useCustomFieldValue, useEventListener } from "@vant/use";
  5. import { useTouch } from "../composables/use-touch.mjs";
  6. const [name, bem] = createNamespace("slider");
  7. const sliderProps = {
  8. min: makeNumericProp(0),
  9. max: makeNumericProp(100),
  10. step: makeNumericProp(1),
  11. range: Boolean,
  12. reverse: Boolean,
  13. disabled: Boolean,
  14. readonly: Boolean,
  15. vertical: Boolean,
  16. barHeight: numericProp,
  17. buttonSize: numericProp,
  18. activeColor: String,
  19. inactiveColor: String,
  20. modelValue: {
  21. type: [Number, Array],
  22. default: 0
  23. }
  24. };
  25. var stdin_default = defineComponent({
  26. name,
  27. props: sliderProps,
  28. emits: ["change", "drag-end", "drag-start", "update:modelValue"],
  29. setup(props, {
  30. emit,
  31. slots
  32. }) {
  33. let buttonIndex;
  34. let current;
  35. let startValue;
  36. const root = ref();
  37. const slider = ref();
  38. const dragStatus = ref();
  39. const touch = useTouch();
  40. const scope = computed(() => Number(props.max) - Number(props.min));
  41. const wrapperStyle = computed(() => {
  42. const crossAxis = props.vertical ? "width" : "height";
  43. return {
  44. background: props.inactiveColor,
  45. [crossAxis]: addUnit(props.barHeight)
  46. };
  47. });
  48. const isRange = (val) => props.range && Array.isArray(val);
  49. const calcMainAxis = () => {
  50. const {
  51. modelValue,
  52. min
  53. } = props;
  54. if (isRange(modelValue)) {
  55. return `${(modelValue[1] - modelValue[0]) * 100 / scope.value}%`;
  56. }
  57. return `${(modelValue - Number(min)) * 100 / scope.value}%`;
  58. };
  59. const calcOffset = () => {
  60. const {
  61. modelValue,
  62. min
  63. } = props;
  64. if (isRange(modelValue)) {
  65. return `${(modelValue[0] - Number(min)) * 100 / scope.value}%`;
  66. }
  67. return "0%";
  68. };
  69. const barStyle = computed(() => {
  70. const mainAxis = props.vertical ? "height" : "width";
  71. const style = {
  72. [mainAxis]: calcMainAxis(),
  73. background: props.activeColor
  74. };
  75. if (dragStatus.value) {
  76. style.transition = "none";
  77. }
  78. const getPositionKey = () => {
  79. if (props.vertical) {
  80. return props.reverse ? "bottom" : "top";
  81. }
  82. return props.reverse ? "right" : "left";
  83. };
  84. style[getPositionKey()] = calcOffset();
  85. return style;
  86. });
  87. const format = (value) => {
  88. const min = +props.min;
  89. const max = +props.max;
  90. const step = +props.step;
  91. value = clamp(value, min, max);
  92. const diff = Math.round((value - min) / step) * step;
  93. return addNumber(min, diff);
  94. };
  95. const isSameValue = (newValue, oldValue) => JSON.stringify(newValue) === JSON.stringify(oldValue);
  96. const handleRangeValue = (value) => {
  97. var _a, _b;
  98. const left = (_a = value[0]) != null ? _a : Number(props.min);
  99. const right = (_b = value[1]) != null ? _b : Number(props.max);
  100. return left > right ? [right, left] : [left, right];
  101. };
  102. const updateValue = (value, end) => {
  103. if (isRange(value)) {
  104. value = handleRangeValue(value).map(format);
  105. } else {
  106. value = format(value);
  107. }
  108. if (!isSameValue(value, props.modelValue)) {
  109. emit("update:modelValue", value);
  110. }
  111. if (end && !isSameValue(value, startValue)) {
  112. emit("change", value);
  113. }
  114. };
  115. const onClick = (event) => {
  116. event.stopPropagation();
  117. if (props.disabled || props.readonly) {
  118. return;
  119. }
  120. const {
  121. min,
  122. reverse,
  123. vertical,
  124. modelValue
  125. } = props;
  126. const rect = useRect(root);
  127. const getDelta = () => {
  128. if (vertical) {
  129. if (reverse) {
  130. return rect.bottom - event.clientY;
  131. }
  132. return event.clientY - rect.top;
  133. }
  134. if (reverse) {
  135. return rect.right - event.clientX;
  136. }
  137. return event.clientX - rect.left;
  138. };
  139. const total = vertical ? rect.height : rect.width;
  140. const value = Number(min) + getDelta() / total * scope.value;
  141. if (isRange(modelValue)) {
  142. const [left, right] = modelValue;
  143. const middle = (left + right) / 2;
  144. if (value <= middle) {
  145. updateValue([value, right], true);
  146. } else {
  147. updateValue([left, value], true);
  148. }
  149. } else {
  150. updateValue(value, true);
  151. }
  152. };
  153. const onTouchStart = (event) => {
  154. if (props.disabled || props.readonly) {
  155. return;
  156. }
  157. touch.start(event);
  158. current = props.modelValue;
  159. if (isRange(current)) {
  160. startValue = current.map(format);
  161. } else {
  162. startValue = format(current);
  163. }
  164. dragStatus.value = "start";
  165. };
  166. const onTouchMove = (event) => {
  167. if (props.disabled || props.readonly) {
  168. return;
  169. }
  170. if (dragStatus.value === "start") {
  171. emit("drag-start", event);
  172. }
  173. preventDefault(event, true);
  174. touch.move(event);
  175. dragStatus.value = "dragging";
  176. const rect = useRect(root);
  177. const delta = props.vertical ? touch.deltaY.value : touch.deltaX.value;
  178. const total = props.vertical ? rect.height : rect.width;
  179. let diff = delta / total * scope.value;
  180. if (props.reverse) {
  181. diff = -diff;
  182. }
  183. if (isRange(startValue)) {
  184. const index = props.reverse ? 1 - buttonIndex : buttonIndex;
  185. current[index] = startValue[index] + diff;
  186. } else {
  187. current = startValue + diff;
  188. }
  189. updateValue(current);
  190. };
  191. const onTouchEnd = (event) => {
  192. if (props.disabled || props.readonly) {
  193. return;
  194. }
  195. if (dragStatus.value === "dragging") {
  196. updateValue(current, true);
  197. emit("drag-end", event);
  198. }
  199. dragStatus.value = "";
  200. };
  201. const getButtonClassName = (index) => {
  202. if (typeof index === "number") {
  203. const position = ["left", "right"];
  204. return bem(`button-wrapper`, position[index]);
  205. }
  206. return bem("button-wrapper", props.reverse ? "left" : "right");
  207. };
  208. const renderButtonContent = (value, index) => {
  209. if (typeof index === "number") {
  210. const slot = slots[index === 0 ? "left-button" : "right-button"];
  211. if (slot) {
  212. return slot({
  213. value
  214. });
  215. }
  216. }
  217. if (slots.button) {
  218. return slots.button({
  219. value
  220. });
  221. }
  222. return _createVNode("div", {
  223. "class": bem("button"),
  224. "style": getSizeStyle(props.buttonSize)
  225. }, null);
  226. };
  227. const renderButton = (index) => {
  228. const current2 = typeof index === "number" ? props.modelValue[index] : props.modelValue;
  229. return _createVNode("div", {
  230. "ref": slider,
  231. "role": "slider",
  232. "class": getButtonClassName(index),
  233. "tabindex": props.disabled ? void 0 : 0,
  234. "aria-valuemin": props.min,
  235. "aria-valuenow": current2,
  236. "aria-valuemax": props.max,
  237. "aria-disabled": props.disabled || void 0,
  238. "aria-readonly": props.readonly || void 0,
  239. "aria-orientation": props.vertical ? "vertical" : "horizontal",
  240. "onTouchstartPassive": (event) => {
  241. if (typeof index === "number") {
  242. buttonIndex = index;
  243. }
  244. onTouchStart(event);
  245. },
  246. "onTouchend": onTouchEnd,
  247. "onTouchcancel": onTouchEnd,
  248. "onClick": stopPropagation
  249. }, [renderButtonContent(current2, index)]);
  250. };
  251. updateValue(props.modelValue);
  252. useCustomFieldValue(() => props.modelValue);
  253. useEventListener("touchmove", onTouchMove, {
  254. target: slider
  255. });
  256. return () => _createVNode("div", {
  257. "ref": root,
  258. "style": wrapperStyle.value,
  259. "class": bem({
  260. vertical: props.vertical,
  261. disabled: props.disabled
  262. }),
  263. "onClick": onClick
  264. }, [_createVNode("div", {
  265. "class": bem("bar"),
  266. "style": barStyle.value
  267. }, [props.range ? [renderButton(0), renderButton(1)] : renderButton()])]);
  268. }
  269. });
  270. export {
  271. stdin_default as default
  272. };