@tarojs/components-advanced 4.1.12-beta.2 → 4.1.12-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/components/index.js +1 -0
  2. package/dist/components/index.js.map +1 -1
  3. package/dist/components/list/NoMore.d.ts +30 -0
  4. package/dist/components/list/NoMore.js +10 -0
  5. package/dist/components/list/NoMore.js.map +1 -0
  6. package/dist/components/list/hooks/useItemSizeCache.d.ts +25 -0
  7. package/dist/components/list/hooks/useItemSizeCache.js +84 -0
  8. package/dist/components/list/hooks/useItemSizeCache.js.map +1 -0
  9. package/dist/components/list/hooks/useRefresher.d.ts +72 -0
  10. package/dist/components/list/hooks/useRefresher.js +480 -0
  11. package/dist/components/list/hooks/useRefresher.js.map +1 -0
  12. package/dist/components/list/hooks/useResizeObserver.d.ts +26 -0
  13. package/dist/components/list/hooks/useResizeObserver.js +146 -0
  14. package/dist/components/list/hooks/useResizeObserver.js.map +1 -0
  15. package/dist/components/list/hooks/useScrollCorrection.d.ts +18 -0
  16. package/dist/components/list/hooks/useScrollCorrection.js +72 -0
  17. package/dist/components/list/hooks/useScrollCorrection.js.map +1 -0
  18. package/dist/components/list/index.d.ts +52 -3
  19. package/dist/components/list/index.js +1081 -99
  20. package/dist/components/list/index.js.map +1 -1
  21. package/dist/components/list/utils.d.ts +16 -0
  22. package/dist/components/list/utils.js +19 -0
  23. package/dist/components/list/utils.js.map +1 -0
  24. package/dist/components/virtual-list/vue/list.d.ts +2 -2
  25. package/dist/components/virtual-waterfall/vue/waterfall.d.ts +1 -1
  26. package/dist/index.js +1 -0
  27. package/dist/index.js.map +1 -1
  28. package/package.json +8 -8
@@ -0,0 +1,146 @@
1
+ import Taro from '@tarojs/taro';
2
+ import { useRef, useCallback, useEffect } from 'react';
3
+ import { isH5, isMiniProgram } from '../utils.js';
4
+
5
+ function useResizeObserver(options) {
6
+ const { enabled, isHorizontal, listId, onResize } = options;
7
+ // H5: ResizeObserver 实例
8
+ const observerRef = useRef(null);
9
+ // 小程序: IntersectionObserver 实例 Map
10
+ const intersectionObserversRef = useRef(new Map());
11
+ // 已观察的元素 Map(用于去重)
12
+ const observedElementsRef = useRef(new Map());
13
+ /**
14
+ * H5 实现:使用 ResizeObserver
15
+ */
16
+ const observeH5 = useCallback((element, index) => {
17
+ if (!element || !enabled)
18
+ return;
19
+ // 避免重复观察
20
+ if (observedElementsRef.current.has(index))
21
+ return;
22
+ // 创建 ResizeObserver(懒加载)
23
+ if (!observerRef.current) {
24
+ observerRef.current = new ResizeObserver((entries) => {
25
+ entries.forEach((entry) => {
26
+ const idx = Number(entry.target.getAttribute('data-index'));
27
+ if (isNaN(idx))
28
+ return;
29
+ const size = isHorizontal
30
+ ? entry.contentRect.width
31
+ : entry.contentRect.height;
32
+ onResize(idx, size);
33
+ });
34
+ });
35
+ }
36
+ // 设置 data-index 属性
37
+ element.setAttribute('data-index', String(index));
38
+ // 开始观察
39
+ observerRef.current.observe(element);
40
+ observedElementsRef.current.set(index, element);
41
+ }, [enabled, isHorizontal, onResize]);
42
+ /**
43
+ * 小程序实现:使用 IntersectionObserver + SelectorQuery
44
+ */
45
+ const observeMiniProgram = useCallback((_element, index) => {
46
+ if (!enabled)
47
+ return;
48
+ // 避免重复观察
49
+ if (intersectionObserversRef.current.has(index))
50
+ return;
51
+ try {
52
+ // 创建 IntersectionObserver
53
+ const observer = Taro.createIntersectionObserver(Taro.getCurrentInstance().page, {
54
+ observeAll: true
55
+ });
56
+ // 相对于 List 容器
57
+ observer.relativeTo(`#${listId}`);
58
+ // 观察元素进入可见区域
59
+ // SelectorQuery 是只读查询,不会触发 setData,滚动期间执行安全
60
+ // sizeCacheVersion 的延迟 bump 由 weappSizeCacheVersionBump 在 onResize 链路中负责
61
+ observer.observe(`[data-index="${index}"]`, (res) => {
62
+ if (res.intersectionRatio > 0) {
63
+ Taro.createSelectorQuery()
64
+ .select(`[data-index="${index}"]`)
65
+ .boundingClientRect((rect) => {
66
+ if (rect) {
67
+ const size = isHorizontal ? rect.width : rect.height;
68
+ onResize(index, size);
69
+ }
70
+ })
71
+ .exec();
72
+ }
73
+ });
74
+ intersectionObserversRef.current.set(index, observer);
75
+ }
76
+ catch (_a) {
77
+ // ignore observe failure
78
+ }
79
+ }, [enabled, isHorizontal, listId, onResize]);
80
+ /**
81
+ * 观察元素(平台自动适配)
82
+ */
83
+ const observe = useCallback((element, index) => {
84
+ if (!enabled)
85
+ return;
86
+ if (isH5) {
87
+ observeH5(element, index);
88
+ }
89
+ else if (isMiniProgram) {
90
+ observeMiniProgram(element, index);
91
+ }
92
+ }, [enabled, observeH5, observeMiniProgram]);
93
+ /**
94
+ * 取消观察元素
95
+ */
96
+ const unobserve = useCallback((element) => {
97
+ if (!element)
98
+ return;
99
+ const index = Number(element.getAttribute('data-index'));
100
+ if (isNaN(index))
101
+ return;
102
+ if (isH5 && observerRef.current) {
103
+ observerRef.current.unobserve(element);
104
+ observedElementsRef.current.delete(index);
105
+ }
106
+ else if (isMiniProgram) {
107
+ const observer = intersectionObserversRef.current.get(index);
108
+ if (observer) {
109
+ observer.disconnect();
110
+ intersectionObserversRef.current.delete(index);
111
+ }
112
+ }
113
+ }, []);
114
+ /**
115
+ * 断开所有观察
116
+ */
117
+ const disconnect = useCallback(() => {
118
+ if (isH5 && observerRef.current) {
119
+ observerRef.current.disconnect();
120
+ observerRef.current = null;
121
+ observedElementsRef.current.clear();
122
+ }
123
+ else if (isMiniProgram) {
124
+ intersectionObserversRef.current.forEach((observer) => {
125
+ observer.disconnect();
126
+ });
127
+ intersectionObserversRef.current.clear();
128
+ }
129
+ }, []);
130
+ /**
131
+ * 组件卸载时清理
132
+ */
133
+ useEffect(() => {
134
+ return () => {
135
+ disconnect();
136
+ };
137
+ }, [disconnect]);
138
+ return {
139
+ observe,
140
+ unobserve,
141
+ disconnect
142
+ };
143
+ }
144
+
145
+ export { useResizeObserver };
146
+ //# sourceMappingURL=useResizeObserver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useResizeObserver.js","sources":["../../../../src/components/list/hooks/useResizeObserver.ts"],"sourcesContent":["import Taro from '@tarojs/taro'\nimport { useCallback, useEffect, useRef } from 'react'\n\nimport { isH5, isMiniProgram } from '../utils'\n\n/**\n * useResizeObserver Hook - 尺寸变化监听(平台适配)\n *\n * H5: 使用 ResizeObserver API\n * 小程序: 使用 IntersectionObserver + SelectorQuery\n */\n\ninterface UseResizeObserverOptions {\n /** 是否启用监听 */\n enabled: boolean\n\n /** 是否水平滚动(监听 width 还是 height) */\n isHorizontal: boolean\n\n /** List 容器 ID(小程序用于 SelectorQuery) */\n listId: string\n\n /** 尺寸变化回调 */\n onResize: (index: number, size: number) => void\n}\n\ninterface UseResizeObserverReturn {\n /** 观察指定元素(传入 ref 和索引) */\n observe: (element: HTMLElement | null, index: number) => void\n\n /** 取消观察指定元素 */\n unobserve: (element: HTMLElement | null) => void\n\n /** 断开所有观察 */\n disconnect: () => void\n}\n\nexport function useResizeObserver(\n options: UseResizeObserverOptions\n): UseResizeObserverReturn {\n const { enabled, isHorizontal, listId, onResize } = options\n\n // H5: ResizeObserver 实例\n const observerRef = useRef<ResizeObserver | null>(null)\n\n // 小程序: IntersectionObserver 实例 Map\n const intersectionObserversRef = useRef<Map<number, Taro.IntersectionObserver>>(new Map())\n\n // 已观察的元素 Map(用于去重)\n const observedElementsRef = useRef<Map<number, HTMLElement | null>>(new Map())\n\n /**\n * H5 实现:使用 ResizeObserver\n */\n const observeH5 = useCallback((element: HTMLElement | null, index: number) => {\n if (!element || !enabled) return\n\n // 避免重复观察\n if (observedElementsRef.current.has(index)) return\n\n // 创建 ResizeObserver(懒加载)\n if (!observerRef.current) {\n observerRef.current = new ResizeObserver((entries) => {\n entries.forEach((entry) => {\n const idx = Number(entry.target.getAttribute('data-index'))\n if (isNaN(idx)) return\n\n const size = isHorizontal\n ? entry.contentRect.width\n : entry.contentRect.height\n\n onResize(idx, size)\n })\n })\n }\n\n // 设置 data-index 属性\n element.setAttribute('data-index', String(index))\n\n // 开始观察\n observerRef.current.observe(element)\n observedElementsRef.current.set(index, element)\n }, [enabled, isHorizontal, onResize])\n\n /**\n * 小程序实现:使用 IntersectionObserver + SelectorQuery\n */\n const observeMiniProgram = useCallback((_element: any, index: number) => {\n if (!enabled) return\n\n // 避免重复观察\n if (intersectionObserversRef.current.has(index)) return\n\n try {\n // 创建 IntersectionObserver\n const observer = Taro.createIntersectionObserver(Taro.getCurrentInstance().page as any, {\n observeAll: true\n })\n\n // 相对于 List 容器\n observer.relativeTo(`#${listId}`)\n\n // 观察元素进入可见区域\n // SelectorQuery 是只读查询,不会触发 setData,滚动期间执行安全\n // sizeCacheVersion 的延迟 bump 由 weappSizeCacheVersionBump 在 onResize 链路中负责\n observer.observe(`[data-index=\"${index}\"]`, (res) => {\n if (res.intersectionRatio > 0) {\n Taro.createSelectorQuery()\n .select(`[data-index=\"${index}\"]`)\n .boundingClientRect((rect: any) => {\n if (rect) {\n const size = isHorizontal ? rect.width : rect.height\n onResize(index, size)\n }\n })\n .exec()\n }\n })\n\n intersectionObserversRef.current.set(index, observer)\n } catch {\n // ignore observe failure\n }\n }, [enabled, isHorizontal, listId, onResize])\n\n /**\n * 观察元素(平台自动适配)\n */\n const observe = useCallback((element: HTMLElement | null, index: number) => {\n if (!enabled) return\n\n if (isH5) {\n observeH5(element, index)\n } else if (isMiniProgram) {\n observeMiniProgram(element, index)\n }\n }, [enabled, observeH5, observeMiniProgram])\n\n /**\n * 取消观察元素\n */\n const unobserve = useCallback((element: HTMLElement | null) => {\n if (!element) return\n\n const index = Number(element.getAttribute('data-index'))\n if (isNaN(index)) return\n\n if (isH5 && observerRef.current) {\n observerRef.current.unobserve(element)\n observedElementsRef.current.delete(index)\n } else if (isMiniProgram) {\n const observer = intersectionObserversRef.current.get(index)\n if (observer) {\n observer.disconnect()\n intersectionObserversRef.current.delete(index)\n }\n }\n }, [])\n\n /**\n * 断开所有观察\n */\n const disconnect = useCallback(() => {\n if (isH5 && observerRef.current) {\n observerRef.current.disconnect()\n observerRef.current = null\n observedElementsRef.current.clear()\n } else if (isMiniProgram) {\n intersectionObserversRef.current.forEach((observer) => {\n observer.disconnect()\n })\n intersectionObserversRef.current.clear()\n }\n }, [])\n\n /**\n * 组件卸载时清理\n */\n useEffect(() => {\n return () => {\n disconnect()\n }\n }, [disconnect])\n\n return {\n observe,\n unobserve,\n disconnect\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ interface UseScrollCorrectionOptions {
2
+ /** 是否启用修正 */
3
+ enabled: boolean;
4
+ /** 可见区域起始索引 */
5
+ visibleStartIndex: number;
6
+ /** 设置滚动位置的回调 */
7
+ setScrollOffset: (offset: number | ((prev: number) => number)) => void;
8
+ }
9
+ interface UseScrollCorrectionReturn {
10
+ /** 记录尺寸变化 */
11
+ recordSizeChange: (index: number, oldSize: number, newSize: number) => void;
12
+ /** 标记用户正在滚动 */
13
+ markUserScrolling: () => void;
14
+ /** 清空修正队列 */
15
+ clearQueue: () => void;
16
+ }
17
+ export declare function useScrollCorrection(options: UseScrollCorrectionOptions): UseScrollCorrectionReturn;
18
+ export {};
@@ -0,0 +1,72 @@
1
+ import { useRef, useCallback } from 'react';
2
+
3
+ function useScrollCorrection(options) {
4
+ const { enabled, visibleStartIndex, setScrollOffset } = options;
5
+ // 修正队列
6
+ const queueRef = useRef([]);
7
+ // 修正定时器
8
+ const timerRef = useRef(null);
9
+ // 用户最后滚动时间
10
+ const lastUserScrollTimeRef = useRef(0);
11
+ /**
12
+ * 记录尺寸变化
13
+ */
14
+ const recordSizeChange = useCallback((index, oldSize, newSize) => {
15
+ if (!enabled)
16
+ return;
17
+ const diff = newSize - oldSize;
18
+ // 阈值过滤:小于 1px 的变化忽略
19
+ if (Math.abs(diff) < 1)
20
+ return;
21
+ // 添加到队列
22
+ queueRef.current.push({ index, diff });
23
+ // 清除旧定时器
24
+ if (timerRef.current) {
25
+ clearTimeout(timerRef.current);
26
+ }
27
+ // 延迟执行修正(批量窗口 100ms)
28
+ timerRef.current = setTimeout(() => {
29
+ const now = Date.now();
30
+ // 如果用户最近 300ms 内主动滚动,跳过修正
31
+ if (now - lastUserScrollTimeRef.current < 300) {
32
+ queueRef.current = [];
33
+ return;
34
+ }
35
+ // 批量计算修正量(仅修正可见区域之前的变化)
36
+ const totalCorrection = queueRef.current
37
+ .filter(item => item.index < visibleStartIndex)
38
+ .reduce((sum, item) => sum + item.diff, 0);
39
+ // 应用修正
40
+ if (Math.abs(totalCorrection) > 1) {
41
+ setScrollOffset(prev => prev + totalCorrection);
42
+ }
43
+ // 清空队列
44
+ queueRef.current = [];
45
+ }, 100);
46
+ }, [enabled, visibleStartIndex, setScrollOffset]);
47
+ /**
48
+ * 标记用户正在滚动
49
+ * 在 onScroll 事件中调用
50
+ */
51
+ const markUserScrolling = useCallback(() => {
52
+ lastUserScrollTimeRef.current = Date.now();
53
+ }, []);
54
+ /**
55
+ * 清空修正队列
56
+ */
57
+ const clearQueue = useCallback(() => {
58
+ if (timerRef.current) {
59
+ clearTimeout(timerRef.current);
60
+ timerRef.current = null;
61
+ }
62
+ queueRef.current = [];
63
+ }, []);
64
+ return {
65
+ recordSizeChange,
66
+ markUserScrolling,
67
+ clearQueue
68
+ };
69
+ }
70
+
71
+ export { useScrollCorrection };
72
+ //# sourceMappingURL=useScrollCorrection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useScrollCorrection.js","sources":["../../../../src/components/list/hooks/useScrollCorrection.ts"],"sourcesContent":["import { useCallback, useRef } from 'react'\n\n/**\n * useScrollCorrection Hook - ScrollTop/Left 修正\n *\n * 当列表项尺寸变化时,自动修正滚动位置,避免视觉跳动\n *\n * 策略:\n * 1. 批量处理:100ms 窗口内的多次变化合并\n * 2. 智能判断:用户滚动后 300ms 内不修正\n * 3. 范围限制:仅修正可见区域之前的变化\n * 4. 阈值过滤:小于 1px 的变化忽略\n */\n\ninterface ScrollCorrectionItem {\n index: number\n diff: number // 尺寸变化量\n}\n\ninterface UseScrollCorrectionOptions {\n /** 是否启用修正 */\n enabled: boolean\n\n /** 可见区域起始索引 */\n visibleStartIndex: number\n\n /** 设置滚动位置的回调 */\n setScrollOffset: (offset: number | ((prev: number) => number)) => void\n}\n\ninterface UseScrollCorrectionReturn {\n /** 记录尺寸变化 */\n recordSizeChange: (index: number, oldSize: number, newSize: number) => void\n\n /** 标记用户正在滚动 */\n markUserScrolling: () => void\n\n /** 清空修正队列 */\n clearQueue: () => void\n}\n\nexport function useScrollCorrection(\n options: UseScrollCorrectionOptions\n): UseScrollCorrectionReturn {\n const { enabled, visibleStartIndex, setScrollOffset } = options\n\n // 修正队列\n const queueRef = useRef<ScrollCorrectionItem[]>([])\n\n // 修正定时器\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n // 用户最后滚动时间\n const lastUserScrollTimeRef = useRef(0)\n\n /**\n * 记录尺寸变化\n */\n const recordSizeChange = useCallback((\n index: number,\n oldSize: number,\n newSize: number\n ) => {\n if (!enabled) return\n\n const diff = newSize - oldSize\n\n // 阈值过滤:小于 1px 的变化忽略\n if (Math.abs(diff) < 1) return\n\n // 添加到队列\n queueRef.current.push({ index, diff })\n\n // 清除旧定时器\n if (timerRef.current) {\n clearTimeout(timerRef.current)\n }\n\n // 延迟执行修正(批量窗口 100ms)\n timerRef.current = setTimeout(() => {\n const now = Date.now()\n\n // 如果用户最近 300ms 内主动滚动,跳过修正\n if (now - lastUserScrollTimeRef.current < 300) {\n queueRef.current = []\n return\n }\n\n // 批量计算修正量(仅修正可见区域之前的变化)\n const totalCorrection = queueRef.current\n .filter(item => item.index < visibleStartIndex)\n .reduce((sum, item) => sum + item.diff, 0)\n\n // 应用修正\n if (Math.abs(totalCorrection) > 1) {\n setScrollOffset(prev => prev + totalCorrection)\n }\n\n // 清空队列\n queueRef.current = []\n }, 100)\n }, [enabled, visibleStartIndex, setScrollOffset])\n\n /**\n * 标记用户正在滚动\n * 在 onScroll 事件中调用\n */\n const markUserScrolling = useCallback(() => {\n lastUserScrollTimeRef.current = Date.now()\n }, [])\n\n /**\n * 清空修正队列\n */\n const clearQueue = useCallback(() => {\n if (timerRef.current) {\n clearTimeout(timerRef.current)\n timerRef.current = null\n }\n queueRef.current = []\n }, [])\n\n return {\n recordSizeChange,\n markUserScrolling,\n clearQueue\n }\n}\n"],"names":[],"mappings":";;AAyCM,SAAU,mBAAmB,CACjC,OAAmC,EAAA;IAEnC,MAAM,EAAE,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,GAAG,OAAO;;AAG/D,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAyB,EAAE,CAAC;;AAGnD,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAuC,IAAI,CAAC;;AAGnE,IAAA,MAAM,qBAAqB,GAAG,MAAM,CAAC,CAAC,CAAC;AAEvC;;AAEG;IACH,MAAM,gBAAgB,GAAG,WAAW,CAAC,CACnC,KAAa,EACb,OAAe,EACf,OAAe,KACb;AACF,QAAA,IAAI,CAAC,OAAO;YAAE;AAEd,QAAA,MAAM,IAAI,GAAG,OAAO,GAAG,OAAO;;AAG9B,QAAA,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE;;QAGxB,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;;AAGtC,QAAA,IAAI,QAAQ,CAAC,OAAO,EAAE;AACpB,YAAA,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC;;;AAIhC,QAAA,QAAQ,CAAC,OAAO,GAAG,UAAU,CAAC,MAAK;AACjC,YAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;;YAGtB,IAAI,GAAG,GAAG,qBAAqB,CAAC,OAAO,GAAG,GAAG,EAAE;AAC7C,gBAAA,QAAQ,CAAC,OAAO,GAAG,EAAE;gBACrB;;;AAIF,YAAA,MAAM,eAAe,GAAG,QAAQ,CAAC;iBAC9B,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,GAAG,iBAAiB;AAC7C,iBAAA,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,KAAK,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;;YAG5C,IAAI,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE;gBACjC,eAAe,CAAC,IAAI,IAAI,IAAI,GAAG,eAAe,CAAC;;;AAIjD,YAAA,QAAQ,CAAC,OAAO,GAAG,EAAE;SACtB,EAAE,GAAG,CAAC;KACR,EAAE,CAAC,OAAO,EAAE,iBAAiB,EAAE,eAAe,CAAC,CAAC;AAEjD;;;AAGG;AACH,IAAA,MAAM,iBAAiB,GAAG,WAAW,CAAC,MAAK;AACzC,QAAA,qBAAqB,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE;KAC3C,EAAE,EAAE,CAAC;AAEN;;AAEG;AACH,IAAA,MAAM,UAAU,GAAG,WAAW,CAAC,MAAK;AAClC,QAAA,IAAI,QAAQ,CAAC,OAAO,EAAE;AACpB,YAAA,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC;AAC9B,YAAA,QAAQ,CAAC,OAAO,GAAG,IAAI;;AAEzB,QAAA,QAAQ,CAAC,OAAO,GAAG,EAAE;KACtB,EAAE,EAAE,CAAC;IAEN,OAAO;QACL,gBAAgB;QAChB,iBAAiB;QACjB;KACD;AACH;;;;"}
@@ -1,7 +1,9 @@
1
1
  import React from 'react';
2
2
  import ListItem from './ListItem';
3
+ import NoMore from './NoMore';
3
4
  import StickyHeader from './StickyHeader';
4
5
  import StickySection from './StickySection';
6
+ /** 与官方 List.d.ts / ScrollView / harmony 对齐,不增减已有语义;扩展项仅用于高级能力 */
5
7
  export interface ListProps {
6
8
  showScrollbar?: boolean;
7
9
  scrollTop?: number;
@@ -15,9 +17,20 @@ export interface ListProps {
15
17
  onScrollToLower?: () => void;
16
18
  upperThreshold?: number;
17
19
  lowerThreshold?: number;
18
- cacheCount?: number;
20
+ /** 与 ScrollView cacheExtent 对齐(视口外渲染距离),可选 */
21
+ cacheExtent?: number;
22
+ /** 与 ScrollView enableBackToTop 对齐 */
23
+ enableBackToTop?: boolean;
24
+ /** 与 ScrollView onScrollStart 对齐 */
25
+ onScrollStart?: () => void;
26
+ /** 与 ScrollView onScrollEnd 对齐 */
27
+ onScrollEnd?: () => void;
28
+ scrollIntoView?: string;
29
+ /** 透传给最外层 ScrollView 的 className,便于自定义样式 */
30
+ className?: string;
19
31
  stickyHeader?: boolean;
20
32
  space?: number;
33
+ cacheCount?: number;
21
34
  itemData?: any[];
22
35
  itemSize?: number | ((index: number, data?: any[]) => number);
23
36
  height?: number | string;
@@ -28,9 +41,45 @@ export interface ListProps {
28
41
  headerWidth?: number;
29
42
  itemHeight?: number;
30
43
  itemWidth?: number;
44
+ useResizeObserver?: boolean;
45
+ estimatedItemSize?: number;
46
+ onItemSizeChange?: (index: number, size: number) => void;
47
+ showNoMore?: boolean;
48
+ noMoreText?: string;
49
+ noMoreStyle?: React.CSSProperties;
50
+ renderNoMore?: () => React.ReactNode;
51
+ onScrollIndex?: (start: number, end: number) => void;
52
+ /** 是否开启下拉刷新;与 Refresher 子组件二选一或同时存在(Refresher 子覆盖同名字段) */
53
+ refresherEnabled?: boolean;
54
+ refresherThreshold?: number;
55
+ refresherDefaultStyle?: 'black' | 'white' | 'none';
56
+ refresherBackground?: string;
57
+ refresherTriggered?: boolean;
58
+ onRefresherPulling?: (e?: {
59
+ detail?: {
60
+ deltaY?: number;
61
+ };
62
+ }) => void;
63
+ onRefresherRefresh?: () => void | Promise<void>;
64
+ onRefresherRestore?: () => void;
65
+ onRefresherAbort?: () => void;
66
+ onRefresherWillRefresh?: () => void;
67
+ onRefresherStatusChange?: (e?: {
68
+ detail?: {
69
+ status?: number;
70
+ dy?: number;
71
+ };
72
+ }) => void;
73
+ }
74
+ export interface ListHandle {
75
+ scroll: (options?: {
76
+ top?: number;
77
+ left?: number;
78
+ behavior?: 'auto' | 'smooth';
79
+ }) => void;
31
80
  }
32
81
  export declare function accumulate(arr: number[]): number[];
33
82
  export declare function isShaking(diffList: number[]): boolean;
34
- declare const List: React.FC<ListProps>;
35
- export { List, ListItem, StickyHeader, StickySection };
83
+ declare const List: React.ForwardRefExoticComponent<ListProps & React.RefAttributes<ListHandle>>;
84
+ export { List, ListItem, NoMore, StickyHeader, StickySection };
36
85
  export default List;