376 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			376 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import { ref, watch, reactive, computed, onMounted, onActivated, onDeactivated, onBeforeUnmount, defineComponent, nextTick, createVNode as _createVNode } from "vue";
 | 
						|
import { clamp, isHidden, truthProp, numericProp, windowWidth, windowHeight, preventDefault, createNamespace, makeNumericProp } from "../utils/index.mjs";
 | 
						|
import { doubleRaf, useChildren, useEventListener, usePageVisibility } from "@vant/use";
 | 
						|
import { useTouch } from "../composables/use-touch.mjs";
 | 
						|
import { useExpose } from "../composables/use-expose.mjs";
 | 
						|
import { onPopupReopen } from "../composables/on-popup-reopen.mjs";
 | 
						|
const [name, bem] = createNamespace("swipe");
 | 
						|
const swipeProps = {
 | 
						|
  loop: truthProp,
 | 
						|
  width: numericProp,
 | 
						|
  height: numericProp,
 | 
						|
  vertical: Boolean,
 | 
						|
  autoplay: makeNumericProp(0),
 | 
						|
  duration: makeNumericProp(500),
 | 
						|
  touchable: truthProp,
 | 
						|
  lazyRender: Boolean,
 | 
						|
  initialSwipe: makeNumericProp(0),
 | 
						|
  indicatorColor: String,
 | 
						|
  showIndicators: truthProp,
 | 
						|
  stopPropagation: truthProp
 | 
						|
};
 | 
						|
const SWIPE_KEY = Symbol(name);
 | 
						|
var stdin_default = defineComponent({
 | 
						|
  name,
 | 
						|
  props: swipeProps,
 | 
						|
  emits: ["change", "dragStart", "dragEnd"],
 | 
						|
  setup(props, {
 | 
						|
    emit,
 | 
						|
    slots
 | 
						|
  }) {
 | 
						|
    const root = ref();
 | 
						|
    const track = ref();
 | 
						|
    const state = reactive({
 | 
						|
      rect: null,
 | 
						|
      width: 0,
 | 
						|
      height: 0,
 | 
						|
      offset: 0,
 | 
						|
      active: 0,
 | 
						|
      swiping: false
 | 
						|
    });
 | 
						|
    let dragging = false;
 | 
						|
    const touch = useTouch();
 | 
						|
    const {
 | 
						|
      children,
 | 
						|
      linkChildren
 | 
						|
    } = useChildren(SWIPE_KEY);
 | 
						|
    const count = computed(() => children.length);
 | 
						|
    const size = computed(() => state[props.vertical ? "height" : "width"]);
 | 
						|
    const delta = computed(() => props.vertical ? touch.deltaY.value : touch.deltaX.value);
 | 
						|
    const minOffset = computed(() => {
 | 
						|
      if (state.rect) {
 | 
						|
        const base = props.vertical ? state.rect.height : state.rect.width;
 | 
						|
        return base - size.value * count.value;
 | 
						|
      }
 | 
						|
      return 0;
 | 
						|
    });
 | 
						|
    const maxCount = computed(() => size.value ? Math.ceil(Math.abs(minOffset.value) / size.value) : count.value);
 | 
						|
    const trackSize = computed(() => count.value * size.value);
 | 
						|
    const activeIndicator = computed(() => (state.active + count.value) % count.value);
 | 
						|
    const isCorrectDirection = computed(() => {
 | 
						|
      const expect = props.vertical ? "vertical" : "horizontal";
 | 
						|
      return touch.direction.value === expect;
 | 
						|
    });
 | 
						|
    const trackStyle = computed(() => {
 | 
						|
      const style = {
 | 
						|
        transitionDuration: `${state.swiping ? 0 : props.duration}ms`,
 | 
						|
        transform: `translate${props.vertical ? "Y" : "X"}(${+state.offset.toFixed(2)}px)`
 | 
						|
      };
 | 
						|
      if (size.value) {
 | 
						|
        const mainAxis = props.vertical ? "height" : "width";
 | 
						|
        const crossAxis = props.vertical ? "width" : "height";
 | 
						|
        style[mainAxis] = `${trackSize.value}px`;
 | 
						|
        style[crossAxis] = props[crossAxis] ? `${props[crossAxis]}px` : "";
 | 
						|
      }
 | 
						|
      return style;
 | 
						|
    });
 | 
						|
    const getTargetActive = (pace) => {
 | 
						|
      const {
 | 
						|
        active
 | 
						|
      } = state;
 | 
						|
      if (pace) {
 | 
						|
        if (props.loop) {
 | 
						|
          return clamp(active + pace, -1, count.value);
 | 
						|
        }
 | 
						|
        return clamp(active + pace, 0, maxCount.value);
 | 
						|
      }
 | 
						|
      return active;
 | 
						|
    };
 | 
						|
    const getTargetOffset = (targetActive, offset = 0) => {
 | 
						|
      let currentPosition = targetActive * size.value;
 | 
						|
      if (!props.loop) {
 | 
						|
        currentPosition = Math.min(currentPosition, -minOffset.value);
 | 
						|
      }
 | 
						|
      let targetOffset = offset - currentPosition;
 | 
						|
      if (!props.loop) {
 | 
						|
        targetOffset = clamp(targetOffset, minOffset.value, 0);
 | 
						|
      }
 | 
						|
      return targetOffset;
 | 
						|
    };
 | 
						|
    const move = ({
 | 
						|
      pace = 0,
 | 
						|
      offset = 0,
 | 
						|
      emitChange
 | 
						|
    }) => {
 | 
						|
      if (count.value <= 1) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      const {
 | 
						|
        active
 | 
						|
      } = state;
 | 
						|
      const targetActive = getTargetActive(pace);
 | 
						|
      const targetOffset = getTargetOffset(targetActive, offset);
 | 
						|
      if (props.loop) {
 | 
						|
        if (children[0] && targetOffset !== minOffset.value) {
 | 
						|
          const outRightBound = targetOffset < minOffset.value;
 | 
						|
          children[0].setOffset(outRightBound ? trackSize.value : 0);
 | 
						|
        }
 | 
						|
        if (children[count.value - 1] && targetOffset !== 0) {
 | 
						|
          const outLeftBound = targetOffset > 0;
 | 
						|
          children[count.value - 1].setOffset(outLeftBound ? -trackSize.value : 0);
 | 
						|
        }
 | 
						|
      }
 | 
						|
      state.active = targetActive;
 | 
						|
      state.offset = targetOffset;
 | 
						|
      if (emitChange && targetActive !== active) {
 | 
						|
        emit("change", activeIndicator.value);
 | 
						|
      }
 | 
						|
    };
 | 
						|
    const correctPosition = () => {
 | 
						|
      state.swiping = true;
 | 
						|
      if (state.active <= -1) {
 | 
						|
        move({
 | 
						|
          pace: count.value
 | 
						|
        });
 | 
						|
      } else if (state.active >= count.value) {
 | 
						|
        move({
 | 
						|
          pace: -count.value
 | 
						|
        });
 | 
						|
      }
 | 
						|
    };
 | 
						|
    const prev = () => {
 | 
						|
      correctPosition();
 | 
						|
      touch.reset();
 | 
						|
      doubleRaf(() => {
 | 
						|
        state.swiping = false;
 | 
						|
        move({
 | 
						|
          pace: -1,
 | 
						|
          emitChange: true
 | 
						|
        });
 | 
						|
      });
 | 
						|
    };
 | 
						|
    const next = () => {
 | 
						|
      correctPosition();
 | 
						|
      touch.reset();
 | 
						|
      doubleRaf(() => {
 | 
						|
        state.swiping = false;
 | 
						|
        move({
 | 
						|
          pace: 1,
 | 
						|
          emitChange: true
 | 
						|
        });
 | 
						|
      });
 | 
						|
    };
 | 
						|
    let autoplayTimer;
 | 
						|
    const stopAutoplay = () => clearTimeout(autoplayTimer);
 | 
						|
    const autoplay = () => {
 | 
						|
      stopAutoplay();
 | 
						|
      if (+props.autoplay > 0 && count.value > 1) {
 | 
						|
        autoplayTimer = setTimeout(() => {
 | 
						|
          next();
 | 
						|
          autoplay();
 | 
						|
        }, +props.autoplay);
 | 
						|
      }
 | 
						|
    };
 | 
						|
    const initialize = (active = +props.initialSwipe) => {
 | 
						|
      if (!root.value) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      const cb = () => {
 | 
						|
        var _a, _b;
 | 
						|
        if (!isHidden(root)) {
 | 
						|
          const rect = {
 | 
						|
            width: root.value.offsetWidth,
 | 
						|
            height: root.value.offsetHeight
 | 
						|
          };
 | 
						|
          state.rect = rect;
 | 
						|
          state.width = +((_a = props.width) != null ? _a : rect.width);
 | 
						|
          state.height = +((_b = props.height) != null ? _b : rect.height);
 | 
						|
        }
 | 
						|
        if (count.value) {
 | 
						|
          active = Math.min(count.value - 1, active);
 | 
						|
          if (active === -1) {
 | 
						|
            active = count.value - 1;
 | 
						|
          }
 | 
						|
        }
 | 
						|
        state.active = active;
 | 
						|
        state.swiping = true;
 | 
						|
        state.offset = getTargetOffset(active);
 | 
						|
        children.forEach((swipe) => {
 | 
						|
          swipe.setOffset(0);
 | 
						|
        });
 | 
						|
        autoplay();
 | 
						|
      };
 | 
						|
      if (isHidden(root)) {
 | 
						|
        nextTick().then(cb);
 | 
						|
      } else {
 | 
						|
        cb();
 | 
						|
      }
 | 
						|
    };
 | 
						|
    const resize = () => initialize(state.active);
 | 
						|
    let touchStartTime;
 | 
						|
    const onTouchStart = (event) => {
 | 
						|
      if (!props.touchable || // avoid resetting position on multi-finger touch
 | 
						|
      event.touches.length > 1) return;
 | 
						|
      touch.start(event);
 | 
						|
      dragging = false;
 | 
						|
      touchStartTime = Date.now();
 | 
						|
      stopAutoplay();
 | 
						|
      correctPosition();
 | 
						|
    };
 | 
						|
    const onTouchMove = (event) => {
 | 
						|
      if (props.touchable && state.swiping) {
 | 
						|
        touch.move(event);
 | 
						|
        if (isCorrectDirection.value) {
 | 
						|
          const isEdgeTouch = !props.loop && (state.active === 0 && delta.value > 0 || state.active === count.value - 1 && delta.value < 0);
 | 
						|
          if (!isEdgeTouch) {
 | 
						|
            preventDefault(event, props.stopPropagation);
 | 
						|
            move({
 | 
						|
              offset: delta.value
 | 
						|
            });
 | 
						|
            if (!dragging) {
 | 
						|
              emit("dragStart", {
 | 
						|
                index: activeIndicator.value
 | 
						|
              });
 | 
						|
              dragging = true;
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    };
 | 
						|
    const onTouchEnd = () => {
 | 
						|
      if (!props.touchable || !state.swiping) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      const duration = Date.now() - touchStartTime;
 | 
						|
      const speed = delta.value / duration;
 | 
						|
      const shouldSwipe = Math.abs(speed) > 0.25 || Math.abs(delta.value) > size.value / 2;
 | 
						|
      if (shouldSwipe && isCorrectDirection.value) {
 | 
						|
        const offset = props.vertical ? touch.offsetY.value : touch.offsetX.value;
 | 
						|
        let pace = 0;
 | 
						|
        if (props.loop) {
 | 
						|
          pace = offset > 0 ? delta.value > 0 ? -1 : 1 : 0;
 | 
						|
        } else {
 | 
						|
          pace = -Math[delta.value > 0 ? "ceil" : "floor"](delta.value / size.value);
 | 
						|
        }
 | 
						|
        move({
 | 
						|
          pace,
 | 
						|
          emitChange: true
 | 
						|
        });
 | 
						|
      } else if (delta.value) {
 | 
						|
        move({
 | 
						|
          pace: 0
 | 
						|
        });
 | 
						|
      }
 | 
						|
      dragging = false;
 | 
						|
      state.swiping = false;
 | 
						|
      emit("dragEnd", {
 | 
						|
        index: activeIndicator.value
 | 
						|
      });
 | 
						|
      autoplay();
 | 
						|
    };
 | 
						|
    const swipeTo = (index, options = {}) => {
 | 
						|
      correctPosition();
 | 
						|
      touch.reset();
 | 
						|
      doubleRaf(() => {
 | 
						|
        let targetIndex;
 | 
						|
        if (props.loop && index === count.value) {
 | 
						|
          targetIndex = state.active === 0 ? 0 : index;
 | 
						|
        } else {
 | 
						|
          targetIndex = index % count.value;
 | 
						|
        }
 | 
						|
        if (options.immediate) {
 | 
						|
          doubleRaf(() => {
 | 
						|
            state.swiping = false;
 | 
						|
          });
 | 
						|
        } else {
 | 
						|
          state.swiping = false;
 | 
						|
        }
 | 
						|
        move({
 | 
						|
          pace: targetIndex - state.active,
 | 
						|
          emitChange: true
 | 
						|
        });
 | 
						|
      });
 | 
						|
    };
 | 
						|
    const renderDot = (_, index) => {
 | 
						|
      const active = index === activeIndicator.value;
 | 
						|
      const style = active ? {
 | 
						|
        backgroundColor: props.indicatorColor
 | 
						|
      } : void 0;
 | 
						|
      return _createVNode("i", {
 | 
						|
        "style": style,
 | 
						|
        "class": bem("indicator", {
 | 
						|
          active
 | 
						|
        })
 | 
						|
      }, null);
 | 
						|
    };
 | 
						|
    const renderIndicator = () => {
 | 
						|
      if (slots.indicator) {
 | 
						|
        return slots.indicator({
 | 
						|
          active: activeIndicator.value,
 | 
						|
          total: count.value
 | 
						|
        });
 | 
						|
      }
 | 
						|
      if (props.showIndicators && count.value > 1) {
 | 
						|
        return _createVNode("div", {
 | 
						|
          "class": bem("indicators", {
 | 
						|
            vertical: props.vertical
 | 
						|
          })
 | 
						|
        }, [Array(count.value).fill("").map(renderDot)]);
 | 
						|
      }
 | 
						|
    };
 | 
						|
    useExpose({
 | 
						|
      prev,
 | 
						|
      next,
 | 
						|
      state,
 | 
						|
      resize,
 | 
						|
      swipeTo
 | 
						|
    });
 | 
						|
    linkChildren({
 | 
						|
      size,
 | 
						|
      props,
 | 
						|
      count,
 | 
						|
      activeIndicator
 | 
						|
    });
 | 
						|
    watch(() => props.initialSwipe, (value) => initialize(+value));
 | 
						|
    watch(count, () => initialize(state.active));
 | 
						|
    watch(() => props.autoplay, autoplay);
 | 
						|
    watch([windowWidth, windowHeight, () => props.width, () => props.height], resize);
 | 
						|
    watch(usePageVisibility(), (visible) => {
 | 
						|
      if (visible === "visible") {
 | 
						|
        autoplay();
 | 
						|
      } else {
 | 
						|
        stopAutoplay();
 | 
						|
      }
 | 
						|
    });
 | 
						|
    onMounted(initialize);
 | 
						|
    onActivated(() => initialize(state.active));
 | 
						|
    onPopupReopen(() => initialize(state.active));
 | 
						|
    onDeactivated(stopAutoplay);
 | 
						|
    onBeforeUnmount(stopAutoplay);
 | 
						|
    useEventListener("touchmove", onTouchMove, {
 | 
						|
      target: track
 | 
						|
    });
 | 
						|
    return () => {
 | 
						|
      var _a;
 | 
						|
      return _createVNode("div", {
 | 
						|
        "ref": root,
 | 
						|
        "class": bem()
 | 
						|
      }, [_createVNode("div", {
 | 
						|
        "ref": track,
 | 
						|
        "style": trackStyle.value,
 | 
						|
        "class": bem("track", {
 | 
						|
          vertical: props.vertical
 | 
						|
        }),
 | 
						|
        "onTouchstartPassive": onTouchStart,
 | 
						|
        "onTouchend": onTouchEnd,
 | 
						|
        "onTouchcancel": onTouchEnd
 | 
						|
      }, [(_a = slots.default) == null ? void 0 : _a.call(slots)]), renderIndicator()]);
 | 
						|
    };
 | 
						|
  }
 | 
						|
});
 | 
						|
export {
 | 
						|
  SWIPE_KEY,
 | 
						|
  stdin_default as default,
 | 
						|
  swipeProps
 | 
						|
};
 |