PickerColumn.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. var __defProp = Object.defineProperty;
  2. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  3. var __getOwnPropNames = Object.getOwnPropertyNames;
  4. var __hasOwnProp = Object.prototype.hasOwnProperty;
  5. var __export = (target, all) => {
  6. for (var name2 in all)
  7. __defProp(target, name2, { get: all[name2], enumerable: true });
  8. };
  9. var __copyProps = (to, from, except, desc) => {
  10. if (from && typeof from === "object" || typeof from === "function") {
  11. for (let key of __getOwnPropNames(from))
  12. if (!__hasOwnProp.call(to, key) && key !== except)
  13. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  14. }
  15. return to;
  16. };
  17. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  18. var stdin_exports = {};
  19. __export(stdin_exports, {
  20. PICKER_KEY: () => PICKER_KEY,
  21. default: () => stdin_default
  22. });
  23. module.exports = __toCommonJS(stdin_exports);
  24. var import_vue = require("vue");
  25. var import_utils = require("../utils");
  26. var import_utils2 = require("./utils");
  27. var import_use = require("@vant/use");
  28. var import_use_touch = require("../composables/use-touch");
  29. var import_use_expose = require("../composables/use-expose");
  30. const DEFAULT_DURATION = 200;
  31. const MOMENTUM_TIME = 300;
  32. const MOMENTUM_DISTANCE = 15;
  33. const [name, bem] = (0, import_utils.createNamespace)("picker-column");
  34. const PICKER_KEY = Symbol(name);
  35. var stdin_default = (0, import_vue.defineComponent)({
  36. name,
  37. props: {
  38. value: import_utils.numericProp,
  39. fields: (0, import_utils.makeRequiredProp)(Object),
  40. options: (0, import_utils.makeArrayProp)(),
  41. readonly: Boolean,
  42. allowHtml: Boolean,
  43. optionHeight: (0, import_utils.makeRequiredProp)(Number),
  44. swipeDuration: (0, import_utils.makeRequiredProp)(import_utils.numericProp),
  45. visibleOptionNum: (0, import_utils.makeRequiredProp)(import_utils.numericProp)
  46. },
  47. emits: ["change", "clickOption", "scrollInto"],
  48. setup(props, {
  49. emit,
  50. slots
  51. }) {
  52. let moving;
  53. let startOffset;
  54. let touchStartTime;
  55. let momentumOffset;
  56. let transitionEndTrigger;
  57. const root = (0, import_vue.ref)();
  58. const wrapper = (0, import_vue.ref)();
  59. const currentOffset = (0, import_vue.ref)(0);
  60. const currentDuration = (0, import_vue.ref)(0);
  61. const touch = (0, import_use_touch.useTouch)();
  62. const count = () => props.options.length;
  63. const baseOffset = () => props.optionHeight * (+props.visibleOptionNum - 1) / 2;
  64. const updateValueByIndex = (index) => {
  65. let enabledIndex = (0, import_utils2.findIndexOfEnabledOption)(props.options, index);
  66. const offset = -enabledIndex * props.optionHeight;
  67. const trigger = () => {
  68. if (enabledIndex > count() - 1) {
  69. enabledIndex = (0, import_utils2.findIndexOfEnabledOption)(props.options, index);
  70. }
  71. const value = props.options[enabledIndex][props.fields.value];
  72. if (value !== props.value) {
  73. emit("change", value);
  74. }
  75. };
  76. if (moving && offset !== currentOffset.value) {
  77. transitionEndTrigger = trigger;
  78. } else {
  79. trigger();
  80. }
  81. currentOffset.value = offset;
  82. };
  83. const isReadonly = () => props.readonly || !props.options.length;
  84. const onClickOption = (index) => {
  85. if (moving || isReadonly()) {
  86. return;
  87. }
  88. transitionEndTrigger = null;
  89. currentDuration.value = DEFAULT_DURATION;
  90. updateValueByIndex(index);
  91. emit("clickOption", props.options[index]);
  92. };
  93. const getIndexByOffset = (offset) => (0, import_utils.clamp)(Math.round(-offset / props.optionHeight), 0, count() - 1);
  94. const currentIndex = (0, import_vue.computed)(() => getIndexByOffset(currentOffset.value));
  95. const momentum = (distance, duration) => {
  96. const speed = Math.abs(distance / duration);
  97. distance = currentOffset.value + speed / 3e-3 * (distance < 0 ? -1 : 1);
  98. const index = getIndexByOffset(distance);
  99. currentDuration.value = +props.swipeDuration;
  100. updateValueByIndex(index);
  101. };
  102. const stopMomentum = () => {
  103. moving = false;
  104. currentDuration.value = 0;
  105. if (transitionEndTrigger) {
  106. transitionEndTrigger();
  107. transitionEndTrigger = null;
  108. }
  109. };
  110. const onTouchStart = (event) => {
  111. if (isReadonly()) {
  112. return;
  113. }
  114. touch.start(event);
  115. if (moving) {
  116. const translateY = (0, import_utils2.getElementTranslateY)(wrapper.value);
  117. currentOffset.value = Math.min(0, translateY - baseOffset());
  118. }
  119. currentDuration.value = 0;
  120. startOffset = currentOffset.value;
  121. touchStartTime = Date.now();
  122. momentumOffset = startOffset;
  123. transitionEndTrigger = null;
  124. };
  125. const onTouchMove = (event) => {
  126. if (isReadonly()) {
  127. return;
  128. }
  129. touch.move(event);
  130. if (touch.isVertical()) {
  131. moving = true;
  132. (0, import_utils.preventDefault)(event, true);
  133. }
  134. const newOffset = (0, import_utils.clamp)(startOffset + touch.deltaY.value, -(count() * props.optionHeight), props.optionHeight);
  135. const newIndex = getIndexByOffset(newOffset);
  136. if (newIndex !== currentIndex.value) {
  137. emit("scrollInto", props.options[newIndex]);
  138. }
  139. currentOffset.value = newOffset;
  140. const now = Date.now();
  141. if (now - touchStartTime > MOMENTUM_TIME) {
  142. touchStartTime = now;
  143. momentumOffset = newOffset;
  144. }
  145. };
  146. const onTouchEnd = () => {
  147. if (isReadonly()) {
  148. return;
  149. }
  150. const distance = currentOffset.value - momentumOffset;
  151. const duration = Date.now() - touchStartTime;
  152. const startMomentum = duration < MOMENTUM_TIME && Math.abs(distance) > MOMENTUM_DISTANCE;
  153. if (startMomentum) {
  154. momentum(distance, duration);
  155. return;
  156. }
  157. const index = getIndexByOffset(currentOffset.value);
  158. currentDuration.value = DEFAULT_DURATION;
  159. updateValueByIndex(index);
  160. setTimeout(() => {
  161. moving = false;
  162. }, 0);
  163. };
  164. const renderOptions = () => {
  165. const optionStyle = {
  166. height: `${props.optionHeight}px`
  167. };
  168. return props.options.map((option, index) => {
  169. const text = option[props.fields.text];
  170. const {
  171. disabled
  172. } = option;
  173. const value = option[props.fields.value];
  174. const data = {
  175. role: "button",
  176. style: optionStyle,
  177. tabindex: disabled ? -1 : 0,
  178. class: [bem("item", {
  179. disabled,
  180. selected: value === props.value
  181. }), option.className],
  182. onClick: () => onClickOption(index)
  183. };
  184. const childData = {
  185. class: "van-ellipsis",
  186. [props.allowHtml ? "innerHTML" : "textContent"]: text
  187. };
  188. return (0, import_vue.createVNode)("li", data, [slots.option ? slots.option(option, index) : (0, import_vue.createVNode)("div", childData, null)]);
  189. });
  190. };
  191. (0, import_use.useParent)(PICKER_KEY);
  192. (0, import_use_expose.useExpose)({
  193. stopMomentum
  194. });
  195. (0, import_vue.watchEffect)(() => {
  196. const index = moving ? Math.floor(-currentOffset.value / props.optionHeight) : props.options.findIndex((option) => option[props.fields.value] === props.value);
  197. const enabledIndex = (0, import_utils2.findIndexOfEnabledOption)(props.options, index);
  198. const offset = -enabledIndex * props.optionHeight;
  199. if (moving && enabledIndex < index) stopMomentum();
  200. currentOffset.value = offset;
  201. });
  202. (0, import_use.useEventListener)("touchmove", onTouchMove, {
  203. target: root
  204. });
  205. return () => (0, import_vue.createVNode)("div", {
  206. "ref": root,
  207. "class": bem(),
  208. "onTouchstartPassive": onTouchStart,
  209. "onTouchend": onTouchEnd,
  210. "onTouchcancel": onTouchEnd
  211. }, [(0, import_vue.createVNode)("ul", {
  212. "ref": wrapper,
  213. "style": {
  214. transform: `translate3d(0, ${currentOffset.value + baseOffset()}px, 0)`,
  215. transitionDuration: `${currentDuration.value}ms`,
  216. transitionProperty: currentDuration.value ? "all" : "none"
  217. },
  218. "class": bem("wrapper"),
  219. "onTransitionend": stopMomentum
  220. }, [renderOptions()])]);
  221. }
  222. });