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 };