Skip to content

Commit b8af509

Browse files
authored
Fix calling observe on null values as argument (#85)
* fix: calling observe on null values Refactor is visible utility function * Fix node version in eslint.yml * Refactor(demo): comments loading logic * fix(workflow): Update npm install command in ESLint workflow
1 parent cd5ae8e commit b8af509

File tree

5 files changed

+51
-31
lines changed

5 files changed

+51
-31
lines changed

.github/workflows/eslint.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ jobs:
1919
- name: Setup Node.js
2020
uses: actions/setup-node@v2
2121
with:
22-
node-version: '20'
22+
node-version: "20"
2323

2424
- name: Install Dependencies
25-
run: npm install -g pnpm && pnpm install
25+
run: npm install -g pnpm && pnpm i --frozen-lockfile
2626

2727
- name: Run ESLint
2828
run: pnpm run lint

demo/src/App.vue

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,22 @@ const distanceHandler = async () => {
6464
mountToggler();
6565
refresh();
6666
};
67+
68+
const limit = 200;
6769
const load = async $state => {
6870
console.log("loading more...");
69-
page++;
7071
try {
7172
const response = await fetch(
72-
"https://jsonplaceholder.typicode.com/comments?_limit=5&_page=" + page
73+
`https://jsonplaceholder.typicode.com/comments?_limit=${limit}&_page=${++page}`
7374
);
7475
const json = await response.json();
75-
if (json.length < 5) $state.complete();
76-
else {
76+
if (json.length) {
7777
if (!top.value) comments.value.push(...json);
78-
else comments.value.unshift(...json);
78+
else comments.value.unshift(...json.reverse());
7979
$state.loaded();
8080
}
81+
82+
if (json.length < limit) $state.complete();
8183
} catch (error) {
8284
$state.error();
8385
}

src/components/InfiniteLoading.vue

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
<script lang="ts" setup>
22
import type { Props, Params, State, StateHandler } from "@root/types";
3-
import { onMounted, ref, toRefs, onUnmounted, watch, nextTick } from "vue";
3+
import { onMounted, ref, toRefs, onUnmounted, watch } from "vue";
44
import { startObserver, getParentEl, isVisible, updateScrollPosition } from "@root/utils";
55
// @ts-ignore
66
import Spinner from "./Spinner.vue";
77
88
const emit = defineEmits<{ infinite: [$state: StateHandler] }>();
9+
910
const props = withDefaults(defineProps<Props>(), {
1011
top: false,
1112
firstload: true,
1213
distance: 0,
1314
});
15+
1416
defineSlots<{
1517
spinner(props: {}): any;
1618
complete(props: {}): any;
@@ -19,6 +21,7 @@ defineSlots<{
1921
2022
let observer: IntersectionObserver | null = null;
2123
let prevHeight = 0;
24+
2225
const infiniteLoading = ref(null);
2326
const state = ref<State>("");
2427
const { top, firstload, distance } = props;
@@ -38,46 +41,42 @@ const params: Params = {
3841
},
3942
};
4043
41-
const resetObserver = () => {
42-
observer?.disconnect();
43-
observer = startObserver(params);
44-
};
45-
4644
const stateHandler: StateHandler = {
4745
loading() {
4846
state.value = "loading";
4947
},
5048
async loaded() {
5149
state.value = "loaded";
52-
if (top) updateScrollPosition(params, prevHeight);
50+
await updateScrollPosition(params, prevHeight);
5351
if (isVisible(infiniteLoading.value!, params.parentEl)) params.emit();
5452
},
5553
async complete() {
5654
state.value = "complete";
57-
if (top) updateScrollPosition(params, prevHeight);
55+
await updateScrollPosition(params, prevHeight);
5856
observer?.disconnect();
5957
},
6058
error() {
6159
state.value = "error";
6260
},
6361
};
6462
65-
watch(identifier, () => {
66-
resetObserver();
67-
});
63+
function resetObserver() {
64+
observer?.disconnect();
65+
observer = startObserver(params);
66+
}
67+
68+
watch(identifier, resetObserver);
6869
6970
onMounted(async () => {
70-
params.parentEl = await getParentEl(target!);
71+
params.parentEl = await getParentEl(target);
7172
resetObserver();
7273
});
7374
74-
onUnmounted(() => {
75-
observer?.disconnect();
76-
});
75+
onUnmounted(() => observer?.disconnect());
7776
</script>
7877

7978
<template>
80-
<div ref="infiniteLoading" style="min-height: 1px">
79+
<div ref="infiniteLoading" class="v3-infinite-loading">
8180
<div v-show="state == 'loading'">
8281
<slot name="spinner">
8382
<Spinner />
@@ -96,11 +95,17 @@ onUnmounted(() => {
9695
</template>
9796

9897
<style scoped>
98+
.v3-infinite-loading {
99+
width: 100%;
100+
height: 44px;
101+
}
102+
99103
.state-error {
100104
display: flex;
101105
flex-direction: column;
102106
align-items: center;
103107
}
108+
104109
.retry {
105110
margin-top: 8px;
106111
padding: 2px 6px 4px 6px;
@@ -114,6 +119,7 @@ onUnmounted(() => {
114119
outline: none;
115120
cursor: pointer;
116121
}
122+
117123
.retry:hover {
118124
opacity: 0.8;
119125
}

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Ref } from "vue";
22

3-
export type Target = HTMLElement | string | null | undefined;
3+
export type Target = HTMLElement | string;
44

55
export type State = "" | "loading" | "loaded" | "complete" | "error";
66

src/utils.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,25 @@ import { nextTick } from "vue";
22
import type { Ref } from "vue";
33
import type { Params, Target } from "./types";
44

5-
function isVisible(el: Element, view: Element | null): boolean {
5+
function isVisible(el: Element, view: Element | null = null): boolean {
6+
if (!el) return false;
7+
68
const elRect = el.getBoundingClientRect();
7-
if (!view) return elRect.top >= 0 && elRect.bottom <= window.innerHeight;
8-
const viewRect = view.getBoundingClientRect();
9-
return elRect.top >= viewRect.top && elRect.bottom <= viewRect.bottom;
9+
const viewRect = view
10+
? view.getBoundingClientRect()
11+
: { top: 0, left: 0, bottom: window.innerHeight, right: window.innerWidth };
12+
13+
return (
14+
elRect.bottom >= viewRect.top &&
15+
elRect.top <= viewRect.bottom &&
16+
elRect.right >= viewRect.left &&
17+
elRect.left <= viewRect.right
18+
);
1019
}
1120

12-
async function getParentEl(target: Ref<Target>): Promise<Element | null> {
21+
async function getParentEl(target?: Ref<Target | undefined>): Promise<Element | null> {
22+
if (!target) return null;
23+
1324
await nextTick();
1425
if (target.value instanceof HTMLElement) return target.value;
1526
return target.value ? document.querySelector(target.value) : null;
@@ -28,13 +39,14 @@ function startObserver(params: Params) {
2839
},
2940
{ root: params.parentEl, rootMargin }
3041
);
31-
observer.observe(params.infiniteLoading.value!);
42+
if (params.infiniteLoading.value) observer.observe(params.infiniteLoading.value);
3243
return observer;
3344
}
3445

3546
async function updateScrollPosition(params: Params, prevHeight: number) {
36-
const parentEl = params.parentEl || document.documentElement;
3747
await nextTick();
48+
if (!params.top) return;
49+
const parentEl = params.parentEl || document.documentElement;
3850
parentEl.scrollTop = parentEl.scrollHeight - prevHeight;
3951
}
4052

0 commit comments

Comments
 (0)