diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 2199852d134..4ad844e2188 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -39,6 +39,7 @@ - `n-data-table` adds `allowExport` prop for column. - `n-date-picker` adds `year-range` prop. - `n-tree-select` adds `header` slot, closes [#5915](https://github.com/tusen-ai/naive-ui/issues/5915). +- `n-infinite` adds `reverse` prop,closes [#6308](https://github.com/tusen-ai/naive-ui/issues/6308) ## 2.39.0 diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index cdbd1701136..50fb58b7759 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -39,6 +39,7 @@ - `n-data-table` 在列的配置中新增 `allowExport` 属性 - `n-date-picker` 新增 `year-range` 属性 - `n-tree-select` 新增 `header` 插槽,关闭 [#5915](https://github.com/tusen-ai/naive-ui/issues/5915) +- `n-infinite` 新增 `reverse` 属性,关闭 [#6308](https://github.com/tusen-ai/naive-ui/issues/6308) ## 2.39.0 diff --git a/src/infinite-scroll/demos/enUS/basic.demo.vue b/src/infinite-scroll/demos/enUS/basic.demo.vue index 8e21de2affa..c43d373de34 100644 --- a/src/infinite-scroll/demos/enUS/basic.demo.vue +++ b/src/infinite-scroll/demos/enUS/basic.demo.vue @@ -2,28 +2,38 @@ # Basic - @@ -36,8 +46,10 @@ export default defineComponent({ margin-bottom: 10px; background-color: rgba(0, 128, 0, 0.16); } - .item:last-child { margin-bottom: 0; } +.text { + text-align: center; +} diff --git a/src/infinite-scroll/demos/enUS/index.demo-entry.md b/src/infinite-scroll/demos/enUS/index.demo-entry.md index 00707618358..a9f04254f7b 100644 --- a/src/infinite-scroll/demos/enUS/index.demo-entry.md +++ b/src/infinite-scroll/demos/enUS/index.demo-entry.md @@ -8,6 +8,7 @@ Available since `2.38.2`. ```demo basic.vue +reverse.vue chat.vue ``` @@ -18,5 +19,12 @@ chat.vue | Name | Type | Default | Description | Version | | --- | --- | --- | --- | --- | | distance | `number` | `0` | Distance threshold that triggers loading. | 2.38.2 | +| reverse | `boolean` | `false` | Whether to trigger load more from the top. Defaults to loading from bottom. | NEXT_VERSION | | scrollbar-props | `Object` | `undefined` | Attribute reference [Scrollbar props](scrollbar#Scrollbar-Props). | 2.38.2 | | on-load | `() => Promise \| void` | `undefined` | The callback function when scrolling to the bottom. | 2.38.2 | + +### Infinite Slots + +| Name | Parameters | Description | +| ------- | ---------- | ------------------ | +| default | `()` | Infinite's content | diff --git a/src/infinite-scroll/demos/enUS/reverse.demo.vue b/src/infinite-scroll/demos/enUS/reverse.demo.vue new file mode 100644 index 00000000000..5c7475e79a1 --- /dev/null +++ b/src/infinite-scroll/demos/enUS/reverse.demo.vue @@ -0,0 +1,61 @@ + +# Reverse + + + + + + + diff --git a/src/infinite-scroll/demos/zhCN/basic.demo.vue b/src/infinite-scroll/demos/zhCN/basic.demo.vue index 3c25f1084bf..e0791588535 100644 --- a/src/infinite-scroll/demos/zhCN/basic.demo.vue +++ b/src/infinite-scroll/demos/zhCN/basic.demo.vue @@ -2,28 +2,38 @@ # 基础 - @@ -36,8 +46,10 @@ export default defineComponent({ margin-bottom: 10px; background-color: rgba(0, 128, 0, 0.16); } - .item:last-child { margin-bottom: 0; } +.text { + text-align: center; +} diff --git a/src/infinite-scroll/demos/zhCN/index.demo-entry.md b/src/infinite-scroll/demos/zhCN/index.demo-entry.md index de2aa1810b1..14bb9c38630 100644 --- a/src/infinite-scroll/demos/zhCN/index.demo-entry.md +++ b/src/infinite-scroll/demos/zhCN/index.demo-entry.md @@ -8,6 +8,7 @@ ```demo basic.vue +reverse.vue chat.vue ``` @@ -18,5 +19,12 @@ chat.vue | 名称 | 类型 | 默认值 | 说明 | 版本 | | --- | --- | --- | --- | --- | | distance | `number` | `0` | 触发加载的距离阈值 | 2.38.2 | +| reverse | `boolean` | `false` | 是否从顶部触发加载。默认从底部触发加载 | NEXT_VERSION | | scrollbar-props | `Object` | `undefined` | 属性参考 [Scrollbar props](scrollbar#Scrollbar-Props) | 2.38.2 | | on-load | `() => Promise \| void` | `undefined` | 滚动到底部时的回调函数 | 2.38.2 | + +### Infinite Slots + +| 名称 | 参数 | 说明 | +| ------- | ---- | --------------- | +| default | `()` | Infinite 的内容 | diff --git a/src/infinite-scroll/demos/zhCN/reverse.demo.vue b/src/infinite-scroll/demos/zhCN/reverse.demo.vue new file mode 100644 index 00000000000..1b62da65dee --- /dev/null +++ b/src/infinite-scroll/demos/zhCN/reverse.demo.vue @@ -0,0 +1,61 @@ + +# 反向 + + + + + + + diff --git a/src/infinite-scroll/src/InfiniteScroll.tsx b/src/infinite-scroll/src/InfiniteScroll.tsx index fbb3d3e111c..824560caee2 100644 --- a/src/infinite-scroll/src/InfiniteScroll.tsx +++ b/src/infinite-scroll/src/InfiniteScroll.tsx @@ -1,6 +1,13 @@ -import { type PropType, defineComponent, h, ref } from 'vue' +import { + type PropType, + defineComponent, + h, + nextTick, + onMounted, + ref +} from 'vue' +import { throttle } from 'lodash' import type { ExtractPublicPropTypes } from '../../_utils' -import { resolveSlot } from '../../_utils' import type { ScrollbarProps } from '../../scrollbar/src/Scrollbar' import { NxScrollbar, type ScrollbarInst } from '../../_internal' @@ -9,6 +16,7 @@ export const infiniteScrollProps = { type: Number, default: 0 }, + reverse: Boolean, onLoad: Function as PropType<() => Promise | void>, scrollbarProps: Object as PropType } as const @@ -24,51 +32,54 @@ export default defineComponent({ const scrollbarInstRef = ref(null) let loading = false - - const handleCheckBottom = async (): Promise => { - const { value: scrollbarInst } = scrollbarInstRef - if (scrollbarInst) { - const { containerRef } = scrollbarInst - const scrollHeight = containerRef?.scrollHeight - const clientHeight = containerRef?.clientHeight - const scrollTop = containerRef?.scrollTop - - if ( - containerRef - && scrollHeight !== undefined - && clientHeight !== undefined - && scrollTop !== undefined - ) { - if (scrollTop + clientHeight >= scrollHeight - props.distance) { - loading = true - try { - await props.onLoad?.() - } - catch {} - loading = false - } - } + async function handleLoad() { + loading = true + try { + await props.onLoad?.() + } + finally { + loading = false } } - - const handleScroll = (): void => { - if (loading) - return - void handleCheckBottom() + const handleScroll = throttle(_handleScroll, 200, { trailing: true }) + function _handleScroll(): void { + triggerLoad() } - - const handleWheel = (e: WheelEvent): void => { - if (e.deltaY <= 0) - return + async function triggerLoad() { if (loading) return - void handleCheckBottom() + const { value: scrollbarInst } = scrollbarInstRef + const containerRef = scrollbarInst?.containerRef + if (!containerRef) + return + const { + scrollHeight: scrollHeightBefore, + clientHeight, + scrollTop + } = containerRef + const { reverse, distance } = props + if (reverse) { + if (scrollTop <= distance) { + await handleLoad() + nextTick(() => { + const scrollHeightAfter = containerRef.scrollHeight + const top = scrollHeightAfter - scrollHeightBefore + scrollbarInstRef.value?.scrollTo({ top }) + }) + } + } + else if (scrollTop + clientHeight + distance >= scrollHeightBefore) { + handleLoad() + } } + onMounted(() => { + triggerLoad() + }) + return { scrollbarInstRef, - handleScroll, - handleWheel + handleScroll } }, render() { @@ -76,14 +87,9 @@ export default defineComponent({ - {{ - default: () => { - return resolveSlot(this.$slots.default, () => []) - } - }} + {this.$slots.default} ) }