Skip to content

Commit

Permalink
feat(list): support scrollTo API to scroll to a specified element (#4863
Browse files Browse the repository at this point in the history
)

* feat(list): list组件虚拟列表支持滚动到指定元素

提供了滚动到指定元素的示例代码

closed #4750

* feat(list): 简化List组件的handleScrollTo

closed #4863

* chore: update snapshot

* chore: update docs

---------

Co-authored-by: naturalshen <[email protected]>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Uyarn <[email protected]>
  • Loading branch information
4 people authored Dec 25, 2024
1 parent 020f13a commit b27a222
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 45 deletions.
45 changes: 33 additions & 12 deletions src/list/_example-ts/virtual-scroll.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,39 @@
<template>
<t-list style="height: 300px" :scroll="{ type: 'virtual' }"
><t-list-item v-for="(item, index) in listRef" :key="index">
<t-list-item-meta :image="imageUrl" title="列表标题" :description="item.content" /></t-list-item
></t-list>
<t-space direction="vertical">
<t-list
ref="list"
style="height: 300px"
:scroll="{ type: 'virtual', rowHeight: 80, bufferSize: 10, threshold: 10 }"
>
<t-list-item v-for="(item, index) in listData" :key="index">
<t-list-item-meta :image="imageUrl" title="列表标题" :description="item.content" />
</t-list-item>
</t-list>
<t-space>
<t-button @click="handleScroll">滚动到指定节点</t-button>
</t-space>
</t-space>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { ListItemMetaProps } from 'tdesign-vue-next';
const list = [];
import { ref, onMounted } from 'vue';
import { ListItemMetaProps, ListInstanceFunctions } from 'tdesign-vue-next';
const list = ref<ListInstanceFunctions>(); // 用于存储对 t-list 的引用
const listData = ref([]); // 使用 ref 来存储列表数据
const imageUrl: ListItemMetaProps['image'] = 'https://tdesign.gtimg.com/site/avatar.jpg';
for (let i = 0; i < 3000; i++) {
list.push({
content: `列表内容的描述性文字`,
onMounted(() => {
for (let i = 0; i < 3000; i++) {
listData.value.push({ content: `第${i + 1}个列表内容的描述性文字` });
}
});
const handleScroll = () => {
// scroll 属性需要设置 rowHeight 参数
list.value?.scrollTo({
// list 不存在嵌套,key 与 index 相同
index: 30,
behavior: 'smooth',
});
}
const listRef = ref(list);
};
</script>
43 changes: 33 additions & 10 deletions src/list/_example/virtual-scroll.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
<template>
<t-list style="height: 300px" :scroll="{ type: 'virtual' }"
><t-list-item v-for="(item, index) in listRef" :key="index">
<t-list-item-meta :image="imageUrl" title="列表标题" :description="item.content" /></t-list-item
></t-list>
<t-space direction="vertical">
<t-list
ref="list"
style="height: 300px"
:scroll="{ type: 'virtual', rowHeight: 80, bufferSize: 10, threshold: 10 }"
>
<t-list-item v-for="(item, index) in listData" :key="index">
<t-list-item-meta :image="imageUrl" title="列表标题" :description="item.content" />
</t-list-item>
</t-list>
<t-space>
<t-button @click="handleScroll">滚动到指定节点</t-button>
</t-space>
</t-space>
</template>

<script setup>
import { ref } from 'vue';
const list = [];
import { ref, onMounted } from 'vue';
const list = ref(); // 用于存储对 t-list 的引用
const listData = ref([]); // 使用 ref 来存储列表数据
const imageUrl = 'https://tdesign.gtimg.com/site/avatar.jpg';
for (let i = 0; i < 3000; i++) {
list.push({ content: `列表内容的描述性文字` });
}
const listRef = ref(list);
onMounted(() => {
for (let i = 0; i < 3000; i++) {
listData.value.push({ content: `${i + 1}个列表内容的描述性文字` });
}
});
const handleScroll = () => {
// scroll 属性需要设置 rowHeight 参数
list.value?.scrollTo({
// list 不存在嵌套,key 与 index 相同
index: 30,
behavior: 'smooth',
});
};
</script>
19 changes: 18 additions & 1 deletion src/list/hooks/useListVirtualScroll.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Ref, computed } from 'vue';

import log from '../../_common/js/log';
import useVirtualScroll from '../../hooks/useVirtualScrollNew';
import { TdListProps } from '../type';
import { Styles } from '../../common';
import { Styles, type ComponentScrollToElementParams } from '../../common';

export const useListVirtualScroll = (
scroll: TdListProps['scroll'],
Expand Down Expand Up @@ -51,11 +53,26 @@ export const useListVirtualScroll = (
} as Styles),
);

const handleScrollTo = (params: ComponentScrollToElementParams) => {
const { index, key } = params;
const targetIndex = index === 0 ? index : index ?? Number(key);
if (!targetIndex && targetIndex !== 0) {
log.error('List', 'scrollTo: `index` or `key` must exist.');
return;
}
if (targetIndex < 0 || targetIndex >= listItems.value.length) {
log.error('List', `${targetIndex} does not exist in data, check \`index\` or \`key\` please.`);
return;
}
virtualConfig.scrollToElement({ ...params, index: targetIndex - 1 });
};

return {
virtualConfig,
cursorStyle,
listStyle,
isVirtualScroll,
onInnerVirtualScroll,
scrollToElement: handleScrollTo,
};
};
7 changes: 7 additions & 0 deletions src/list/list.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ name | params | description
load-more | `(options: { e: MouseEvent })` | \-
scroll | `(options: { e: Event \| WheelEvent; scrollTop: number; scrollBottom: number })` | \-

### ListInstanceFunctions

name | params | return | description
-- | -- | -- | --
scrollTo | `(scrollToParams: ScrollToElementParams)` | \- | support scrolling to a specific node when virtual scrolling


### ListItem Props

name | type | default | description | required
Expand Down
7 changes: 7 additions & 0 deletions src/list/list.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ onScroll | Function | | TS 类型:`(options: { e: Event \| WheelEvent; scroll
load-more | `(options: { e: MouseEvent })` | 点击加载更多时触发
scroll | `(options: { e: Event \| WheelEvent; scrollTop: number; scrollBottom: number })` | 列表滚动时触发,scrollTop 表示顶部滚动距离,scrollBottom 表示底部滚动距离

### ListInstanceFunctions 组件实例方法

名称 | 参数 | 返回值 | 描述
-- | -- | -- | --
scrollTo | `(scrollToParams: ScrollToElementParams)` | \- | 虚拟滚动场景下,支持指定滚动到具体的节点


### ListItem Props

名称 | 类型 | 默认值 | 说明 | 必传
Expand Down
8 changes: 3 additions & 5 deletions src/list/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,8 @@ export default defineComponent({
const renderTNodeJSX = useTNodeJSX();
const { listItems } = useListItems();

const { virtualConfig, cursorStyle, listStyle, isVirtualScroll, onInnerVirtualScroll } = useListVirtualScroll(
props.scroll,
listRef,
listItems,
);
const { virtualConfig, cursorStyle, listStyle, isVirtualScroll, onInnerVirtualScroll, scrollToElement } =
useListVirtualScroll(props.scroll, listRef, listItems);

/** 列表基础逻辑 start */
const listClass = computed(() => {
Expand Down Expand Up @@ -123,6 +120,7 @@ export default defineComponent({
handleLoadMore,
listRef,
isVirtualScroll,
scrollTo: scrollToElement,
};
},

Expand Down
10 changes: 9 additions & 1 deletion src/list/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */

import { TNode, TScroll } from '../common';
import { TNode, TScroll, ComponentScrollToElementParams } from '../common';

export interface TdListProps {
/**
Expand Down Expand Up @@ -53,6 +53,14 @@ export interface TdListProps {
onScroll?: (options: { e: Event | WheelEvent; scrollTop: number; scrollBottom: number }) => void;
}

/** 组件实例方法 */
export interface ListInstanceFunctions {
/**
* 虚拟滚动场景下,支持指定滚动到具体的节点
*/
scrollTo?: (scrollToParams: ComponentScrollToElementParams) => void;
}

export interface TdListItemProps {
/**
* 操作栏
Expand Down
75 changes: 60 additions & 15 deletions test/unit/snap/__snapshots__/csr.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -87330,31 +87330,76 @@ exports[`csr snapshot test > csr test ./src/list/_example/stripe.vue 1`] = `

exports[`csr snapshot test > csr test ./src/list/_example/virtual-scroll.vue 1`] = `
<div
class="t-list t-size-m"
style="position: relative; height: 300px;"
class="t-space t-space-vertical"
style="gap: 16px;"
>


<!---->

<div
style="position: absolute; width: 1px; height: 1px; transition: transform 0.2s; transform: translate(0, 0px);"
/>
<ul
class="t-list__inner"
style="transform: translate(0, 150000px);"
class="t-space-item"
>


</ul>

<div
class="t-list t-size-m"
style="height: 300px;"
>


<!---->
<ul
class="t-list__inner"
>




</ul>
<!---->

<div
class="t-list__load"
>
<!---->
</div>

</div>
</div>
<!---->


<div
class="t-list__load"
class="t-space-item"
>
<!---->
<div
class="t-space t-space-horizontal"
style="gap: 16px;"
>


<div
class="t-space-item"
>
<button
class="t-button t-button--variant-base t-button--theme-primary"
href=""
tabindex="0"
type="button"
>
<span
class="t-button__text"
>

滚动到指定节点

</span>
</button>
</div>
<!---->


</div>
</div>
<!---->


</div>
`;
Expand Down
2 changes: 1 addition & 1 deletion test/unit/snap/__snapshots__/ssr.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ exports[`ssr snapshot test > ssr test ./src/list/_example/size.vue 1`] = `"<div

exports[`ssr snapshot test > ssr test ./src/list/_example/stripe.vue 1`] = `"<div class="t-list t-size-m t-list--stripe" style=""><!--[--><!--[--><!----><ul class="t-list__inner"><!--[--><!--[--><li class="t-list-item"><div class="t-list-item-main"><!--[-->列表内容的描述性文字<!--]--><!----></div></li><li class="t-list-item"><div class="t-list-item-main"><!--[-->列表内容的描述性文字<!--]--><!----></div></li><li class="t-list-item"><div class="t-list-item-main"><!--[-->列表内容的描述性文字<!--]--><!----></div></li><li class="t-list-item"><div class="t-list-item-main"><!--[-->列表内容的描述性文字<!--]--><!----></div></li><!--]--><!--]--></ul><!----><!--]--><div class="t-list__load"><!----></div><!--]--></div>"`;

exports[`ssr snapshot test > ssr test ./src/list/_example/virtual-scroll.vue 1`] = `"<div class="t-list t-size-m" style="position:relative;height:300px;"><!--[--><!--[--><!----><!--[--><div style="position:absolute;width:1px;height:1px;transition:transform 0.2s;transform:translate(0, 0px);-ms-transform:translate(0, 0px);-moz-transform:translate(0, 0px);-webkit-transform:translate(0, 0px);"></div><ul class="t-list__inner" style="transform:translate(0, 150000px);-ms-transform:translate(0, 150000px);-moz-transform:translate(0, 150000px);-webkit-transform:translate(0, 150000px);"><!--[--><!--]--></ul><!--]--><!----><!--]--><div class="t-list__load"><!----></div><!--]--></div>"`;
exports[`ssr snapshot test > ssr test ./src/list/_example/virtual-scroll.vue 1`] = `"<div class="t-space t-space-vertical" style="gap:16px;"><!--[--><!--[--><div class="t-space-item"><div class="t-list t-size-m" style="height:300px;"><!--[--><!--[--><!----><ul class="t-list__inner"><!--[--><!--[--><!--]--><!--]--></ul><!----><!--]--><div class="t-list__load"><!----></div><!--]--></div></div><!----><!--]--><!--[--><div class="t-space-item"><div class="t-space t-space-horizontal" style="gap:16px;"><!--[--><!--[--><div class="t-space-item"><button class="t-button t-button--variant-base t-button--theme-primary" type="button" href tabindex="0"><span class="t-button__text"><!--[-->滚动到指定节点<!--]--></span></button></div><!----><!--]--><!--]--></div></div><!----><!--]--><!--]--></div>"`;

exports[`ssr snapshot test > ssr test ./src/loading/_example/attach.vue 1`] = `"<div class="t-space t-space-vertical" style="gap:16px;" data-v-5d1ead0d><!--[--><!--[--><div class="t-space-item"><div id="alice" class="loading-attach-demo__title" data-v-5d1ead0d>Hello, I&#39;m Alice. I&#39;m going to be a front-end developer.</div></div><!----><!--]--><!--[--><div class="t-space-item"><!----></div><!----><!--]--><!--[--><div class="t-space-item"><div class="t-switch t-size-m" data-v-5d1ead0d><span class="t-switch__handle"><!----></span><div class="t-switch__content t-size-m">隐藏</div></div></div><!----><!--]--><!--]--></div>"`;

Expand Down

0 comments on commit b27a222

Please sign in to comment.