mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-29 04:46:36 +08:00
167 lines
3.7 KiB
JavaScript
167 lines
3.7 KiB
JavaScript
import { createNamespace } from '../utils';
|
|
import { isHidden } from '../utils/dom/style';
|
|
import { BindEventMixin } from '../mixins/bind-event';
|
|
import { getScroller } from '../utils/dom/scroll';
|
|
import Loading from '../loading';
|
|
|
|
const [createComponent, bem, t] = createNamespace('list');
|
|
|
|
export default createComponent({
|
|
mixins: [
|
|
BindEventMixin(function(bind) {
|
|
if (!this.scroller) {
|
|
this.scroller = getScroller(this.$el);
|
|
}
|
|
|
|
bind(this.scroller, 'scroll', this.check);
|
|
}),
|
|
],
|
|
|
|
model: {
|
|
prop: 'loading',
|
|
},
|
|
|
|
props: {
|
|
error: Boolean,
|
|
loading: Boolean,
|
|
finished: Boolean,
|
|
errorText: String,
|
|
loadingText: String,
|
|
finishedText: String,
|
|
immediateCheck: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
offset: {
|
|
type: Number,
|
|
default: 300,
|
|
},
|
|
direction: {
|
|
type: String,
|
|
default: 'down',
|
|
},
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
// use sync innerLoading state to avoid repeated loading in some edge cases
|
|
innerLoading: this.loading,
|
|
};
|
|
},
|
|
|
|
updated() {
|
|
this.innerLoading = this.loading;
|
|
},
|
|
|
|
mounted() {
|
|
if (this.immediateCheck) {
|
|
this.check();
|
|
}
|
|
},
|
|
|
|
watch: {
|
|
loading: 'check',
|
|
finished: 'check',
|
|
},
|
|
|
|
methods: {
|
|
// @exposed-api
|
|
check() {
|
|
this.$nextTick(() => {
|
|
if (this.innerLoading || this.finished || this.error) {
|
|
return;
|
|
}
|
|
|
|
const { $el: el, scroller, offset, direction } = this;
|
|
let scrollerRect;
|
|
|
|
if (scroller.getBoundingClientRect) {
|
|
scrollerRect = scroller.getBoundingClientRect();
|
|
} else {
|
|
scrollerRect = {
|
|
top: 0,
|
|
bottom: scroller.innerHeight,
|
|
};
|
|
}
|
|
|
|
const scrollerHeight = scrollerRect.bottom - scrollerRect.top;
|
|
|
|
/* istanbul ignore next */
|
|
if (!scrollerHeight || isHidden(el)) {
|
|
return false;
|
|
}
|
|
|
|
let isReachEdge = false;
|
|
const placeholderRect = this.$refs.placeholder.getBoundingClientRect();
|
|
|
|
if (direction === 'up') {
|
|
isReachEdge = scrollerRect.top - placeholderRect.top <= offset;
|
|
} else {
|
|
isReachEdge = placeholderRect.bottom - scrollerRect.bottom <= offset;
|
|
}
|
|
|
|
if (isReachEdge) {
|
|
this.innerLoading = true;
|
|
this.$emit('input', true);
|
|
this.$emit('load');
|
|
}
|
|
});
|
|
},
|
|
|
|
clickErrorText() {
|
|
this.$emit('update:error', false);
|
|
this.check();
|
|
},
|
|
|
|
genLoading() {
|
|
if (this.innerLoading && !this.finished) {
|
|
return (
|
|
<div class={bem('loading')} key="loading">
|
|
{this.slots('loading') || (
|
|
<Loading size="16">{this.loadingText || t('loading')}</Loading>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
},
|
|
|
|
genFinishedText() {
|
|
if (this.finished) {
|
|
const text = this.slots('finished') || this.finishedText;
|
|
|
|
if (text) {
|
|
return <div class={bem('finished-text')}>{text}</div>;
|
|
}
|
|
}
|
|
},
|
|
|
|
genErrorText() {
|
|
if (this.error) {
|
|
const text = this.slots('error') || this.errorText;
|
|
|
|
if (text) {
|
|
return (
|
|
<div onClick={this.clickErrorText} class={bem('error-text')}>
|
|
{text}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
|
|
render() {
|
|
const Placeholder = <div ref="placeholder" class={bem('placeholder')} />;
|
|
|
|
return (
|
|
<div class={bem()} role="feed" aria-busy={this.innerLoading}>
|
|
{this.direction === 'down' ? this.slots() : Placeholder}
|
|
{this.genLoading()}
|
|
{this.genFinishedText()}
|
|
{this.genErrorText()}
|
|
{this.direction === 'up' ? this.slots() : Placeholder}
|
|
</div>
|
|
);
|
|
},
|
|
});
|