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"; import { u as useExpose } from "./use-scope-id-0b5b8615.js"; import { L as Loading } from "./index-217c49a0.js"; import { u as useTouch } from "./index-eef3af38.js"; 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"; const [name$3, bem$2, t] = createNamespace("picker"); const getFirstEnabledOption = (options) => options.find((option) => !option.disabled) || options[0]; function getColumnsType(columns, fields) { const firstColumn = columns[0]; if (firstColumn) { if (Array.isArray(firstColumn)) { return "multiple"; } if (fields.children in firstColumn) { return "cascade"; } } return "default"; } function findIndexOfEnabledOption(options, index2) { index2 = clamp(index2, 0, options.length); for (let i = index2; i < options.length; i++) { if (!options[i].disabled) return i; } for (let i = index2 - 1; i >= 0; i--) { if (!options[i].disabled) return i; } return 0; } const isOptionExist = (options, value, fields) => value !== void 0 && !!options.find((option) => option[fields.value] === value); function findOptionByValue(options, value, fields) { const index2 = options.findIndex((option) => option[fields.value] === value); const enabledIndex = findIndexOfEnabledOption(options, index2); return options[enabledIndex]; } function formatCascadeColumns(columns, fields, selectedValues) { const formatted = []; let cursor = { [fields.children]: columns }; let columnIndex = 0; while (cursor && cursor[fields.children]) { const options = cursor[fields.children]; const value = selectedValues.value[columnIndex]; cursor = isDef(value) ? findOptionByValue(options, value, fields) : void 0; if (!cursor && options.length) { const firstValue = getFirstEnabledOption(options)[fields.value]; cursor = findOptionByValue(options, firstValue, fields); } columnIndex++; formatted.push(options); } return formatted; } function getElementTranslateY(element) { const { transform } = window.getComputedStyle(element); const translateY = transform.slice(7, transform.length - 1).split(", ")[5]; return Number(translateY); } function assignDefaultFields(fields) { return extend( { text: "text", value: "value", children: "children" }, fields ); } const DEFAULT_DURATION = 200; const MOMENTUM_TIME = 300; const MOMENTUM_DISTANCE = 15; const [name$2, bem$1] = createNamespace("picker-column"); const PICKER_KEY = Symbol(name$2); var stdin_default$2 = defineComponent({ name: name$2, props: { value: numericProp, fields: makeRequiredProp(Object), options: makeArrayProp(), readonly: Boolean, allowHtml: Boolean, optionHeight: makeRequiredProp(Number), swipeDuration: makeRequiredProp(numericProp), visibleOptionNum: makeRequiredProp(numericProp) }, emits: ["change", "clickOption", "scrollInto"], setup(props, { emit, slots }) { let moving; let startOffset; let touchStartTime; let momentumOffset; let transitionEndTrigger; const root = ref(); const wrapper = ref(); const currentOffset = ref(0); const currentDuration = ref(0); const touch = useTouch(); const count = () => props.options.length; const baseOffset = () => props.optionHeight * (+props.visibleOptionNum - 1) / 2; const updateValueByIndex = (index2) => { let enabledIndex = findIndexOfEnabledOption(props.options, index2); const offset = -enabledIndex * props.optionHeight; const trigger = () => { if (enabledIndex > count() - 1) { enabledIndex = findIndexOfEnabledOption(props.options, index2); } const value = props.options[enabledIndex][props.fields.value]; if (value !== props.value) { emit("change", value); } }; if (moving && offset !== currentOffset.value) { transitionEndTrigger = trigger; } else { trigger(); } currentOffset.value = offset; }; const isReadonly = () => props.readonly || !props.options.length; const onClickOption = (index2) => { if (moving || isReadonly()) { return; } transitionEndTrigger = null; currentDuration.value = DEFAULT_DURATION; updateValueByIndex(index2); emit("clickOption", props.options[index2]); }; const getIndexByOffset = (offset) => clamp(Math.round(-offset / props.optionHeight), 0, count() - 1); const currentIndex = computed(() => getIndexByOffset(currentOffset.value)); const momentum = (distance, duration) => { const speed = Math.abs(distance / duration); distance = currentOffset.value + speed / 3e-3 * (distance < 0 ? -1 : 1); const index2 = getIndexByOffset(distance); currentDuration.value = +props.swipeDuration; updateValueByIndex(index2); }; const stopMomentum = () => { moving = false; currentDuration.value = 0; if (transitionEndTrigger) { transitionEndTrigger(); transitionEndTrigger = null; } }; const onTouchStart = (event) => { if (isReadonly()) { return; } touch.start(event); if (moving) { const translateY = getElementTranslateY(wrapper.value); currentOffset.value = Math.min(0, translateY - baseOffset()); } currentDuration.value = 0; startOffset = currentOffset.value; touchStartTime = Date.now(); momentumOffset = startOffset; transitionEndTrigger = null; }; const onTouchMove = (event) => { if (isReadonly()) { return; } touch.move(event); if (touch.isVertical()) { moving = true; preventDefault(event, true); } const newOffset = clamp(startOffset + touch.deltaY.value, -(count() * props.optionHeight), props.optionHeight); const newIndex = getIndexByOffset(newOffset); if (newIndex !== currentIndex.value) { emit("scrollInto", props.options[newIndex]); } currentOffset.value = newOffset; const now = Date.now(); if (now - touchStartTime > MOMENTUM_TIME) { touchStartTime = now; momentumOffset = newOffset; } }; const onTouchEnd = () => { if (isReadonly()) { return; } const distance = currentOffset.value - momentumOffset; const duration = Date.now() - touchStartTime; const startMomentum = duration < MOMENTUM_TIME && Math.abs(distance) > MOMENTUM_DISTANCE; if (startMomentum) { momentum(distance, duration); return; } const index2 = getIndexByOffset(currentOffset.value); currentDuration.value = DEFAULT_DURATION; updateValueByIndex(index2); setTimeout(() => { moving = false; }, 0); }; const renderOptions = () => { const optionStyle = { height: `${props.optionHeight}px` }; return props.options.map((option, index2) => { const text = option[props.fields.text]; const { disabled } = option; const value = option[props.fields.value]; const data = { role: "button", style: optionStyle, tabindex: disabled ? -1 : 0, class: [bem$1("item", { disabled, selected: value === props.value }), option.className], onClick: () => onClickOption(index2) }; const childData = { class: "van-ellipsis", [props.allowHtml ? "innerHTML" : "textContent"]: text }; return createVNode("li", data, [slots.option ? slots.option(option, index2) : createVNode("div", childData, null)]); }); }; useParent(PICKER_KEY); useExpose({ stopMomentum }); watchEffect(() => { const index2 = moving ? Math.floor(-currentOffset.value / props.optionHeight) : props.options.findIndex((option) => option[props.fields.value] === props.value); const enabledIndex = findIndexOfEnabledOption(props.options, index2); const offset = -enabledIndex * props.optionHeight; if (moving && enabledIndex < index2) stopMomentum(); currentOffset.value = offset; }); useEventListener("touchmove", onTouchMove, { target: root }); return () => createVNode("div", { "ref": root, "class": bem$1(), "onTouchstartPassive": onTouchStart, "onTouchend": onTouchEnd, "onTouchcancel": onTouchEnd }, [createVNode("ul", { "ref": wrapper, "style": { transform: `translate3d(0, ${currentOffset.value + baseOffset()}px, 0)`, transitionDuration: `${currentDuration.value}ms`, transitionProperty: currentDuration.value ? "all" : "none" }, "class": bem$1("wrapper"), "onTransitionend": stopMomentum }, [renderOptions()])]); } }); const [name$1] = createNamespace("picker-toolbar"); const pickerToolbarProps = { title: String, cancelButtonText: String, confirmButtonText: String }; const pickerToolbarSlots = ["cancel", "confirm", "title", "toolbar"]; const pickerToolbarPropKeys = Object.keys(pickerToolbarProps); var stdin_default$1 = defineComponent({ name: name$1, props: pickerToolbarProps, emits: ["confirm", "cancel"], setup(props, { emit, slots }) { const renderTitle = () => { if (slots.title) { return slots.title(); } if (props.title) { return createVNode("div", { "class": [bem$2("title"), "van-ellipsis"] }, [props.title]); } }; const onCancel = () => emit("cancel"); const onConfirm = () => emit("confirm"); const renderCancel = () => { var _a; const text = (_a = props.cancelButtonText) != null ? _a : t("cancel"); if (!slots.cancel && !text) { return; } return createVNode("button", { "type": "button", "class": [bem$2("cancel"), HAPTICS_FEEDBACK], "onClick": onCancel }, [slots.cancel ? slots.cancel() : text]); }; const renderConfirm = () => { var _a; const text = (_a = props.confirmButtonText) != null ? _a : t("confirm"); if (!slots.confirm && !text) { return; } return createVNode("button", { "type": "button", "class": [bem$2("confirm"), HAPTICS_FEEDBACK], "onClick": onConfirm }, [slots.confirm ? slots.confirm() : text]); }; return () => createVNode("div", { "class": bem$2("toolbar") }, [slots.toolbar ? slots.toolbar() : [renderCancel(), renderTitle(), renderConfirm()]]); } }); const [name, bem] = createNamespace("picker-group"); const PICKER_GROUP_KEY = Symbol(name); extend({ tabs: makeArrayProp(), activeTab: makeNumericProp(0), nextStepText: String, showToolbar: truthProp }, pickerToolbarProps); const pickerSharedProps = extend({ loading: Boolean, readonly: Boolean, allowHtml: Boolean, optionHeight: makeNumericProp(44), showToolbar: truthProp, swipeDuration: makeNumericProp(1e3), visibleOptionNum: makeNumericProp(6) }, pickerToolbarProps); const pickerProps = extend({}, pickerSharedProps, { columns: makeArrayProp(), modelValue: makeArrayProp(), toolbarPosition: makeStringProp("top"), columnsFieldNames: Object }); var stdin_default = defineComponent({ name: name$3, props: pickerProps, emits: ["confirm", "cancel", "change", "scrollInto", "clickOption", "update:modelValue"], setup(props, { emit, slots }) { const columnsRef = ref(); const selectedValues = ref(props.modelValue.slice(0)); const { parent } = useParent(PICKER_GROUP_KEY); const { children, linkChildren } = useChildren(PICKER_KEY); linkChildren(); const fields = computed(() => assignDefaultFields(props.columnsFieldNames)); const optionHeight = computed(() => unitToPx(props.optionHeight)); const columnsType = computed(() => getColumnsType(props.columns, fields.value)); const currentColumns = computed(() => { const { columns } = props; switch (columnsType.value) { case "multiple": return columns; case "cascade": return formatCascadeColumns(columns, fields.value, selectedValues); default: return [columns]; } }); const hasOptions = computed(() => currentColumns.value.some((options) => options.length)); const selectedOptions = computed(() => currentColumns.value.map((options, index2) => findOptionByValue(options, selectedValues.value[index2], fields.value))); const selectedIndexes = computed(() => currentColumns.value.map((options, index2) => options.findIndex((option) => option[fields.value.value] === selectedValues.value[index2]))); const setValue = (index2, value) => { if (selectedValues.value[index2] !== value) { const newValues = selectedValues.value.slice(0); newValues[index2] = value; selectedValues.value = newValues; } }; const getEventParams = () => ({ selectedValues: selectedValues.value.slice(0), selectedOptions: selectedOptions.value, selectedIndexes: selectedIndexes.value }); const onChange = (value, columnIndex) => { setValue(columnIndex, value); if (columnsType.value === "cascade") { selectedValues.value.forEach((value2, index2) => { const options = currentColumns.value[index2]; if (!isOptionExist(options, value2, fields.value)) { setValue(index2, options.length ? options[0][fields.value.value] : void 0); } }); } nextTick(() => { emit("change", extend({ columnIndex }, getEventParams())); }); }; const onClickOption = (currentOption, columnIndex) => { const params = { columnIndex, currentOption }; emit("clickOption", extend(getEventParams(), params)); emit("scrollInto", params); }; const confirm = () => { children.forEach((child) => child.stopMomentum()); const params = getEventParams(); nextTick(() => { emit("confirm", params); }); return params; }; const cancel = () => emit("cancel", getEventParams()); const renderColumnItems = () => currentColumns.value.map((options, columnIndex) => createVNode(stdin_default$2, { "value": selectedValues.value[columnIndex], "fields": fields.value, "options": options, "readonly": props.readonly, "allowHtml": props.allowHtml, "optionHeight": optionHeight.value, "swipeDuration": props.swipeDuration, "visibleOptionNum": props.visibleOptionNum, "onChange": (value) => onChange(value, columnIndex), "onClickOption": (option) => onClickOption(option, columnIndex), "onScrollInto": (option) => { emit("scrollInto", { currentOption: option, columnIndex }); } }, { option: slots.option })); const renderMask = (wrapHeight) => { if (hasOptions.value) { const frameStyle = { height: `${optionHeight.value}px` }; const maskStyle = { backgroundSize: `100% ${(wrapHeight - optionHeight.value) / 2}px` }; return [createVNode("div", { "class": bem$2("mask"), "style": maskStyle }, null), createVNode("div", { "class": [BORDER_UNSET_TOP_BOTTOM, bem$2("frame")], "style": frameStyle }, null)]; } }; const renderColumns = () => { const wrapHeight = optionHeight.value * +props.visibleOptionNum; const columnsStyle = { height: `${wrapHeight}px` }; return createVNode("div", { "ref": columnsRef, "class": bem$2("columns"), "style": columnsStyle }, [renderColumnItems(), renderMask(wrapHeight)]); }; const renderToolbar = () => { if (props.showToolbar && !parent) { return createVNode(stdin_default$1, mergeProps(pick(props, pickerToolbarPropKeys), { "onConfirm": confirm, "onCancel": cancel }), pick(slots, pickerToolbarSlots)); } }; watch(currentColumns, (columns) => { columns.forEach((options, index2) => { if (options.length && !isOptionExist(options, selectedValues.value[index2], fields.value)) { setValue(index2, getFirstEnabledOption(options)[fields.value.value]); } }); }, { immediate: true }); let lastEmittedModelValue; watch(() => props.modelValue, (newValues) => { if (!isSameValue(newValues, selectedValues.value) && !isSameValue(newValues, lastEmittedModelValue)) { selectedValues.value = newValues.slice(0); lastEmittedModelValue = newValues.slice(0); } }, { deep: true }); watch(selectedValues, (newValues) => { if (!isSameValue(newValues, props.modelValue)) { lastEmittedModelValue = newValues.slice(0); emit("update:modelValue", lastEmittedModelValue); } }, { immediate: true }); useEventListener("touchmove", preventDefault, { target: columnsRef }); const getSelectedOptions = () => selectedOptions.value; useExpose({ confirm, getSelectedOptions }); return () => { var _a, _b; return createVNode("div", { "class": bem$2() }, [props.toolbarPosition === "top" ? renderToolbar() : null, props.loading ? createVNode(Loading, { "class": bem$2("loading") }, 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]); }; } }); const Picker = withInstall(stdin_default); const index$6 = ""; const index$5 = ""; const index$4 = ""; const index$3 = ""; const index$2 = ""; const index$1 = ""; const index = ""; export { Picker as P, pickerSharedProps as p };