Skip to content

Commit

Permalink
Fix calling observe on null values as argument (#85)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
oumoussa98 authored Aug 26, 2024
1 parent cd5ae8e commit b8af509
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 31 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/eslint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '20'
node-version: "20"

- name: Install Dependencies
run: npm install -g pnpm && pnpm install
run: npm install -g pnpm && pnpm i --frozen-lockfile

- name: Run ESLint
run: pnpm run lint
Expand Down
12 changes: 7 additions & 5 deletions demo/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,22 @@ const distanceHandler = async () => {
mountToggler();
refresh();
};
const limit = 200;
const load = async $state => {
console.log("loading more...");
page++;
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/comments?_limit=5&_page=" + page
`https://jsonplaceholder.typicode.com/comments?_limit=${limit}&_page=${++page}`
);
const json = await response.json();
if (json.length < 5) $state.complete();
else {
if (json.length) {
if (!top.value) comments.value.push(...json);
else comments.value.unshift(...json);
else comments.value.unshift(...json.reverse());
$state.loaded();
}
if (json.length < limit) $state.complete();
} catch (error) {
$state.error();
}
Expand Down
38 changes: 22 additions & 16 deletions src/components/InfiniteLoading.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
<script lang="ts" setup>
import type { Props, Params, State, StateHandler } from "@root/types";
import { onMounted, ref, toRefs, onUnmounted, watch, nextTick } from "vue";
import { onMounted, ref, toRefs, onUnmounted, watch } from "vue";
import { startObserver, getParentEl, isVisible, updateScrollPosition } from "@root/utils";
// @ts-ignore
import Spinner from "./Spinner.vue";
const emit = defineEmits<{ infinite: [$state: StateHandler] }>();
const props = withDefaults(defineProps<Props>(), {
top: false,
firstload: true,
distance: 0,
});
defineSlots<{
spinner(props: {}): any;
complete(props: {}): any;
Expand All @@ -19,6 +21,7 @@ defineSlots<{
let observer: IntersectionObserver | null = null;
let prevHeight = 0;
const infiniteLoading = ref(null);
const state = ref<State>("");
const { top, firstload, distance } = props;
Expand All @@ -38,46 +41,42 @@ const params: Params = {
},
};
const resetObserver = () => {
observer?.disconnect();
observer = startObserver(params);
};
const stateHandler: StateHandler = {
loading() {
state.value = "loading";
},
async loaded() {
state.value = "loaded";
if (top) updateScrollPosition(params, prevHeight);
await updateScrollPosition(params, prevHeight);
if (isVisible(infiniteLoading.value!, params.parentEl)) params.emit();
},
async complete() {
state.value = "complete";
if (top) updateScrollPosition(params, prevHeight);
await updateScrollPosition(params, prevHeight);
observer?.disconnect();
},
error() {
state.value = "error";
},
};
watch(identifier, () => {
resetObserver();
});
function resetObserver() {
observer?.disconnect();
observer = startObserver(params);
}
watch(identifier, resetObserver);
onMounted(async () => {
params.parentEl = await getParentEl(target!);
params.parentEl = await getParentEl(target);
resetObserver();
});
onUnmounted(() => {
observer?.disconnect();
});
onUnmounted(() => observer?.disconnect());
</script>

<template>
<div ref="infiniteLoading" style="min-height: 1px">
<div ref="infiniteLoading" class="v3-infinite-loading">
<div v-show="state == 'loading'">
<slot name="spinner">
<Spinner />
Expand All @@ -96,11 +95,17 @@ onUnmounted(() => {
</template>

<style scoped>
.v3-infinite-loading {
width: 100%;
height: 44px;
}
.state-error {
display: flex;
flex-direction: column;
align-items: center;
}
.retry {
margin-top: 8px;
padding: 2px 6px 4px 6px;
Expand All @@ -114,6 +119,7 @@ onUnmounted(() => {
outline: none;
cursor: pointer;
}
.retry:hover {
opacity: 0.8;
}
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Ref } from "vue";

export type Target = HTMLElement | string | null | undefined;
export type Target = HTMLElement | string;

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

Expand Down
26 changes: 19 additions & 7 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,25 @@ import { nextTick } from "vue";
import type { Ref } from "vue";
import type { Params, Target } from "./types";

function isVisible(el: Element, view: Element | null): boolean {
function isVisible(el: Element, view: Element | null = null): boolean {
if (!el) return false;

const elRect = el.getBoundingClientRect();
if (!view) return elRect.top >= 0 && elRect.bottom <= window.innerHeight;
const viewRect = view.getBoundingClientRect();
return elRect.top >= viewRect.top && elRect.bottom <= viewRect.bottom;
const viewRect = view
? view.getBoundingClientRect()
: { top: 0, left: 0, bottom: window.innerHeight, right: window.innerWidth };

return (
elRect.bottom >= viewRect.top &&
elRect.top <= viewRect.bottom &&
elRect.right >= viewRect.left &&
elRect.left <= viewRect.right
);
}

async function getParentEl(target: Ref<Target>): Promise<Element | null> {
async function getParentEl(target?: Ref<Target | undefined>): Promise<Element | null> {
if (!target) return null;

await nextTick();
if (target.value instanceof HTMLElement) return target.value;
return target.value ? document.querySelector(target.value) : null;
Expand All @@ -28,13 +39,14 @@ function startObserver(params: Params) {
},
{ root: params.parentEl, rootMargin }
);
observer.observe(params.infiniteLoading.value!);
if (params.infiniteLoading.value) observer.observe(params.infiniteLoading.value);
return observer;
}

async function updateScrollPosition(params: Params, prevHeight: number) {
const parentEl = params.parentEl || document.documentElement;
await nextTick();
if (!params.top) return;
const parentEl = params.parentEl || document.documentElement;
parentEl.scrollTop = parentEl.scrollHeight - prevHeight;
}

Expand Down

0 comments on commit b8af509

Please sign in to comment.