summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components
diff options
context:
space:
mode:
authortamaina <tamaina@hotmail.co.jp>2023-01-13 18:25:40 +0900
committerGitHub <noreply@github.com>2023-01-13 18:25:40 +0900
commitd2204fd5c8fb2361e9c29ed31cd0d40eb69d2f4b (patch)
tree3479f9cde6139af5cebf94cf6de005e16c404e21 /packages/frontend/src/components
parentUpdate CHANGELOG.md (diff)
downloadmisskey-d2204fd5c8fb2361e9c29ed31cd0d40eb69d2f4b.tar.gz
misskey-d2204fd5c8fb2361e9c29ed31cd0d40eb69d2f4b.tar.bz2
misskey-d2204fd5c8fb2361e9c29ed31cd0d40eb69d2f4b.zip
refactor: pagination/date-separated-list系処理を良い感じに? (#8209)
* pages/messaging/messaging-room.vue * wip * wip * wip??? * wip? * :v: * messaaging-room.form.vue rewrite to compositon api * refactor * 関心事でないのでとりあえず置いておく * :art: * :art: * i18n.ts * fix scroll container find function * fix * FIX * :v: * Fix scroll bottom detect * wip * aaaaaaaaaaa * rename * fix * fix? * :v: * :v: * clean up * clena up * refactor * scroll event once or not * fix * fix once * add safe-area-inset-bottom to spacer * fix * :v: * :art: * fix * fix * wip * :v: * clean up * fix lint * Update packages/client/src/components/global/sticky-container.vue Co-authored-by: Johann150 <johann.galle@protonmail.com> * Update packages/client/src/components/ui/pagination.vue Co-authored-by: Johann150 <johann.galle@protonmail.com> * Update packages/client/src/pages/messaging/messaging-room.form.vue Co-authored-by: Johann150 <johann.galle@protonmail.com> * clean up: single line comment * https://github.com/misskey-dev/misskey/pull/8209#discussion_r867386077 * fix * asobi → tolerance * pick form * pick message * pick room * fix lint * fix scroll? * fix scroll.ts * fix directives/sticky-container * update global/sticky-container.vue * fix, :art: * revert merge * :v: * fix lint errors * :art: * Update packages/client/src/types/date-separated-list.ts Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> * https://github.com/misskey-dev/misskey/pull/8209#discussion_r917225080 * use ' * Update packages/client/src/scripts/scroll.ts Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> * use Number.EPSILON Co-authored-by: acid-chicken <root@acid-chicken.com> * revert * fix * fix * Use % instead of vh * :art: * :art: * :art: * wip * wip * css modules Co-authored-by: Johann150 <johann.galle@protonmail.com> Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
Diffstat (limited to 'packages/frontend/src/components')
-rw-r--r--packages/frontend/src/components/MkDateSeparatedList.vue188
-rw-r--r--packages/frontend/src/components/MkNotes.vue11
-rw-r--r--packages/frontend/src/components/MkPagination.vue286
3 files changed, 306 insertions, 179 deletions
diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue
index 5d8e14c3c8..cb88444d34 100644
--- a/packages/frontend/src/components/MkDateSeparatedList.vue
+++ b/packages/frontend/src/components/MkDateSeparatedList.vue
@@ -1,13 +1,14 @@
<script lang="ts">
-import { defineComponent, h, PropType, TransitionGroup } from 'vue';
+import { defineComponent, h, PropType, TransitionGroup, useCssModule } from 'vue';
import MkAd from '@/components/global/MkAd.vue';
import { i18n } from '@/i18n';
import { defaultStore } from '@/store';
+import { MisskeyEntity } from '@/types/date-separated-list';
export default defineComponent({
props: {
items: {
- type: Array as PropType<{ id: string; createdAt: string; _shouldInsertAd_: boolean; }[]>,
+ type: Array as PropType<MisskeyEntity[]>,
required: true,
},
direction: {
@@ -33,6 +34,7 @@ export default defineComponent({
},
setup(props, { slots, expose }) {
+ const $style = useCssModule();
function getDateText(time: string) {
const date = new Date(time).getDate();
const month = new Date(time).getMonth() + 1;
@@ -57,21 +59,25 @@ export default defineComponent({
new Date(item.createdAt).getDate() !== new Date(props.items[i + 1].createdAt).getDate()
) {
const separator = h('div', {
- class: 'separator',
+ class: $style['separator'],
key: item.id + ':separator',
}, h('p', {
- class: 'date',
+ class: $style['date'],
}, [
- h('span', [
+ h('span', {
+ class: $style['date-1'],
+ }, [
h('i', {
- class: 'ti ti-chevron-up icon',
+ class: `ti ti-chevron-up ${$style['date-1-icon']}`,
}),
getDateText(item.createdAt),
]),
- h('span', [
+ h('span', {
+ class: $style['date-2'],
+ }, [
getDateText(props.items[i + 1].createdAt),
h('i', {
- class: 'ti ti-chevron-down icon',
+ class: `ti ti-chevron-down ${$style['date-2-icon']}`,
}),
]),
]));
@@ -89,100 +95,138 @@ export default defineComponent({
}
});
+ function onBeforeLeave(el: HTMLElement) {
+ el.style.top = `${el.offsetTop}px`;
+ el.style.left = `${el.offsetLeft}px`;
+ }
+ function onLeaveCanceled(el: HTMLElement) {
+ el.style.top = '';
+ el.style.left = '';
+ }
+
return () => h(
defaultStore.state.animation ? TransitionGroup : 'div',
- defaultStore.state.animation ? {
- class: 'sqadhkmv' + (props.noGap ? ' noGap' : ''),
- name: 'list',
- tag: 'div',
- 'data-direction': props.direction,
- 'data-reversed': props.reversed ? 'true' : 'false',
- } : {
- class: 'sqadhkmv' + (props.noGap ? ' noGap' : ''),
+ {
+ class: {
+ [$style['date-separated-list']]: true,
+ [$style['date-separated-list-nogap']]: props.noGap,
+ [$style['reversed']]: props.reversed,
+ [$style['direction-down']]: props.direction === 'down',
+ [$style['direction-up']]: props.direction === 'up',
+ },
+ ...(defaultStore.state.animation ? {
+ name: 'list',
+ tag: 'div',
+ onBeforeLeave,
+ onLeaveCanceled,
+ } : {}),
},
{ default: renderChildren });
},
});
</script>
-<style lang="scss">
-.sqadhkmv {
+<style lang="scss" module>
+.date-separated-list {
container-type: inline-size;
- > *:empty {
- display: none;
+ &:global {
+ > .list-move {
+ transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1);
}
- > *:not(:last-child) {
- margin-bottom: var(--margin);
+ &.deny-move-transition > .list-move {
+ transition: none !important;
}
- > .list-move {
- transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1);
+ > .list-leave-active,
+ > .list-enter-active {
+ transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1);
}
- > .list-enter-active {
+ > .list-leave-from,
+ > .list-leave-to,
+ > .list-leave-active {
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1);
+ position: absolute !important;
}
- &[data-direction="up"] {
- > .list-enter-from {
- opacity: 0;
- transform: translateY(64px);
- }
+ > *:empty {
+ display: none;
}
- &[data-direction="down"] {
- > .list-enter-from {
- opacity: 0;
- transform: translateY(-64px);
+ > *:not(:last-child) {
+ margin-bottom: var(--margin);
+ }
+ }
+}
+
+.date-separated-list-nogap {
+ > * {
+ margin: 0 !important;
+ border: none;
+ border-radius: 0;
+ box-shadow: none;
+
+ &:not(:last-child) {
+ border-bottom: solid 0.5px var(--divider);
}
}
+}
- > .separator {
- text-align: center;
+.direction-up {
+ &:global {
+ > .list-enter-from,
+ > .list-leave-to {
+ opacity: 0;
+ transform: translateY(64px);
+ }
+ }
+}
+.direction-down {
+ &:global {
+ > .list-enter-from,
+ > .list-leave-to {
+ opacity: 0;
+ transform: translateY(-64px);
+ }
+ }
+}
- > .date {
- display: inline-block;
- position: relative;
- margin: 0;
- padding: 0 16px;
- line-height: 32px;
- text-align: center;
- font-size: 12px;
- color: var(--dateLabelFg);
+.reversed {
+ display: flex;
+ flex-direction: column-reverse;
+}
- > span {
- &:first-child {
- margin-right: 8px;
+.separator {
+ text-align: center;
+}
- > .icon {
- margin-right: 8px;
- }
- }
+.date {
+ display: inline-block;
+ position: relative;
+ margin: 0;
+ padding: 0 16px;
+ line-height: 32px;
+ text-align: center;
+ font-size: 12px;
+ color: var(--dateLabelFg);
+}
- &:last-child {
- margin-left: 8px;
+.date-1 {
+ margin-right: 8px;
+}
- > .icon {
- margin-left: 8px;
- }
- }
- }
- }
- }
+.date-1-icon {
+ margin-right: 8px;
+}
- &.noGap {
- > * {
- margin: 0 !important;
- border: none;
- border-radius: 0;
- box-shadow: none;
+.date-2 {
+ margin-left: 8px;
+}
- &:not(:last-child) {
- border-bottom: solid 0.5px var(--divider);
- }
- }
- }
+.date-2-icon {
+ margin-left: 8px;
}
</style>
+
diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue
index 73659fd501..f9952e4245 100644
--- a/packages/frontend/src/components/MkNotes.vue
+++ b/packages/frontend/src/components/MkNotes.vue
@@ -9,7 +9,16 @@
<template #default="{ items: notes }">
<div :class="[$style.root, { [$style.noGap]: noGap }]">
- <MkDateSeparatedList ref="notes" v-slot="{ item: note }" :items="notes" :direction="pagination.reversed ? 'up' : 'down'" :reversed="pagination.reversed" :no-gap="noGap" :ad="true" :class="$style.notes">
+ <MkDateSeparatedList
+ ref="notes"
+ v-slot="{ item: note }"
+ :items="notes"
+ :direction="pagination.reversed ? 'up' : 'down'"
+ :reversed="pagination.reversed"
+ :no-gap="noGap"
+ :ad="true"
+ :class="$style.notes"
+ >
<XNote :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note"/>
</MkDateSeparatedList>
</div>
diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue
index b92e6d2360..a78e7e064a 100644
--- a/packages/frontend/src/components/MkPagination.vue
+++ b/packages/frontend/src/components/MkPagination.vue
@@ -15,14 +15,14 @@
<div v-else ref="rootEl">
<div v-show="pagination.reversed && more" key="_more_" class="cxiknjgy _margin">
- <MkButton v-if="!moreFetching" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMoreAhead">
+ <MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
{{ i18n.ts.loadMore }}
</MkButton>
<MkLoading v-else class="loading"/>
</div>
- <slot :items="items"></slot>
+ <slot :items="items" :fetching="fetching || moreFetching"></slot>
<div v-show="!pagination.reversed && more" key="_more_" class="cxiknjgy _margin">
- <MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
+ <MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
{{ i18n.ts.loadMore }}
</MkButton>
<MkLoading v-else class="loading"/>
@@ -31,15 +31,18 @@
</Transition>
</template>
-<script lang="ts" setup>
-import { computed, ComputedRef, isRef, markRaw, onActivated, onDeactivated, Ref, ref, shallowRef, watch } from 'vue';
+<script lang="ts">
+import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted, ref, watch } from 'vue';
import * as misskey from 'misskey-js';
import * as os from '@/os';
-import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer } from '@/scripts/scroll';
+import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll';
import MkButton from '@/components/MkButton.vue';
+import { defaultStore } from '@/store';
+import { MisskeyEntity } from '@/types/date-separated-list';
import { i18n } from '@/i18n';
const SECOND_FETCH_LIMIT = 30;
+const TOLERANCE = 16;
export type Paging<E extends keyof misskey.Endpoints = keyof misskey.Endpoints> = {
endpoint: E;
@@ -58,8 +61,11 @@ export type Paging<E extends keyof misskey.Endpoints = keyof misskey.Endpoints>
reversed?: boolean;
offsetMode?: boolean;
-};
+ pageEl?: HTMLElement;
+};
+</script>
+<script lang="ts" setup>
const props = withDefaults(defineProps<{
pagination: Paging;
disableAutoLoad?: boolean;
@@ -72,21 +78,73 @@ const emit = defineEmits<{
(ev: 'queue', count: number): void;
}>();
-type Item = { id: string; [another: string]: unknown; };
+let rootEl = $shallowRef<HTMLElement>();
+
+// 遡り中かどうか
+let backed = $ref(false);
+
+let scrollRemove = $ref<(() => void) | null>(null);
-const rootEl = shallowRef<HTMLElement>();
-const items = ref<Item[]>([]);
-const queue = ref<Item[]>([]);
+const items = ref<MisskeyEntity[]>([]);
+const queue = ref<MisskeyEntity[]>([]);
const offset = ref(0);
const fetching = ref(true);
const moreFetching = ref(false);
const more = ref(false);
-const backed = ref(false); // 遡り中か否か
const isBackTop = ref(false);
const empty = computed(() => items.value.length === 0);
const error = ref(false);
+const {
+ enableInfiniteScroll
+} = defaultStore.reactiveState;
-const init = async (): Promise<void> => {
+const contentEl = $computed(() => props.pagination.pageEl || rootEl);
+const scrollableElement = $computed(() => getScrollContainer(contentEl));
+
+// 先頭が表示されているかどうかを検出
+// https://qiita.com/mkataigi/items/0154aefd2223ce23398e
+let scrollObserver = $ref<IntersectionObserver>();
+
+watch([() => props.pagination.reversed, $$(scrollableElement)], () => {
+ if (scrollObserver) scrollObserver.disconnect();
+
+ scrollObserver = new IntersectionObserver(entries => {
+ backed = entries[0].isIntersecting;
+ }, {
+ root: scrollableElement,
+ rootMargin: props.pagination.reversed ? '-100% 0px 100% 0px' : '100% 0px -100% 0px',
+ threshold: 0.01,
+ });
+}, { immediate: true });
+
+watch($$(rootEl), () => {
+ scrollObserver.disconnect();
+ nextTick(() => {
+ if (rootEl) scrollObserver.observe(rootEl);
+ });
+});
+
+watch([$$(backed), $$(contentEl)], () => {
+ if (!backed) {
+ if (!contentEl) return;
+
+ scrollRemove = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl, executeQueue, TOLERANCE);
+ } else {
+ if (scrollRemove) scrollRemove();
+ scrollRemove = null;
+ }
+});
+
+if (props.pagination.params && isRef(props.pagination.params)) {
+ watch(props.pagination.params, init, { deep: true });
+}
+
+watch(queue, (a, b) => {
+ if (a.length === 0 && b.length === 0) return;
+ emit('queue', queue.value.length);
+}, { deep: true });
+
+async function init(): Promise<void> {
queue.value = [];
fetching.value = true;
const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
@@ -96,18 +154,15 @@ const init = async (): Promise<void> => {
}).then(res => {
for (let i = 0; i < res.length; i++) {
const item = res[i];
- if (props.pagination.reversed) {
- if (i === res.length - 2) item._shouldInsertAd_ = true;
- } else {
- if (i === 3) item._shouldInsertAd_ = true;
- }
+ if (i === 3) item._shouldInsertAd_ = true;
}
if (!props.pagination.noPaging && (res.length > (props.pagination.limit || 10))) {
res.pop();
- items.value = props.pagination.reversed ? [...res].reverse() : res;
+ if (props.pagination.reversed) moreFetching.value = true;
+ items.value = res;
more.value = true;
} else {
- items.value = props.pagination.reversed ? [...res].reverse() : res;
+ items.value = res;
more.value = false;
}
offset.value = res.length;
@@ -117,17 +172,16 @@ const init = async (): Promise<void> => {
error.value = true;
fetching.value = false;
});
-};
+}
-const reload = (): void => {
+const reload = (): Promise<void> => {
items.value = [];
- init();
+ return init();
};
const fetchMore = async (): Promise<void> => {
if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return;
moreFetching.value = true;
- backed.value = true;
const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
await os.api(props.pagination.endpoint, {
...params,
@@ -142,22 +196,52 @@ const fetchMore = async (): Promise<void> => {
}).then(res => {
for (let i = 0; i < res.length; i++) {
const item = res[i];
- if (props.pagination.reversed) {
- if (i === res.length - 9) item._shouldInsertAd_ = true;
- } else {
- if (i === 10) item._shouldInsertAd_ = true;
- }
+ if (i === 10) item._shouldInsertAd_ = true;
}
+
+ const reverseConcat = _res => {
+ const oldHeight = scrollableElement ? scrollableElement.scrollHeight : getBodyScrollHeight();
+ const oldScroll = scrollableElement ? scrollableElement.scrollTop : window.scrollY;
+
+ items.value = items.value.concat(_res);
+
+ return nextTick(() => {
+ if (scrollableElement) {
+ scroll(scrollableElement, { top: oldScroll + (scrollableElement.scrollHeight - oldHeight), behavior: 'instant' });
+ } else {
+ window.scroll({ top: oldScroll + (getBodyScrollHeight() - oldHeight), behavior: 'instant' });
+ }
+
+ return nextTick();
+ });
+ };
+
if (res.length > SECOND_FETCH_LIMIT) {
res.pop();
- items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
- more.value = true;
+
+ if (props.pagination.reversed) {
+ reverseConcat(res).then(() => {
+ more.value = true;
+ moreFetching.value = false;
+ });
+ } else {
+ items.value = items.value.concat(res);
+ more.value = true;
+ moreFetching.value = false;
+ }
} else {
- items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
- more.value = false;
+ if (props.pagination.reversed) {
+ reverseConcat(res).then(() => {
+ more.value = false;
+ moreFetching.value = false;
+ });
+ } else {
+ items.value = items.value.concat(res);
+ more.value = false;
+ moreFetching.value = false;
+ }
}
offset.value += res.length;
- moreFetching.value = false;
}, err => {
moreFetching.value = false;
});
@@ -180,10 +264,10 @@ const fetchMoreAhead = async (): Promise<void> => {
}).then(res => {
if (res.length > SECOND_FETCH_LIMIT) {
res.pop();
- items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
+ items.value = items.value.concat(res);
more.value = true;
} else {
- items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
+ items.value = items.value.concat(res);
more.value = false;
}
offset.value += res.length;
@@ -193,106 +277,96 @@ const fetchMoreAhead = async (): Promise<void> => {
});
};
-const prepend = (item: Item): void => {
- if (props.pagination.reversed) {
- if (rootEl.value) {
- const container = getScrollContainer(rootEl.value);
- if (container == null) {
- // TODO?
- } else {
- const pos = getScrollPosition(rootEl.value);
- const viewHeight = container.clientHeight;
- const height = container.scrollHeight;
- const isBottom = (pos + viewHeight > height - 32);
- if (isBottom) {
- // オーバーフローしたら古いアイテムは捨てる
- if (items.value.length >= props.displayLimit) {
- // このやり方だとVue 3.2以降アニメーションが動かなくなる
- //items.value = items.value.slice(-props.displayLimit);
- while (items.value.length >= props.displayLimit) {
- items.value.shift();
- }
- more.value = true;
- }
- }
- }
- }
- items.value.push(item);
- // TODO
- } else {
- // 初回表示時はunshiftだけでOK
- if (!rootEl.value) {
- items.value.unshift(item);
- return;
- }
+const prepend = (item: MisskeyEntity): void => {
+ // 初回表示時はunshiftだけでOK
+ if (!rootEl) {
+ items.value.unshift(item);
+ return;
+ }
- const isTop = isBackTop.value || (document.body.contains(rootEl.value) && isTopVisible(rootEl.value));
+ const isTop = isBackTop.value || (props.pagination.reversed ? isBottomVisible : isTopVisible)(contentEl, TOLERANCE);
- if (isTop) {
- // Prepend the item
- items.value.unshift(item);
+ if (isTop) unshiftItems([item]);
+ else prependQueue(item);
+};
- // オーバーフローしたら古いアイテムは捨てる
- if (items.value.length >= props.displayLimit) {
- // このやり方だとVue 3.2以降アニメーションが動かなくなる
- //this.items = items.value.slice(0, props.displayLimit);
- while (items.value.length >= props.displayLimit) {
- items.value.pop();
- }
- more.value = true;
- }
- } else {
- queue.value.push(item);
- onScrollTop(rootEl.value, () => {
- for (const item of queue.value) {
- prepend(item);
- }
- queue.value = [];
- });
- }
+function unshiftItems(newItems: MisskeyEntity[]) {
+ const length = newItems.length + items.value.length;
+ items.value = [ ...newItems, ...items.value ].slice(0, props.displayLimit);
+
+ if (length >= props.displayLimit) more.value = true;
+}
+
+function executeQueue() {
+ if (queue.value.length === 0) return;
+ unshiftItems(queue.value);
+ queue.value = [];
+}
+
+function prependQueue(newItem: MisskeyEntity) {
+ queue.value.unshift(newItem);
+ if (queue.value.length >= props.displayLimit) {
+ queue.value.pop();
}
-};
+}
-const append = (item: Item): void => {
+const appendItem = (item: MisskeyEntity): void => {
items.value.push(item);
};
-const removeItem = (finder: (item: Item) => boolean) => {
+const removeItem = (finder: (item: MisskeyEntity) => boolean) => {
const i = items.value.findIndex(finder);
items.value.splice(i, 1);
};
-const updateItem = (id: Item['id'], replacer: (old: Item) => Item): void => {
+const updateItem = (id: MisskeyEntity['id'], replacer: (old: MisskeyEntity) => MisskeyEntity): void => {
const i = items.value.findIndex(item => item.id === id);
items.value[i] = replacer(items.value[i]);
};
-if (props.pagination.params && isRef(props.pagination.params)) {
- watch(props.pagination.params, init, { deep: true });
-}
-
-watch(queue, (a, b) => {
- if (a.length === 0 && b.length === 0) return;
- emit('queue', queue.value.length);
-}, { deep: true });
-
-init();
+const inited = init();
onActivated(() => {
isBackTop.value = false;
});
onDeactivated(() => {
- isBackTop.value = window.scrollY === 0;
+ isBackTop.value = props.pagination.reversed ? window.scrollY >= (rootEl ? rootEl?.scrollHeight - window.innerHeight : 0) : window.scrollY === 0;
+});
+
+function toBottom() {
+ scrollToBottom(contentEl);
+}
+
+onMounted(() => {
+ inited.then(() => {
+ if (props.pagination.reversed) {
+ nextTick(() => {
+ setTimeout(toBottom, 800);
+
+ // scrollToBottomでmoreFetchingボタンが画面外まで出るまで
+ // more = trueを遅らせる
+ setTimeout(() => {
+ moreFetching.value = false;
+ }, 2000);
+ });
+ }
+ });
+});
+
+onBeforeUnmount(() => {
+ scrollObserver.disconnect();
});
defineExpose({
items,
queue,
backed,
+ more,
+ inited,
reload,
prepend,
- append,
+ append: appendItem,
removeItem,
updateItem,
});