Slider.mjs 8.5 KB

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