Picker.mjs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import { ref, watch, computed, nextTick, defineComponent, createVNode as _createVNode, mergeProps as _mergeProps } from "vue";
  2. import { pick, extend, unitToPx, truthProp, isSameValue, makeArrayProp, preventDefault, makeStringProp, makeNumericProp, BORDER_UNSET_TOP_BOTTOM } from "../utils/index.mjs";
  3. import { bem, name, isOptionExist, getColumnsType, findOptionByValue, assignDefaultFields, formatCascadeColumns, getFirstEnabledOption } from "./utils.mjs";
  4. import { useChildren, useEventListener, useParent } 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. import Toolbar, { pickerToolbarPropKeys, pickerToolbarProps, pickerToolbarSlots } from "./PickerToolbar.mjs";
  9. import { PICKER_GROUP_KEY } from "../picker-group/PickerGroup.mjs";
  10. const pickerSharedProps = extend({
  11. loading: Boolean,
  12. readonly: Boolean,
  13. allowHtml: Boolean,
  14. optionHeight: makeNumericProp(44),
  15. showToolbar: truthProp,
  16. swipeDuration: makeNumericProp(1e3),
  17. visibleOptionNum: makeNumericProp(6)
  18. }, pickerToolbarProps);
  19. const pickerProps = extend({}, pickerSharedProps, {
  20. columns: makeArrayProp(),
  21. modelValue: makeArrayProp(),
  22. toolbarPosition: makeStringProp("top"),
  23. columnsFieldNames: Object
  24. });
  25. var stdin_default = defineComponent({
  26. name,
  27. props: pickerProps,
  28. emits: ["confirm", "cancel", "change", "scrollInto", "clickOption", "update:modelValue"],
  29. setup(props, {
  30. emit,
  31. slots
  32. }) {
  33. const columnsRef = ref();
  34. const selectedValues = ref(props.modelValue.slice(0));
  35. const {
  36. parent
  37. } = useParent(PICKER_GROUP_KEY);
  38. const {
  39. children,
  40. linkChildren
  41. } = useChildren(PICKER_KEY);
  42. linkChildren();
  43. const fields = computed(() => assignDefaultFields(props.columnsFieldNames));
  44. const optionHeight = computed(() => unitToPx(props.optionHeight));
  45. const columnsType = computed(() => getColumnsType(props.columns, fields.value));
  46. const currentColumns = computed(() => {
  47. const {
  48. columns
  49. } = props;
  50. switch (columnsType.value) {
  51. case "multiple":
  52. return columns;
  53. case "cascade":
  54. return formatCascadeColumns(columns, fields.value, selectedValues);
  55. default:
  56. return [columns];
  57. }
  58. });
  59. const hasOptions = computed(() => currentColumns.value.some((options) => options.length));
  60. const selectedOptions = computed(() => currentColumns.value.map((options, index) => findOptionByValue(options, selectedValues.value[index], fields.value)));
  61. const selectedIndexes = computed(() => currentColumns.value.map((options, index) => options.findIndex((option) => option[fields.value.value] === selectedValues.value[index])));
  62. const setValue = (index, value) => {
  63. if (selectedValues.value[index] !== value) {
  64. const newValues = selectedValues.value.slice(0);
  65. newValues[index] = value;
  66. selectedValues.value = newValues;
  67. }
  68. };
  69. const getEventParams = () => ({
  70. selectedValues: selectedValues.value.slice(0),
  71. selectedOptions: selectedOptions.value,
  72. selectedIndexes: selectedIndexes.value
  73. });
  74. const onChange = (value, columnIndex) => {
  75. setValue(columnIndex, value);
  76. if (columnsType.value === "cascade") {
  77. selectedValues.value.forEach((value2, index) => {
  78. const options = currentColumns.value[index];
  79. if (!isOptionExist(options, value2, fields.value)) {
  80. setValue(index, options.length ? options[0][fields.value.value] : void 0);
  81. }
  82. });
  83. }
  84. nextTick(() => {
  85. emit("change", extend({
  86. columnIndex
  87. }, getEventParams()));
  88. });
  89. };
  90. const onClickOption = (currentOption, columnIndex) => {
  91. const params = {
  92. columnIndex,
  93. currentOption
  94. };
  95. emit("clickOption", extend(getEventParams(), params));
  96. emit("scrollInto", params);
  97. };
  98. const confirm = () => {
  99. children.forEach((child) => child.stopMomentum());
  100. const params = getEventParams();
  101. nextTick(() => {
  102. emit("confirm", params);
  103. });
  104. return params;
  105. };
  106. const cancel = () => emit("cancel", getEventParams());
  107. const renderColumnItems = () => currentColumns.value.map((options, columnIndex) => _createVNode(Column, {
  108. "value": selectedValues.value[columnIndex],
  109. "fields": fields.value,
  110. "options": options,
  111. "readonly": props.readonly,
  112. "allowHtml": props.allowHtml,
  113. "optionHeight": optionHeight.value,
  114. "swipeDuration": props.swipeDuration,
  115. "visibleOptionNum": props.visibleOptionNum,
  116. "onChange": (value) => onChange(value, columnIndex),
  117. "onClickOption": (option) => onClickOption(option, columnIndex),
  118. "onScrollInto": (option) => {
  119. emit("scrollInto", {
  120. currentOption: option,
  121. columnIndex
  122. });
  123. }
  124. }, {
  125. option: slots.option
  126. }));
  127. const renderMask = (wrapHeight) => {
  128. if (hasOptions.value) {
  129. const frameStyle = {
  130. height: `${optionHeight.value}px`
  131. };
  132. const maskStyle = {
  133. backgroundSize: `100% ${(wrapHeight - optionHeight.value) / 2}px`
  134. };
  135. return [_createVNode("div", {
  136. "class": bem("mask"),
  137. "style": maskStyle
  138. }, null), _createVNode("div", {
  139. "class": [BORDER_UNSET_TOP_BOTTOM, bem("frame")],
  140. "style": frameStyle
  141. }, null)];
  142. }
  143. };
  144. const renderColumns = () => {
  145. const wrapHeight = optionHeight.value * +props.visibleOptionNum;
  146. const columnsStyle = {
  147. height: `${wrapHeight}px`
  148. };
  149. return _createVNode("div", {
  150. "ref": columnsRef,
  151. "class": bem("columns"),
  152. "style": columnsStyle
  153. }, [renderColumnItems(), renderMask(wrapHeight)]);
  154. };
  155. const renderToolbar = () => {
  156. if (props.showToolbar && !parent) {
  157. return _createVNode(Toolbar, _mergeProps(pick(props, pickerToolbarPropKeys), {
  158. "onConfirm": confirm,
  159. "onCancel": cancel
  160. }), pick(slots, pickerToolbarSlots));
  161. }
  162. };
  163. watch(currentColumns, (columns) => {
  164. columns.forEach((options, index) => {
  165. if (options.length && !isOptionExist(options, selectedValues.value[index], fields.value)) {
  166. setValue(index, getFirstEnabledOption(options)[fields.value.value]);
  167. }
  168. });
  169. }, {
  170. immediate: true
  171. });
  172. let lastEmittedModelValue;
  173. watch(() => props.modelValue, (newValues) => {
  174. if (!isSameValue(newValues, selectedValues.value) && !isSameValue(newValues, lastEmittedModelValue)) {
  175. selectedValues.value = newValues.slice(0);
  176. lastEmittedModelValue = newValues.slice(0);
  177. }
  178. }, {
  179. deep: true
  180. });
  181. watch(selectedValues, (newValues) => {
  182. if (!isSameValue(newValues, props.modelValue)) {
  183. lastEmittedModelValue = newValues.slice(0);
  184. emit("update:modelValue", lastEmittedModelValue);
  185. }
  186. }, {
  187. immediate: true
  188. });
  189. useEventListener("touchmove", preventDefault, {
  190. target: columnsRef
  191. });
  192. const getSelectedOptions = () => selectedOptions.value;
  193. useExpose({
  194. confirm,
  195. getSelectedOptions
  196. });
  197. return () => {
  198. var _a, _b;
  199. return _createVNode("div", {
  200. "class": bem()
  201. }, [props.toolbarPosition === "top" ? renderToolbar() : null, props.loading ? _createVNode(Loading, {
  202. "class": bem("loading")
  203. }, 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]);
  204. };
  205. }
  206. });
  207. export {
  208. stdin_default as default,
  209. pickerProps,
  210. pickerSharedProps
  211. };