|
@@ -0,0 +1,147 @@
|
|
|
+import throttle from 'throttle-debounce/debounce';
|
|
|
+import {
|
|
|
+ isHtmlElement,
|
|
|
+ isFunction,
|
|
|
+ isUndefined,
|
|
|
+ isDefined
|
|
|
+} from 'element-ui/src/utils/types';
|
|
|
+import {
|
|
|
+ getScrollContainer
|
|
|
+} from 'element-ui/src/utils/dom';
|
|
|
+
|
|
|
+const getStyleComputedProperty = (element, property) => {
|
|
|
+ if (element === window) {
|
|
|
+ element = document.documentElement;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (element.nodeType !== 1) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ // NOTE: 1 DOM access here
|
|
|
+ const css = window.getComputedStyle(element, null);
|
|
|
+ return property ? css[property] : css;
|
|
|
+};
|
|
|
+
|
|
|
+const entries = (obj) => {
|
|
|
+ return Object.keys(obj || {})
|
|
|
+ .map(key => ([key, obj[key]]));
|
|
|
+};
|
|
|
+
|
|
|
+const getPositionSize = (el, prop) => {
|
|
|
+ return el === window || el === document
|
|
|
+ ? document.documentElement[prop]
|
|
|
+ : el[prop];
|
|
|
+};
|
|
|
+
|
|
|
+const getOffsetHeight = el => {
|
|
|
+ return getPositionSize(el, 'offsetHeight');
|
|
|
+};
|
|
|
+
|
|
|
+const getClientHeight = el => {
|
|
|
+ return getPositionSize(el, 'clientHeight');
|
|
|
+};
|
|
|
+
|
|
|
+const scope = 'ElInfiniteScroll';
|
|
|
+const attributes = {
|
|
|
+ delay: {
|
|
|
+ type: Number,
|
|
|
+ default: 200
|
|
|
+ },
|
|
|
+ distance: {
|
|
|
+ type: Number,
|
|
|
+ default: 0
|
|
|
+ },
|
|
|
+ disabled: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ immediate: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const getScrollOptions = (el, vm) => {
|
|
|
+ if (!isHtmlElement(el)) return {};
|
|
|
+
|
|
|
+ return entries(attributes).reduce((map, [key, option]) => {
|
|
|
+ const { type, default: defaultValue } = option;
|
|
|
+ let value = el.getAttribute(`infinite-scroll-${key}`);
|
|
|
+ value = isUndefined(vm[value]) ? value : vm[value];
|
|
|
+ switch (type) {
|
|
|
+ case Number:
|
|
|
+ value = Number(value);
|
|
|
+ value = Number.isNaN(value) ? defaultValue : value;
|
|
|
+ break;
|
|
|
+ case Boolean:
|
|
|
+ value = isDefined(value) ? value === 'false' ? false : Boolean(value) : defaultValue;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ value = type(value);
|
|
|
+ }
|
|
|
+ map[key] = value;
|
|
|
+ return map;
|
|
|
+ }, {});
|
|
|
+};
|
|
|
+
|
|
|
+const getElementTop = el => el.getBoundingClientRect().top;
|
|
|
+
|
|
|
+const handleScroll = function(cb) {
|
|
|
+ const { el, vm, container, observer } = this[scope];
|
|
|
+ const { distance, disabled } = getScrollOptions(el, vm);
|
|
|
+
|
|
|
+ if (disabled) return;
|
|
|
+
|
|
|
+ let shouldTrigger = false;
|
|
|
+
|
|
|
+ if (container === el) {
|
|
|
+ // be aware of difference between clientHeight & offsetHeight & window.getComputedStyle().height
|
|
|
+ const scrollBottom = container.scrollTop + getClientHeight(container);
|
|
|
+ shouldTrigger = container.scrollHeight - scrollBottom <= distance;
|
|
|
+ } else {
|
|
|
+ const heightBelowTop = getOffsetHeight(el) + getElementTop(el) - getElementTop(container);
|
|
|
+ const offsetHeight = getOffsetHeight(container);
|
|
|
+ const borderBottom = Number.parseFloat(getStyleComputedProperty(container, 'borderBottomWidth'));
|
|
|
+ shouldTrigger = heightBelowTop - offsetHeight + borderBottom <= distance;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (shouldTrigger && isFunction(cb)) {
|
|
|
+ cb.call(vm);
|
|
|
+ } else if (observer) {
|
|
|
+ observer.disconnect();
|
|
|
+ this[scope].observer = null;
|
|
|
+ }
|
|
|
+
|
|
|
+};
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'InfiniteScroll',
|
|
|
+ inserted(el, binding, vnode) {
|
|
|
+ const cb = binding.value;
|
|
|
+
|
|
|
+ const vm = vnode.context;
|
|
|
+ // only include vertical scroll
|
|
|
+ const container = getScrollContainer(el, true);
|
|
|
+ const { delay, immediate } = getScrollOptions(el, vm);
|
|
|
+ const onScroll = throttle(delay, handleScroll.bind(el, cb));
|
|
|
+
|
|
|
+ el[scope] = { el, vm, container, onScroll };
|
|
|
+
|
|
|
+ if (container) {
|
|
|
+ container.addEventListener('scroll', onScroll);
|
|
|
+
|
|
|
+ if (immediate) {
|
|
|
+ const observer = el[scope].observer = new MutationObserver(onScroll);
|
|
|
+ observer.observe(container, { childList: true, subtree: true });
|
|
|
+ onScroll();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ unbind(el) {
|
|
|
+ const { container, onScroll } = el[scope];
|
|
|
+ if (container) {
|
|
|
+ container.removeEventListener('scroll', onScroll);
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|