PickerColumn.js 7.6 KB

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