Slider.mjs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. import { createVNode as _createVNode } from "vue";
  2. import { ref, computed, defineComponent } from "vue";
  3. import { clamp, addUnit, addNumber, numericProp, isSameValue, 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", "dragEnd", "dragStart", "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 handleRangeValue = (value) => {
  96. var _a, _b;
  97. const left = (_a = value[0]) != null ? _a : Number(props.min);
  98. const right = (_b = value[1]) != null ? _b : Number(props.max);
  99. return left > right ? [right, left] : [left, right];
  100. };
  101. const updateValue = (value, end) => {
  102. if (isRange(value)) {
  103. value = handleRangeValue(value).map(format);
  104. } else {
  105. value = format(value);
  106. }
  107. if (!isSameValue(value, props.modelValue)) {
  108. emit("update:modelValue", value);
  109. }
  110. if (end && !isSameValue(value, startValue)) {
  111. emit("change", value);
  112. }
  113. };
  114. const onClick = (event) => {
  115. event.stopPropagation();
  116. if (props.disabled || props.readonly) {
  117. return;
  118. }
  119. const {
  120. min,
  121. reverse,
  122. vertical,
  123. modelValue
  124. } = props;
  125. const rect = useRect(root);
  126. const getDelta = () => {
  127. if (vertical) {
  128. if (reverse) {
  129. return rect.bottom - event.clientY;
  130. }
  131. return event.clientY - rect.top;
  132. }
  133. if (reverse) {
  134. return rect.right - event.clientX;
  135. }
  136. return event.clientX - rect.left;
  137. };
  138. const total = vertical ? rect.height : rect.width;
  139. const value = Number(min) + getDelta() / total * scope.value;
  140. if (isRange(modelValue)) {
  141. const [left, right] = modelValue;
  142. const middle = (left + right) / 2;
  143. if (value <= middle) {
  144. updateValue([value, right], true);
  145. } else {
  146. updateValue([left, value], true);
  147. }
  148. } else {
  149. updateValue(value, true);
  150. }
  151. };
  152. const onTouchStart = (event) => {
  153. if (props.disabled || props.readonly) {
  154. return;
  155. }
  156. touch.start(event);
  157. current = props.modelValue;
  158. if (isRange(current)) {
  159. startValue = current.map(format);
  160. } else {
  161. startValue = format(current);
  162. }
  163. dragStatus.value = "start";
  164. };
  165. const onTouchMove = (event) => {
  166. if (props.disabled || props.readonly) {
  167. return;
  168. }
  169. if (dragStatus.value === "start") {
  170. emit("dragStart", event);
  171. }
  172. preventDefault(event, true);
  173. touch.move(event);
  174. dragStatus.value = "dragging";
  175. const rect = useRect(root);
  176. const delta = props.vertical ? touch.deltaY.value : touch.deltaX.value;
  177. const total = props.vertical ? rect.height : rect.width;
  178. let diff = delta / total * scope.value;
  179. if (props.reverse) {
  180. diff = -diff;
  181. }
  182. if (isRange(startValue)) {
  183. const index = props.reverse ? 1 - buttonIndex : buttonIndex;
  184. current[index] = startValue[index] + diff;
  185. } else {
  186. current = startValue + diff;
  187. }
  188. updateValue(current);
  189. };
  190. const onTouchEnd = (event) => {
  191. if (props.disabled || props.readonly) {
  192. return;
  193. }
  194. if (dragStatus.value === "dragging") {
  195. updateValue(current, true);
  196. emit("dragEnd", event);
  197. }
  198. dragStatus.value = "";
  199. };
  200. const getButtonClassName = (index) => {
  201. if (typeof index === "number") {
  202. const position = ["left", "right"];
  203. return bem(`button-wrapper`, position[index]);
  204. }
  205. return bem("button-wrapper", props.reverse ? "left" : "right");
  206. };
  207. const renderButtonContent = (value, index) => {
  208. if (typeof index === "number") {
  209. const slot = slots[index === 0 ? "left-button" : "right-button"];
  210. if (slot) {
  211. return slot({
  212. value
  213. });
  214. }
  215. }
  216. if (slots.button) {
  217. return slots.button({
  218. value
  219. });
  220. }
  221. return _createVNode("div", {
  222. "class": bem("button"),
  223. "style": getSizeStyle(props.buttonSize)
  224. }, null);
  225. };
  226. const renderButton = (index) => {
  227. const current2 = typeof index === "number" ? props.modelValue[index] : props.modelValue;
  228. return _createVNode("div", {
  229. "ref": slider,
  230. "role": "slider",
  231. "class": getButtonClassName(index),
  232. "tabindex": props.disabled ? void 0 : 0,
  233. "aria-valuemin": props.min,
  234. "aria-valuenow": current2,
  235. "aria-valuemax": props.max,
  236. "aria-disabled": props.disabled || void 0,
  237. "aria-readonly": props.readonly || void 0,
  238. "aria-orientation": props.vertical ? "vertical" : "horizontal",
  239. "onTouchstartPassive": (event) => {
  240. if (typeof index === "number") {
  241. buttonIndex = index;
  242. }
  243. onTouchStart(event);
  244. },
  245. "onTouchend": onTouchEnd,
  246. "onTouchcancel": onTouchEnd,
  247. "onClick": stopPropagation
  248. }, [renderButtonContent(current2, index)]);
  249. };
  250. updateValue(props.modelValue);
  251. useCustomFieldValue(() => props.modelValue);
  252. useEventListener("touchmove", onTouchMove, {
  253. target: slider
  254. });
  255. return () => _createVNode("div", {
  256. "ref": root,
  257. "style": wrapperStyle.value,
  258. "class": bem({
  259. vertical: props.vertical,
  260. disabled: props.disabled
  261. }),
  262. "onClick": onClick
  263. }, [_createVNode("div", {
  264. "class": bem("bar"),
  265. "style": barStyle.value
  266. }, [props.range ? [renderButton(0), renderButton(1)] : renderButton()])]);
  267. }
  268. });
  269. export {
  270. stdin_default as default,
  271. sliderProps
  272. };