import { ref, computed, onMounted, onUnmounted } from 'vue'; export interface PullToRefreshOptions { onRefresh: () => Promise; threshold?: number; maxPull?: number; } export function usePullToRefresh(options: PullToRefreshOptions) { const { onRefresh, threshold = 48, maxPull = 128 } = options; const pullDistance = ref(0); const spinning = ref(false); const refreshing = ref(false); const progress = computed(() => Math.min(pullDistance.value / maxPull, 1)); let scrollEl: HTMLElement | null = null; let startY = 0; let pulling = false; function findScrollEl(): HTMLElement | null { return document.querySelector('.layout > .main') as HTMLElement | null; } function isInsideScrollableChild(target: EventTarget | null): boolean { let el = target as HTMLElement | null; while (el && el !== scrollEl) { const style = getComputedStyle(el); const overflowY = style.overflowY; if ((overflowY === 'auto' || overflowY === 'scroll') && el.scrollTop > 2) { return true; } el = el.parentElement; } return false; } function handleTouchStart(e: TouchEvent) { scrollEl = findScrollEl(); if (!scrollEl || refreshing.value) return; if (scrollEl.scrollTop > 4) return; if (isInsideScrollableChild(e.target)) return; startY = e.touches[0].clientY; pulling = true; } function handleTouchMove(e: TouchEvent) { if (!pulling || refreshing.value) return; const delta = e.touches[0].clientY - startY; if (delta <= 0) { pullDistance.value = 0; spinning.value = false; return; } const damped = Math.min(delta * 0.7, maxPull); pullDistance.value = damped; spinning.value = damped >= threshold * 0.5; } function handleTouchEnd() { if (!pulling) return; pulling = false; if (pullDistance.value >= threshold && !refreshing.value) { refreshing.value = true; spinning.value = true; pullDistance.value = threshold; setTimeout(() => { void onRefresh().finally(() => { refreshing.value = false; spinning.value = false; pullDistance.value = 0; }); }, 900); } else { pullDistance.value = 0; spinning.value = false; } } let attached = false; function attach() { if (attached) return; const el = findScrollEl(); if (!el) return; el.addEventListener('touchstart', handleTouchStart, { passive: true }); el.addEventListener('touchmove', handleTouchMove, { passive: false }); el.addEventListener('touchend', handleTouchEnd); attached = true; } function detach() { const el = findScrollEl(); if (!el) return; el.removeEventListener('touchstart', handleTouchStart); el.removeEventListener('touchmove', handleTouchMove); el.removeEventListener('touchend', handleTouchEnd); attached = false; } onMounted(attach); onUnmounted(detach); return { pullDistance, spinning, refreshing, progress }; }