PickerColumn.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  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_deep_clone = require("../utils/deep-clone");
  27. var import_utils = 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_LIMIT_TIME = 300;
  33. const MOMENTUM_LIMIT_DISTANCE = 15;
  34. const [name, bem] = (0, import_utils.createNamespace)("picker-column");
  35. function getElementTranslateY(element) {
  36. const {
  37. transform
  38. } = window.getComputedStyle(element);
  39. const translateY = transform.slice(7, transform.length - 1).split(", ")[5];
  40. return Number(translateY);
  41. }
  42. const PICKER_KEY = Symbol(name);
  43. const isOptionDisabled = (option) => (0, import_utils.isObject)(option) && option.disabled;
  44. var stdin_default = (0, import_vue2.defineComponent)({
  45. name,
  46. props: {
  47. textKey: (0, import_utils.makeRequiredProp)(String),
  48. readonly: Boolean,
  49. allowHtml: Boolean,
  50. className: import_utils.unknownProp,
  51. itemHeight: (0, import_utils.makeRequiredProp)(Number),
  52. defaultIndex: (0, import_utils.makeNumberProp)(0),
  53. swipeDuration: (0, import_utils.makeRequiredProp)(import_utils.numericProp),
  54. initialOptions: (0, import_utils.makeArrayProp)(),
  55. visibleItemCount: (0, import_utils.makeRequiredProp)(import_utils.numericProp)
  56. },
  57. emits: ["change"],
  58. setup(props, {
  59. emit,
  60. slots
  61. }) {
  62. let moving;
  63. let startOffset;
  64. let touchStartTime;
  65. let momentumOffset;
  66. let transitionEndTrigger;
  67. const root = (0, import_vue2.ref)();
  68. const wrapper = (0, import_vue2.ref)();
  69. const state = (0, import_vue2.reactive)({
  70. index: props.defaultIndex,
  71. offset: 0,
  72. duration: 0,
  73. options: (0, import_deep_clone.deepClone)(props.initialOptions)
  74. });
  75. const touch = (0, import_use_touch.useTouch)();
  76. const count = () => state.options.length;
  77. const baseOffset = () => props.itemHeight * (+props.visibleItemCount - 1) / 2;
  78. const adjustIndex = (index) => {
  79. index = (0, import_utils.clamp)(index, 0, count());
  80. for (let i = index; i < count(); i++) {
  81. if (!isOptionDisabled(state.options[i]))
  82. return i;
  83. }
  84. for (let i = index - 1; i >= 0; i--) {
  85. if (!isOptionDisabled(state.options[i]))
  86. return i;
  87. }
  88. };
  89. const setIndex = (index, emitChange) => {
  90. index = adjustIndex(index) || 0;
  91. const offset = -index * props.itemHeight;
  92. const trigger = () => {
  93. if (index !== state.index) {
  94. state.index = index;
  95. if (emitChange) {
  96. emit("change", index);
  97. }
  98. }
  99. };
  100. if (moving && offset !== state.offset) {
  101. transitionEndTrigger = trigger;
  102. } else {
  103. trigger();
  104. }
  105. state.offset = offset;
  106. };
  107. const setOptions = (options) => {
  108. if (JSON.stringify(options) !== JSON.stringify(state.options)) {
  109. state.options = (0, import_deep_clone.deepClone)(options);
  110. setIndex(props.defaultIndex);
  111. }
  112. };
  113. const onClickItem = (index) => {
  114. if (moving || props.readonly) {
  115. return;
  116. }
  117. transitionEndTrigger = null;
  118. state.duration = DEFAULT_DURATION;
  119. setIndex(index, true);
  120. };
  121. const getOptionText = (option) => {
  122. if ((0, import_utils.isObject)(option) && props.textKey in option) {
  123. return option[props.textKey];
  124. }
  125. return option;
  126. };
  127. const getIndexByOffset = (offset) => (0, import_utils.clamp)(Math.round(-offset / props.itemHeight), 0, count() - 1);
  128. const momentum = (distance, duration) => {
  129. const speed = Math.abs(distance / duration);
  130. distance = state.offset + speed / 3e-3 * (distance < 0 ? -1 : 1);
  131. const index = getIndexByOffset(distance);
  132. state.duration = +props.swipeDuration;
  133. setIndex(index, true);
  134. };
  135. const stopMomentum = () => {
  136. moving = false;
  137. state.duration = 0;
  138. if (transitionEndTrigger) {
  139. transitionEndTrigger();
  140. transitionEndTrigger = null;
  141. }
  142. };
  143. const onTouchStart = (event) => {
  144. if (props.readonly) {
  145. return;
  146. }
  147. touch.start(event);
  148. if (moving) {
  149. const translateY = getElementTranslateY(wrapper.value);
  150. state.offset = Math.min(0, translateY - baseOffset());
  151. startOffset = state.offset;
  152. } else {
  153. startOffset = state.offset;
  154. }
  155. state.duration = 0;
  156. touchStartTime = Date.now();
  157. momentumOffset = startOffset;
  158. transitionEndTrigger = null;
  159. };
  160. const onTouchMove = (event) => {
  161. if (props.readonly) {
  162. return;
  163. }
  164. touch.move(event);
  165. if (touch.isVertical()) {
  166. moving = true;
  167. (0, import_utils.preventDefault)(event, true);
  168. }
  169. state.offset = (0, import_utils.clamp)(startOffset + touch.deltaY.value, -(count() * props.itemHeight), props.itemHeight);
  170. const now = Date.now();
  171. if (now - touchStartTime > MOMENTUM_LIMIT_TIME) {
  172. touchStartTime = now;
  173. momentumOffset = state.offset;
  174. }
  175. };
  176. const onTouchEnd = () => {
  177. if (props.readonly) {
  178. return;
  179. }
  180. const distance = state.offset - momentumOffset;
  181. const duration = Date.now() - touchStartTime;
  182. const allowMomentum = duration < MOMENTUM_LIMIT_TIME && Math.abs(distance) > MOMENTUM_LIMIT_DISTANCE;
  183. if (allowMomentum) {
  184. momentum(distance, duration);
  185. return;
  186. }
  187. const index = getIndexByOffset(state.offset);
  188. state.duration = DEFAULT_DURATION;
  189. setIndex(index, true);
  190. setTimeout(() => {
  191. moving = false;
  192. }, 0);
  193. };
  194. const renderOptions = () => {
  195. const optionStyle = {
  196. height: `${props.itemHeight}px`
  197. };
  198. return state.options.map((option, index) => {
  199. const text = getOptionText(option);
  200. const disabled = isOptionDisabled(option);
  201. const data = {
  202. role: "button",
  203. style: optionStyle,
  204. tabindex: disabled ? -1 : 0,
  205. class: bem("item", {
  206. disabled,
  207. selected: index === state.index
  208. }),
  209. onClick: () => onClickItem(index)
  210. };
  211. const childData = {
  212. class: "van-ellipsis",
  213. [props.allowHtml ? "innerHTML" : "textContent"]: text
  214. };
  215. return (0, import_vue.createVNode)("li", data, [slots.option ? slots.option(option) : (0, import_vue.createVNode)("div", childData, null)]);
  216. });
  217. };
  218. const setValue = (value) => {
  219. const {
  220. options
  221. } = state;
  222. for (let i = 0; i < options.length; i++) {
  223. if (getOptionText(options[i]) === value) {
  224. return setIndex(i);
  225. }
  226. }
  227. };
  228. const getValue = () => state.options[state.index];
  229. const hasOptions = () => state.options.length;
  230. setIndex(state.index);
  231. (0, import_use.useParent)(PICKER_KEY);
  232. (0, import_use_expose.useExpose)({
  233. state,
  234. setIndex,
  235. getValue,
  236. setValue,
  237. setOptions,
  238. hasOptions,
  239. stopMomentum
  240. });
  241. (0, import_vue2.watch)(() => props.initialOptions, setOptions);
  242. (0, import_vue2.watch)(() => props.defaultIndex, (value) => setIndex(value));
  243. (0, import_use.useEventListener)("touchmove", onTouchMove, {
  244. target: root
  245. });
  246. return () => (0, import_vue.createVNode)("div", {
  247. "ref": root,
  248. "class": [bem(), props.className],
  249. "onTouchstartPassive": onTouchStart,
  250. "onTouchend": onTouchEnd,
  251. "onTouchcancel": onTouchEnd
  252. }, [(0, import_vue.createVNode)("ul", {
  253. "ref": wrapper,
  254. "style": {
  255. transform: `translate3d(0, ${state.offset + baseOffset()}px, 0)`,
  256. transitionDuration: `${state.duration}ms`,
  257. transitionProperty: state.duration ? "all" : "none"
  258. },
  259. "class": bem("wrapper"),
  260. "onTransitionend": stopMomentum
  261. }, [renderOptions()])]);
  262. }
  263. });