Picker.mjs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. import { createVNode as _createVNode } from "vue";
  2. import { ref, watch, computed, defineComponent } from "vue";
  3. import { extend, unitToPx, truthProp, makeArrayProp, preventDefault, makeStringProp, makeNumericProp, createNamespace, HAPTICS_FEEDBACK, BORDER_UNSET_TOP_BOTTOM } from "../utils/index.mjs";
  4. import { useChildren, useEventListener } from "@vant/use";
  5. import { useExpose } from "../composables/use-expose.mjs";
  6. import { Loading } from "../loading/index.mjs";
  7. import Column, { PICKER_KEY } from "./PickerColumn.mjs";
  8. const [name, bem, t] = createNamespace("picker");
  9. const pickerSharedProps = {
  10. title: String,
  11. loading: Boolean,
  12. readonly: Boolean,
  13. allowHtml: Boolean,
  14. itemHeight: makeNumericProp(44),
  15. showToolbar: truthProp,
  16. swipeDuration: makeNumericProp(1e3),
  17. visibleItemCount: makeNumericProp(6),
  18. cancelButtonText: String,
  19. confirmButtonText: String
  20. };
  21. const pickerProps = extend({}, pickerSharedProps, {
  22. columns: makeArrayProp(),
  23. valueKey: String,
  24. defaultIndex: makeNumericProp(0),
  25. toolbarPosition: makeStringProp("top"),
  26. columnsFieldNames: Object
  27. });
  28. var stdin_default = defineComponent({
  29. name,
  30. props: pickerProps,
  31. emits: ["confirm", "cancel", "change"],
  32. setup(props, {
  33. emit,
  34. slots
  35. }) {
  36. if (process.env.NODE_ENV !== "production") {
  37. if (slots.default) {
  38. console.warn('[Vant] Picker: "default" slot is deprecated, please use "toolbar" slot instead.');
  39. }
  40. if (props.valueKey) {
  41. console.warn('[Vant] Picker: "valueKey" prop is deprecated, please use "columnsFieldNames" prop instead.');
  42. }
  43. }
  44. const hasOptions = ref(false);
  45. const columnsRef = ref();
  46. const formattedColumns = ref([]);
  47. const columnsFieldNames = computed(() => {
  48. const {
  49. columnsFieldNames: columnsFieldNames2
  50. } = props;
  51. return {
  52. text: (columnsFieldNames2 == null ? void 0 : columnsFieldNames2.text) || props.valueKey || "text",
  53. values: (columnsFieldNames2 == null ? void 0 : columnsFieldNames2.values) || "values",
  54. children: (columnsFieldNames2 == null ? void 0 : columnsFieldNames2.children) || "children"
  55. };
  56. });
  57. const {
  58. children,
  59. linkChildren
  60. } = useChildren(PICKER_KEY);
  61. linkChildren();
  62. const itemHeight = computed(() => unitToPx(props.itemHeight));
  63. const dataType = computed(() => {
  64. const firstColumn = props.columns[0];
  65. if (typeof firstColumn === "object") {
  66. if (columnsFieldNames.value.children in firstColumn) {
  67. return "cascade";
  68. }
  69. if (columnsFieldNames.value.values in firstColumn) {
  70. return "object";
  71. }
  72. }
  73. return "plain";
  74. });
  75. const formatCascade = () => {
  76. var _a;
  77. const formatted = [];
  78. let cursor = {
  79. [columnsFieldNames.value.children]: props.columns
  80. };
  81. while (cursor && cursor[columnsFieldNames.value.children]) {
  82. const children2 = cursor[columnsFieldNames.value.children];
  83. let defaultIndex = (_a = cursor.defaultIndex) != null ? _a : +props.defaultIndex;
  84. while (children2[defaultIndex] && children2[defaultIndex].disabled) {
  85. if (defaultIndex < children2.length - 1) {
  86. defaultIndex++;
  87. } else {
  88. defaultIndex = 0;
  89. break;
  90. }
  91. }
  92. formatted.push({
  93. [columnsFieldNames.value.values]: cursor[columnsFieldNames.value.children],
  94. className: cursor.className,
  95. defaultIndex
  96. });
  97. cursor = children2[defaultIndex];
  98. }
  99. formattedColumns.value = formatted;
  100. };
  101. const format = () => {
  102. const {
  103. columns
  104. } = props;
  105. if (dataType.value === "plain") {
  106. formattedColumns.value = [{
  107. [columnsFieldNames.value.values]: columns
  108. }];
  109. } else if (dataType.value === "cascade") {
  110. formatCascade();
  111. } else {
  112. formattedColumns.value = columns;
  113. }
  114. hasOptions.value = formattedColumns.value.some((item) => item[columnsFieldNames.value.values] && item[columnsFieldNames.value.values].length !== 0) || children.some((item) => item.hasOptions);
  115. };
  116. const getIndexes = () => children.map((child) => child.state.index);
  117. const setColumnValues = (index, options) => {
  118. const column = children[index];
  119. if (column) {
  120. column.setOptions(options);
  121. hasOptions.value = true;
  122. }
  123. };
  124. const onCascadeChange = (columnIndex) => {
  125. let cursor = {
  126. [columnsFieldNames.value.children]: props.columns
  127. };
  128. const indexes = getIndexes();
  129. for (let i = 0; i <= columnIndex; i++) {
  130. cursor = cursor[columnsFieldNames.value.children][indexes[i]];
  131. }
  132. while (cursor && cursor[columnsFieldNames.value.children]) {
  133. columnIndex++;
  134. setColumnValues(columnIndex, cursor[columnsFieldNames.value.children]);
  135. cursor = cursor[columnsFieldNames.value.children][cursor.defaultIndex || 0];
  136. }
  137. };
  138. const getChild = (index) => children[index];
  139. const getColumnValue = (index) => {
  140. const column = getChild(index);
  141. if (column) {
  142. return column.getValue();
  143. }
  144. };
  145. const setColumnValue = (index, value) => {
  146. const column = getChild(index);
  147. if (column) {
  148. column.setValue(value);
  149. if (dataType.value === "cascade") {
  150. onCascadeChange(index);
  151. }
  152. }
  153. };
  154. const getColumnIndex = (index) => {
  155. const column = getChild(index);
  156. if (column) {
  157. return column.state.index;
  158. }
  159. };
  160. const setColumnIndex = (columnIndex, optionIndex) => {
  161. const column = getChild(columnIndex);
  162. if (column) {
  163. column.setIndex(optionIndex);
  164. if (dataType.value === "cascade") {
  165. onCascadeChange(columnIndex);
  166. }
  167. }
  168. };
  169. const getColumnValues = (index) => {
  170. const column = getChild(index);
  171. if (column) {
  172. return column.state.options;
  173. }
  174. };
  175. const getValues = () => children.map((child) => child.getValue());
  176. const setValues = (values) => {
  177. values.forEach((value, index) => {
  178. setColumnValue(index, value);
  179. });
  180. };
  181. const setIndexes = (indexes) => {
  182. indexes.forEach((optionIndex, columnIndex) => {
  183. setColumnIndex(columnIndex, optionIndex);
  184. });
  185. };
  186. const emitAction = (event) => {
  187. if (dataType.value === "plain") {
  188. emit(event, getColumnValue(0), getColumnIndex(0));
  189. } else {
  190. emit(event, getValues(), getIndexes());
  191. }
  192. };
  193. const onChange = (columnIndex) => {
  194. if (dataType.value === "cascade") {
  195. onCascadeChange(columnIndex);
  196. }
  197. if (dataType.value === "plain") {
  198. emit("change", getColumnValue(0), getColumnIndex(0));
  199. } else {
  200. emit("change", getValues(), columnIndex);
  201. }
  202. };
  203. const confirm = () => {
  204. children.forEach((child) => child.stopMomentum());
  205. emitAction("confirm");
  206. };
  207. const cancel = () => emitAction("cancel");
  208. const renderTitle = () => {
  209. if (slots.title) {
  210. return slots.title();
  211. }
  212. if (props.title) {
  213. return _createVNode("div", {
  214. "class": [bem("title"), "van-ellipsis"]
  215. }, [props.title]);
  216. }
  217. };
  218. const renderCancel = () => {
  219. const text = props.cancelButtonText || t("cancel");
  220. return _createVNode("button", {
  221. "type": "button",
  222. "class": [bem("cancel"), HAPTICS_FEEDBACK],
  223. "onClick": cancel
  224. }, [slots.cancel ? slots.cancel() : text]);
  225. };
  226. const renderConfirm = () => {
  227. const text = props.confirmButtonText || t("confirm");
  228. return _createVNode("button", {
  229. "type": "button",
  230. "class": [bem("confirm"), HAPTICS_FEEDBACK],
  231. "onClick": confirm
  232. }, [slots.confirm ? slots.confirm() : text]);
  233. };
  234. const renderToolbar = () => {
  235. if (props.showToolbar) {
  236. const slot = slots.toolbar || slots.default;
  237. return _createVNode("div", {
  238. "class": bem("toolbar")
  239. }, [slot ? slot() : [renderCancel(), renderTitle(), renderConfirm()]]);
  240. }
  241. };
  242. const renderColumnItems = () => formattedColumns.value.map((item, columnIndex) => {
  243. var _a;
  244. return _createVNode(Column, {
  245. "textKey": columnsFieldNames.value.text,
  246. "readonly": props.readonly,
  247. "allowHtml": props.allowHtml,
  248. "className": item.className,
  249. "itemHeight": itemHeight.value,
  250. "defaultIndex": (_a = item.defaultIndex) != null ? _a : +props.defaultIndex,
  251. "swipeDuration": props.swipeDuration,
  252. "initialOptions": item[columnsFieldNames.value.values],
  253. "visibleItemCount": props.visibleItemCount,
  254. "onChange": () => onChange(columnIndex)
  255. }, {
  256. option: slots.option
  257. });
  258. });
  259. const renderMask = (wrapHeight) => {
  260. if (hasOptions.value) {
  261. const frameStyle = {
  262. height: `${itemHeight.value}px`
  263. };
  264. const maskStyle = {
  265. backgroundSize: `100% ${(wrapHeight - itemHeight.value) / 2}px`
  266. };
  267. return [_createVNode("div", {
  268. "class": bem("mask"),
  269. "style": maskStyle
  270. }, null), _createVNode("div", {
  271. "class": [BORDER_UNSET_TOP_BOTTOM, bem("frame")],
  272. "style": frameStyle
  273. }, null)];
  274. }
  275. };
  276. const renderColumns = () => {
  277. const wrapHeight = itemHeight.value * +props.visibleItemCount;
  278. const columnsStyle = {
  279. height: `${wrapHeight}px`
  280. };
  281. return _createVNode("div", {
  282. "ref": columnsRef,
  283. "class": bem("columns"),
  284. "style": columnsStyle
  285. }, [renderColumnItems(), renderMask(wrapHeight)]);
  286. };
  287. watch(() => props.columns, format, {
  288. immediate: true
  289. });
  290. useEventListener("touchmove", preventDefault, {
  291. target: columnsRef
  292. });
  293. useExpose({
  294. confirm,
  295. getValues,
  296. setValues,
  297. getIndexes,
  298. setIndexes,
  299. getColumnIndex,
  300. setColumnIndex,
  301. getColumnValue,
  302. setColumnValue,
  303. getColumnValues,
  304. setColumnValues
  305. });
  306. return () => {
  307. var _a, _b;
  308. return _createVNode("div", {
  309. "class": bem()
  310. }, [props.toolbarPosition === "top" ? renderToolbar() : null, props.loading ? _createVNode(Loading, {
  311. "class": bem("loading")
  312. }, 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]);
  313. };
  314. }
  315. });
  316. export {
  317. stdin_default as default,
  318. pickerSharedProps
  319. };