import { ref, watch, nextTick, PropType, onUpdated, onMounted, defineComponent, } from 'vue'; // Utils import { isHidden, createNamespace } from '../utils'; // Composables import { useRect, useScrollParent, useEventListener } from '@vant/use'; import { useExpose } from '../composables/use-expose'; // Components import { Loading } from '../loading'; const [name, bem, t] = createNamespace('list'); export default defineComponent({ name, props: { error: Boolean, loading: Boolean, finished: Boolean, errorText: String, loadingText: String, finishedText: String, offset: { type: [Number, String], default: 300, }, direction: { type: String as PropType<'up' | 'down'>, default: 'down', }, immediateCheck: { type: Boolean, default: true, }, }, emits: ['load', 'update:error', 'update:loading'], setup(props, { emit, slots }) { // use sync innerLoading state to avoid repeated loading in some edge cases const loading = ref(false); const root = ref(); const placeholder = ref(); const scrollParent = useScrollParent(root); const check = () => { nextTick(() => { if (loading.value || props.finished || props.error) { return; } const { offset, direction } = props; const scrollParentRect = useRect(scrollParent); if (!scrollParentRect.height || isHidden(root)) { return false; } let isReachEdge = false; const placeholderRect = useRect(placeholder); if (direction === 'up') { isReachEdge = scrollParentRect.top - placeholderRect.top <= offset; } else { isReachEdge = placeholderRect.bottom - scrollParentRect.bottom <= offset; } if (isReachEdge) { loading.value = true; emit('update:loading', true); emit('load'); } }); }; const renderFinishedText = () => { if (props.finished) { const text = slots.finished ? slots.finished() : props.finishedText; if (text) { return
{text}
; } } }; const clickErrorText = () => { emit('update:error', false); check(); }; const renderErrorText = () => { if (props.error) { const text = slots.error ? slots.error() : props.errorText; if (text) { return (
{text}
); } } }; const renderLoading = () => { if (loading.value && !props.finished) { return (
{slots.loading ? ( slots.loading() ) : ( {props.loadingText || t('loading')} )}
); } }; watch( [() => props.loading, () => props.finished, () => props.error], check ); onUpdated(() => { loading.value = props.loading!; }); onMounted(() => { if (props.immediateCheck) { check(); } }); useExpose({ check }); useEventListener('scroll', check, { target: scrollParent }); return () => { const Content = slots.default?.(); const Placeholder =
; return (
{props.direction === 'down' ? Content : Placeholder} {renderLoading()} {renderFinishedText()} {renderErrorText()} {props.direction === 'up' ? Content : Placeholder}
); }; }, });