index-0f48f786.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. import { c as createNamespace, k as isDef, e as extend, s as clamp, n as numericProp, M as makeRequiredProp, o as makeArrayProp, a as useParent, D as useEventListener, p as preventDefault, H as HAPTICS_FEEDBACK, m as makeNumericProp, t as truthProp, z as makeStringProp, u as useChildren, U as unitToPx, x as isSameValue, y as pick, V as BORDER_UNSET_TOP_BOTTOM, w as withInstall } from "./index-487cde8c.js";
  2. import { u as useExpose } from "./use-scope-id-0b5b8615.js";
  3. import { L as Loading } from "./index-217c49a0.js";
  4. import { u as useTouch } from "./index-eef3af38.js";
  5. import { d as defineComponent, r as ref, a as computed, S as watchEffect, c as createVNode, w as watch, m as mergeProps, z as nextTick } from "./index-5e4623ce.js";
  6. const [name$3, bem$2, t] = createNamespace("picker");
  7. const getFirstEnabledOption = (options) => options.find((option) => !option.disabled) || options[0];
  8. function getColumnsType(columns, fields) {
  9. const firstColumn = columns[0];
  10. if (firstColumn) {
  11. if (Array.isArray(firstColumn)) {
  12. return "multiple";
  13. }
  14. if (fields.children in firstColumn) {
  15. return "cascade";
  16. }
  17. }
  18. return "default";
  19. }
  20. function findIndexOfEnabledOption(options, index2) {
  21. index2 = clamp(index2, 0, options.length);
  22. for (let i = index2; i < options.length; i++) {
  23. if (!options[i].disabled)
  24. return i;
  25. }
  26. for (let i = index2 - 1; i >= 0; i--) {
  27. if (!options[i].disabled)
  28. return i;
  29. }
  30. return 0;
  31. }
  32. const isOptionExist = (options, value, fields) => value !== void 0 && !!options.find((option) => option[fields.value] === value);
  33. function findOptionByValue(options, value, fields) {
  34. const index2 = options.findIndex((option) => option[fields.value] === value);
  35. const enabledIndex = findIndexOfEnabledOption(options, index2);
  36. return options[enabledIndex];
  37. }
  38. function formatCascadeColumns(columns, fields, selectedValues) {
  39. const formatted = [];
  40. let cursor = {
  41. [fields.children]: columns
  42. };
  43. let columnIndex = 0;
  44. while (cursor && cursor[fields.children]) {
  45. const options = cursor[fields.children];
  46. const value = selectedValues.value[columnIndex];
  47. cursor = isDef(value) ? findOptionByValue(options, value, fields) : void 0;
  48. if (!cursor && options.length) {
  49. const firstValue = getFirstEnabledOption(options)[fields.value];
  50. cursor = findOptionByValue(options, firstValue, fields);
  51. }
  52. columnIndex++;
  53. formatted.push(options);
  54. }
  55. return formatted;
  56. }
  57. function getElementTranslateY(element) {
  58. const { transform } = window.getComputedStyle(element);
  59. const translateY = transform.slice(7, transform.length - 1).split(", ")[5];
  60. return Number(translateY);
  61. }
  62. function assignDefaultFields(fields) {
  63. return extend(
  64. {
  65. text: "text",
  66. value: "value",
  67. children: "children"
  68. },
  69. fields
  70. );
  71. }
  72. const DEFAULT_DURATION = 200;
  73. const MOMENTUM_TIME = 300;
  74. const MOMENTUM_DISTANCE = 15;
  75. const [name$2, bem$1] = createNamespace("picker-column");
  76. const PICKER_KEY = Symbol(name$2);
  77. var stdin_default$2 = defineComponent({
  78. name: name$2,
  79. props: {
  80. value: numericProp,
  81. fields: makeRequiredProp(Object),
  82. options: makeArrayProp(),
  83. readonly: Boolean,
  84. allowHtml: Boolean,
  85. optionHeight: makeRequiredProp(Number),
  86. swipeDuration: makeRequiredProp(numericProp),
  87. visibleOptionNum: makeRequiredProp(numericProp)
  88. },
  89. emits: ["change", "clickOption", "scrollInto"],
  90. setup(props, {
  91. emit,
  92. slots
  93. }) {
  94. let moving;
  95. let startOffset;
  96. let touchStartTime;
  97. let momentumOffset;
  98. let transitionEndTrigger;
  99. const root = ref();
  100. const wrapper = ref();
  101. const currentOffset = ref(0);
  102. const currentDuration = ref(0);
  103. const touch = useTouch();
  104. const count = () => props.options.length;
  105. const baseOffset = () => props.optionHeight * (+props.visibleOptionNum - 1) / 2;
  106. const updateValueByIndex = (index2) => {
  107. let enabledIndex = findIndexOfEnabledOption(props.options, index2);
  108. const offset = -enabledIndex * props.optionHeight;
  109. const trigger = () => {
  110. if (enabledIndex > count() - 1) {
  111. enabledIndex = findIndexOfEnabledOption(props.options, index2);
  112. }
  113. const value = props.options[enabledIndex][props.fields.value];
  114. if (value !== props.value) {
  115. emit("change", value);
  116. }
  117. };
  118. if (moving && offset !== currentOffset.value) {
  119. transitionEndTrigger = trigger;
  120. } else {
  121. trigger();
  122. }
  123. currentOffset.value = offset;
  124. };
  125. const isReadonly = () => props.readonly || !props.options.length;
  126. const onClickOption = (index2) => {
  127. if (moving || isReadonly()) {
  128. return;
  129. }
  130. transitionEndTrigger = null;
  131. currentDuration.value = DEFAULT_DURATION;
  132. updateValueByIndex(index2);
  133. emit("clickOption", props.options[index2]);
  134. };
  135. const getIndexByOffset = (offset) => clamp(Math.round(-offset / props.optionHeight), 0, count() - 1);
  136. const currentIndex = computed(() => getIndexByOffset(currentOffset.value));
  137. const momentum = (distance, duration) => {
  138. const speed = Math.abs(distance / duration);
  139. distance = currentOffset.value + speed / 3e-3 * (distance < 0 ? -1 : 1);
  140. const index2 = getIndexByOffset(distance);
  141. currentDuration.value = +props.swipeDuration;
  142. updateValueByIndex(index2);
  143. };
  144. const stopMomentum = () => {
  145. moving = false;
  146. currentDuration.value = 0;
  147. if (transitionEndTrigger) {
  148. transitionEndTrigger();
  149. transitionEndTrigger = null;
  150. }
  151. };
  152. const onTouchStart = (event) => {
  153. if (isReadonly()) {
  154. return;
  155. }
  156. touch.start(event);
  157. if (moving) {
  158. const translateY = getElementTranslateY(wrapper.value);
  159. currentOffset.value = Math.min(0, translateY - baseOffset());
  160. }
  161. currentDuration.value = 0;
  162. startOffset = currentOffset.value;
  163. touchStartTime = Date.now();
  164. momentumOffset = startOffset;
  165. transitionEndTrigger = null;
  166. };
  167. const onTouchMove = (event) => {
  168. if (isReadonly()) {
  169. return;
  170. }
  171. touch.move(event);
  172. if (touch.isVertical()) {
  173. moving = true;
  174. preventDefault(event, true);
  175. }
  176. const newOffset = clamp(startOffset + touch.deltaY.value, -(count() * props.optionHeight), props.optionHeight);
  177. const newIndex = getIndexByOffset(newOffset);
  178. if (newIndex !== currentIndex.value) {
  179. emit("scrollInto", props.options[newIndex]);
  180. }
  181. currentOffset.value = newOffset;
  182. const now = Date.now();
  183. if (now - touchStartTime > MOMENTUM_TIME) {
  184. touchStartTime = now;
  185. momentumOffset = newOffset;
  186. }
  187. };
  188. const onTouchEnd = () => {
  189. if (isReadonly()) {
  190. return;
  191. }
  192. const distance = currentOffset.value - momentumOffset;
  193. const duration = Date.now() - touchStartTime;
  194. const startMomentum = duration < MOMENTUM_TIME && Math.abs(distance) > MOMENTUM_DISTANCE;
  195. if (startMomentum) {
  196. momentum(distance, duration);
  197. return;
  198. }
  199. const index2 = getIndexByOffset(currentOffset.value);
  200. currentDuration.value = DEFAULT_DURATION;
  201. updateValueByIndex(index2);
  202. setTimeout(() => {
  203. moving = false;
  204. }, 0);
  205. };
  206. const renderOptions = () => {
  207. const optionStyle = {
  208. height: `${props.optionHeight}px`
  209. };
  210. return props.options.map((option, index2) => {
  211. const text = option[props.fields.text];
  212. const {
  213. disabled
  214. } = option;
  215. const value = option[props.fields.value];
  216. const data = {
  217. role: "button",
  218. style: optionStyle,
  219. tabindex: disabled ? -1 : 0,
  220. class: [bem$1("item", {
  221. disabled,
  222. selected: value === props.value
  223. }), option.className],
  224. onClick: () => onClickOption(index2)
  225. };
  226. const childData = {
  227. class: "van-ellipsis",
  228. [props.allowHtml ? "innerHTML" : "textContent"]: text
  229. };
  230. return createVNode("li", data, [slots.option ? slots.option(option, index2) : createVNode("div", childData, null)]);
  231. });
  232. };
  233. useParent(PICKER_KEY);
  234. useExpose({
  235. stopMomentum
  236. });
  237. watchEffect(() => {
  238. const index2 = moving ? Math.floor(-currentOffset.value / props.optionHeight) : props.options.findIndex((option) => option[props.fields.value] === props.value);
  239. const enabledIndex = findIndexOfEnabledOption(props.options, index2);
  240. const offset = -enabledIndex * props.optionHeight;
  241. if (moving && enabledIndex < index2)
  242. stopMomentum();
  243. currentOffset.value = offset;
  244. });
  245. useEventListener("touchmove", onTouchMove, {
  246. target: root
  247. });
  248. return () => createVNode("div", {
  249. "ref": root,
  250. "class": bem$1(),
  251. "onTouchstartPassive": onTouchStart,
  252. "onTouchend": onTouchEnd,
  253. "onTouchcancel": onTouchEnd
  254. }, [createVNode("ul", {
  255. "ref": wrapper,
  256. "style": {
  257. transform: `translate3d(0, ${currentOffset.value + baseOffset()}px, 0)`,
  258. transitionDuration: `${currentDuration.value}ms`,
  259. transitionProperty: currentDuration.value ? "all" : "none"
  260. },
  261. "class": bem$1("wrapper"),
  262. "onTransitionend": stopMomentum
  263. }, [renderOptions()])]);
  264. }
  265. });
  266. const [name$1] = createNamespace("picker-toolbar");
  267. const pickerToolbarProps = {
  268. title: String,
  269. cancelButtonText: String,
  270. confirmButtonText: String
  271. };
  272. const pickerToolbarSlots = ["cancel", "confirm", "title", "toolbar"];
  273. const pickerToolbarPropKeys = Object.keys(pickerToolbarProps);
  274. var stdin_default$1 = defineComponent({
  275. name: name$1,
  276. props: pickerToolbarProps,
  277. emits: ["confirm", "cancel"],
  278. setup(props, {
  279. emit,
  280. slots
  281. }) {
  282. const renderTitle = () => {
  283. if (slots.title) {
  284. return slots.title();
  285. }
  286. if (props.title) {
  287. return createVNode("div", {
  288. "class": [bem$2("title"), "van-ellipsis"]
  289. }, [props.title]);
  290. }
  291. };
  292. const onCancel = () => emit("cancel");
  293. const onConfirm = () => emit("confirm");
  294. const renderCancel = () => {
  295. var _a;
  296. const text = (_a = props.cancelButtonText) != null ? _a : t("cancel");
  297. if (!slots.cancel && !text) {
  298. return;
  299. }
  300. return createVNode("button", {
  301. "type": "button",
  302. "class": [bem$2("cancel"), HAPTICS_FEEDBACK],
  303. "onClick": onCancel
  304. }, [slots.cancel ? slots.cancel() : text]);
  305. };
  306. const renderConfirm = () => {
  307. var _a;
  308. const text = (_a = props.confirmButtonText) != null ? _a : t("confirm");
  309. if (!slots.confirm && !text) {
  310. return;
  311. }
  312. return createVNode("button", {
  313. "type": "button",
  314. "class": [bem$2("confirm"), HAPTICS_FEEDBACK],
  315. "onClick": onConfirm
  316. }, [slots.confirm ? slots.confirm() : text]);
  317. };
  318. return () => createVNode("div", {
  319. "class": bem$2("toolbar")
  320. }, [slots.toolbar ? slots.toolbar() : [renderCancel(), renderTitle(), renderConfirm()]]);
  321. }
  322. });
  323. const [name, bem] = createNamespace("picker-group");
  324. const PICKER_GROUP_KEY = Symbol(name);
  325. extend({
  326. tabs: makeArrayProp(),
  327. activeTab: makeNumericProp(0),
  328. nextStepText: String,
  329. showToolbar: truthProp
  330. }, pickerToolbarProps);
  331. const pickerSharedProps = extend({
  332. loading: Boolean,
  333. readonly: Boolean,
  334. allowHtml: Boolean,
  335. optionHeight: makeNumericProp(44),
  336. showToolbar: truthProp,
  337. swipeDuration: makeNumericProp(1e3),
  338. visibleOptionNum: makeNumericProp(6)
  339. }, pickerToolbarProps);
  340. const pickerProps = extend({}, pickerSharedProps, {
  341. columns: makeArrayProp(),
  342. modelValue: makeArrayProp(),
  343. toolbarPosition: makeStringProp("top"),
  344. columnsFieldNames: Object
  345. });
  346. var stdin_default = defineComponent({
  347. name: name$3,
  348. props: pickerProps,
  349. emits: ["confirm", "cancel", "change", "scrollInto", "clickOption", "update:modelValue"],
  350. setup(props, {
  351. emit,
  352. slots
  353. }) {
  354. const columnsRef = ref();
  355. const selectedValues = ref(props.modelValue.slice(0));
  356. const {
  357. parent
  358. } = useParent(PICKER_GROUP_KEY);
  359. const {
  360. children,
  361. linkChildren
  362. } = useChildren(PICKER_KEY);
  363. linkChildren();
  364. const fields = computed(() => assignDefaultFields(props.columnsFieldNames));
  365. const optionHeight = computed(() => unitToPx(props.optionHeight));
  366. const columnsType = computed(() => getColumnsType(props.columns, fields.value));
  367. const currentColumns = computed(() => {
  368. const {
  369. columns
  370. } = props;
  371. switch (columnsType.value) {
  372. case "multiple":
  373. return columns;
  374. case "cascade":
  375. return formatCascadeColumns(columns, fields.value, selectedValues);
  376. default:
  377. return [columns];
  378. }
  379. });
  380. const hasOptions = computed(() => currentColumns.value.some((options) => options.length));
  381. const selectedOptions = computed(() => currentColumns.value.map((options, index2) => findOptionByValue(options, selectedValues.value[index2], fields.value)));
  382. const selectedIndexes = computed(() => currentColumns.value.map((options, index2) => options.findIndex((option) => option[fields.value.value] === selectedValues.value[index2])));
  383. const setValue = (index2, value) => {
  384. if (selectedValues.value[index2] !== value) {
  385. const newValues = selectedValues.value.slice(0);
  386. newValues[index2] = value;
  387. selectedValues.value = newValues;
  388. }
  389. };
  390. const getEventParams = () => ({
  391. selectedValues: selectedValues.value.slice(0),
  392. selectedOptions: selectedOptions.value,
  393. selectedIndexes: selectedIndexes.value
  394. });
  395. const onChange = (value, columnIndex) => {
  396. setValue(columnIndex, value);
  397. if (columnsType.value === "cascade") {
  398. selectedValues.value.forEach((value2, index2) => {
  399. const options = currentColumns.value[index2];
  400. if (!isOptionExist(options, value2, fields.value)) {
  401. setValue(index2, options.length ? options[0][fields.value.value] : void 0);
  402. }
  403. });
  404. }
  405. nextTick(() => {
  406. emit("change", extend({
  407. columnIndex
  408. }, getEventParams()));
  409. });
  410. };
  411. const onClickOption = (currentOption, columnIndex) => {
  412. const params = {
  413. columnIndex,
  414. currentOption
  415. };
  416. emit("clickOption", extend(getEventParams(), params));
  417. emit("scrollInto", params);
  418. };
  419. const confirm = () => {
  420. children.forEach((child) => child.stopMomentum());
  421. const params = getEventParams();
  422. nextTick(() => {
  423. emit("confirm", params);
  424. });
  425. return params;
  426. };
  427. const cancel = () => emit("cancel", getEventParams());
  428. const renderColumnItems = () => currentColumns.value.map((options, columnIndex) => createVNode(stdin_default$2, {
  429. "value": selectedValues.value[columnIndex],
  430. "fields": fields.value,
  431. "options": options,
  432. "readonly": props.readonly,
  433. "allowHtml": props.allowHtml,
  434. "optionHeight": optionHeight.value,
  435. "swipeDuration": props.swipeDuration,
  436. "visibleOptionNum": props.visibleOptionNum,
  437. "onChange": (value) => onChange(value, columnIndex),
  438. "onClickOption": (option) => onClickOption(option, columnIndex),
  439. "onScrollInto": (option) => {
  440. emit("scrollInto", {
  441. currentOption: option,
  442. columnIndex
  443. });
  444. }
  445. }, {
  446. option: slots.option
  447. }));
  448. const renderMask = (wrapHeight) => {
  449. if (hasOptions.value) {
  450. const frameStyle = {
  451. height: `${optionHeight.value}px`
  452. };
  453. const maskStyle = {
  454. backgroundSize: `100% ${(wrapHeight - optionHeight.value) / 2}px`
  455. };
  456. return [createVNode("div", {
  457. "class": bem$2("mask"),
  458. "style": maskStyle
  459. }, null), createVNode("div", {
  460. "class": [BORDER_UNSET_TOP_BOTTOM, bem$2("frame")],
  461. "style": frameStyle
  462. }, null)];
  463. }
  464. };
  465. const renderColumns = () => {
  466. const wrapHeight = optionHeight.value * +props.visibleOptionNum;
  467. const columnsStyle = {
  468. height: `${wrapHeight}px`
  469. };
  470. return createVNode("div", {
  471. "ref": columnsRef,
  472. "class": bem$2("columns"),
  473. "style": columnsStyle
  474. }, [renderColumnItems(), renderMask(wrapHeight)]);
  475. };
  476. const renderToolbar = () => {
  477. if (props.showToolbar && !parent) {
  478. return createVNode(stdin_default$1, mergeProps(pick(props, pickerToolbarPropKeys), {
  479. "onConfirm": confirm,
  480. "onCancel": cancel
  481. }), pick(slots, pickerToolbarSlots));
  482. }
  483. };
  484. watch(currentColumns, (columns) => {
  485. columns.forEach((options, index2) => {
  486. if (options.length && !isOptionExist(options, selectedValues.value[index2], fields.value)) {
  487. setValue(index2, getFirstEnabledOption(options)[fields.value.value]);
  488. }
  489. });
  490. }, {
  491. immediate: true
  492. });
  493. let lastEmittedModelValue;
  494. watch(() => props.modelValue, (newValues) => {
  495. if (!isSameValue(newValues, selectedValues.value) && !isSameValue(newValues, lastEmittedModelValue)) {
  496. selectedValues.value = newValues.slice(0);
  497. lastEmittedModelValue = newValues.slice(0);
  498. }
  499. }, {
  500. deep: true
  501. });
  502. watch(selectedValues, (newValues) => {
  503. if (!isSameValue(newValues, props.modelValue)) {
  504. lastEmittedModelValue = newValues.slice(0);
  505. emit("update:modelValue", lastEmittedModelValue);
  506. }
  507. }, {
  508. immediate: true
  509. });
  510. useEventListener("touchmove", preventDefault, {
  511. target: columnsRef
  512. });
  513. const getSelectedOptions = () => selectedOptions.value;
  514. useExpose({
  515. confirm,
  516. getSelectedOptions
  517. });
  518. return () => {
  519. var _a, _b;
  520. return createVNode("div", {
  521. "class": bem$2()
  522. }, [props.toolbarPosition === "top" ? renderToolbar() : null, props.loading ? createVNode(Loading, {
  523. "class": bem$2("loading")
  524. }, null) : null, (_a = slots["columns-top"]) == null ? void 0 : _a.call(slots), renderColumns(), (_b = slots["columns-bottom"]) == null ? void 0 : _b.call(slots), props.toolbarPosition === "bottom" ? renderToolbar() : null]);
  525. };
  526. }
  527. });
  528. const Picker = withInstall(stdin_default);
  529. const index$6 = "";
  530. const index$5 = "";
  531. const index$4 = "";
  532. const index$3 = "";
  533. const index$2 = "";
  534. const index$1 = "";
  535. const index = "";
  536. export {
  537. Picker as P,
  538. pickerSharedProps as p
  539. };