123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- import { ref, computed, watchEffect, defineComponent, createVNode as _createVNode } from "vue";
- import { clamp, numericProp, makeArrayProp, preventDefault, createNamespace, makeRequiredProp } from "../utils/index.mjs";
- import { getElementTranslateY, findIndexOfEnabledOption } from "./utils.mjs";
- import { useEventListener, useParent } from "@vant/use";
- import { useTouch } from "../composables/use-touch.mjs";
- import { useExpose } from "../composables/use-expose.mjs";
- const DEFAULT_DURATION = 200;
- const MOMENTUM_TIME = 300;
- const MOMENTUM_DISTANCE = 15;
- const [name, bem] = createNamespace("picker-column");
- const PICKER_KEY = Symbol(name);
- var stdin_default = defineComponent({
- name,
- 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 = (index) => {
- let enabledIndex = findIndexOfEnabledOption(props.options, index);
- const offset = -enabledIndex * props.optionHeight;
- const trigger = () => {
- if (enabledIndex > count() - 1) {
- enabledIndex = findIndexOfEnabledOption(props.options, index);
- }
- 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 = (index) => {
- if (moving || isReadonly()) {
- return;
- }
- transitionEndTrigger = null;
- currentDuration.value = DEFAULT_DURATION;
- updateValueByIndex(index);
- emit("clickOption", props.options[index]);
- };
- 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 index = getIndexByOffset(distance);
- currentDuration.value = +props.swipeDuration;
- updateValueByIndex(index);
- };
- 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 index = getIndexByOffset(currentOffset.value);
- currentDuration.value = DEFAULT_DURATION;
- updateValueByIndex(index);
- setTimeout(() => {
- moving = false;
- }, 0);
- };
- const renderOptions = () => {
- const optionStyle = {
- height: `${props.optionHeight}px`
- };
- return props.options.map((option, index) => {
- 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("item", {
- disabled,
- selected: value === props.value
- }), option.className],
- onClick: () => onClickOption(index)
- };
- const childData = {
- class: "van-ellipsis",
- [props.allowHtml ? "innerHTML" : "textContent"]: text
- };
- return _createVNode("li", data, [slots.option ? slots.option(option, index) : _createVNode("div", childData, null)]);
- });
- };
- useParent(PICKER_KEY);
- useExpose({
- stopMomentum
- });
- watchEffect(() => {
- const index = moving ? Math.floor(-currentOffset.value / props.optionHeight) : props.options.findIndex((option) => option[props.fields.value] === props.value);
- const enabledIndex = findIndexOfEnabledOption(props.options, index);
- const offset = -enabledIndex * props.optionHeight;
- if (moving && enabledIndex < index) stopMomentum();
- currentOffset.value = offset;
- });
- useEventListener("touchmove", onTouchMove, {
- target: root
- });
- return () => _createVNode("div", {
- "ref": root,
- "class": bem(),
- "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("wrapper"),
- "onTransitionend": stopMomentum
- }, [renderOptions()])]);
- }
- });
- export {
- PICKER_KEY,
- stdin_default as default
- };
|