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

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.
@@ -8,18 +8,6 @@ interface UseItemSizeCacheReturn {
8
8
  getItemSize: (index: number) => number;
9
9
  /** 设置项的尺寸 */
10
10
  setItemSize: (index: number, size: number) => void;
11
- /** 获取指定索引的累积偏移 */
12
- getItemOffset: (index: number) => number;
13
- /** 清空缓存 */
14
- clearCache: () => void;
15
- /** 获取缓存统计信息 */
16
- getCacheStats: () => {
17
- totalItems: number;
18
- measuredItems: number;
19
- estimatedItems: number;
20
- };
21
- /** 维度名称('height' 或 'width') */
22
- dimension: 'height' | 'width';
23
11
  }
24
12
  export declare function useItemSizeCache(options: UseItemSizeCacheOptions): UseItemSizeCacheReturn;
25
13
  export {};
@@ -1,11 +1,9 @@
1
1
  import { useRef, useCallback } from 'react';
2
2
 
3
3
  function useItemSizeCache(options) {
4
- const { isHorizontal, estimatedItemSize, itemCount } = options;
4
+ const { estimatedItemSize } = options;
5
5
  // 缓存 Map:key = 索引,value = 尺寸信息
6
6
  const cacheRef = useRef(new Map());
7
- // 维度名称
8
- const dimension = isHorizontal ? 'width' : 'height';
9
7
  /**
10
8
  * 获取项的尺寸
11
9
  * 优先返回实际测量值,否则返回估算值
@@ -32,51 +30,9 @@ function useItemSizeCache(options) {
32
30
  isMeasured: true
33
31
  });
34
32
  }, [estimatedItemSize]);
35
- /**
36
- * 获取指定索引的累积偏移
37
- * 计算从 0 到 index-1 的所有项尺寸之和
38
- */
39
- const getItemOffset = useCallback((index) => {
40
- let offset = 0;
41
- for (let i = 0; i < index; i++) {
42
- offset += getItemSize(i);
43
- }
44
- return offset;
45
- }, [getItemSize]);
46
- /**
47
- * 清空缓存
48
- */
49
- const clearCache = useCallback(() => {
50
- cacheRef.current.clear();
51
- }, []);
52
- /**
53
- * 获取缓存统计信息
54
- */
55
- const getCacheStats = useCallback(() => {
56
- let measuredItems = 0;
57
- let estimatedItems = 0;
58
- for (let i = 0; i < itemCount; i++) {
59
- const cached = cacheRef.current.get(i);
60
- if (cached === null || cached === void 0 ? void 0 : cached.isMeasured) {
61
- measuredItems++;
62
- }
63
- else {
64
- estimatedItems++;
65
- }
66
- }
67
- return {
68
- totalItems: itemCount,
69
- measuredItems,
70
- estimatedItems
71
- };
72
- }, [itemCount]);
73
33
  return {
74
34
  getItemSize,
75
- setItemSize,
76
- getItemOffset,
77
- clearCache,
78
- getCacheStats,
79
- dimension
35
+ setItemSize
80
36
  };
81
37
  }
82
38
 
@@ -1 +1 @@
1
- {"version":3,"file":"useItemSizeCache.js","sources":["../../../../src/components/list/hooks/useItemSizeCache.ts"],"sourcesContent":["import { useCallback, useRef } from 'react'\n\n/**\n * useItemSizeCache Hook - 动态尺寸缓存管理\n *\n * 支持垂直滚动(高度)和水平滚动(宽度)两种模式\n */\n\n/** 单个项的尺寸缓存 */\ninterface ItemMeasureCache {\n measuredSize: number | null // 实际测量尺寸\n estimatedSize: number // 估算尺寸\n isMeasured: boolean // 是否已测量\n}\n\ninterface UseItemSizeCacheOptions {\n isHorizontal: boolean // 是否水平滚动\n estimatedItemSize: number // 估算尺寸\n itemCount: number // 项总数\n}\n\ninterface UseItemSizeCacheReturn {\n /** 获取项的尺寸(高度或宽度) */\n getItemSize: (index: number) => number\n\n /** 设置项的尺寸 */\n setItemSize: (index: number, size: number) => void\n\n /** 获取指定索引的累积偏移 */\n getItemOffset: (index: number) => number\n\n /** 清空缓存 */\n clearCache: () => void\n\n /** 获取缓存统计信息 */\n getCacheStats: () => {\n totalItems: number\n measuredItems: number\n estimatedItems: number\n }\n\n /** 维度名称('height' 或 'width') */\n dimension: 'height' | 'width'\n}\n\nexport function useItemSizeCache(\n options: UseItemSizeCacheOptions\n): UseItemSizeCacheReturn {\n const { isHorizontal, estimatedItemSize, itemCount } = options\n\n // 缓存 Map:key = 索引,value = 尺寸信息\n const cacheRef = useRef<Map<number, ItemMeasureCache>>(new Map())\n\n // 维度名称\n const dimension: 'height' | 'width' = isHorizontal ? 'width' : 'height'\n\n /**\n * 获取项的尺寸\n * 优先返回实际测量值,否则返回估算值\n */\n const getItemSize = useCallback((index: number): number => {\n const cached = cacheRef.current.get(index)\n\n if (cached?.isMeasured && cached.measuredSize !== null) {\n return cached.measuredSize\n }\n\n return estimatedItemSize\n }, [estimatedItemSize])\n\n /**\n * 设置项的尺寸(实际测量后调用)\n */\n const setItemSize = useCallback((index: number, size: number) => {\n const cached = cacheRef.current.get(index)\n\n // 尺寸变化小于 1px,忽略(避免微小抖动)\n if (cached?.measuredSize && Math.abs(cached.measuredSize - size) < 1) {\n return\n }\n\n cacheRef.current.set(index, {\n measuredSize: size,\n estimatedSize: estimatedItemSize,\n isMeasured: true\n })\n }, [estimatedItemSize])\n\n /**\n * 获取指定索引的累积偏移\n * 计算从 0 到 index-1 的所有项尺寸之和\n */\n const getItemOffset = useCallback((index: number): number => {\n let offset = 0\n\n for (let i = 0; i < index; i++) {\n offset += getItemSize(i)\n }\n\n return offset\n }, [getItemSize])\n\n /**\n * 清空缓存\n */\n const clearCache = useCallback(() => {\n cacheRef.current.clear()\n }, [])\n\n /**\n * 获取缓存统计信息\n */\n const getCacheStats = useCallback(() => {\n let measuredItems = 0\n let estimatedItems = 0\n\n for (let i = 0; i < itemCount; i++) {\n const cached = cacheRef.current.get(i)\n if (cached?.isMeasured) {\n measuredItems++\n } else {\n estimatedItems++\n }\n }\n\n return {\n totalItems: itemCount,\n measuredItems,\n estimatedItems\n }\n }, [itemCount])\n\n return {\n getItemSize,\n setItemSize,\n getItemOffset,\n clearCache,\n getCacheStats,\n dimension\n }\n}\n"],"names":[],"mappings":";;AA6CM,SAAU,gBAAgB,CAC9B,OAAgC,EAAA;IAEhC,MAAM,EAAE,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,GAAG,OAAO;;IAG9D,MAAM,QAAQ,GAAG,MAAM,CAAgC,IAAI,GAAG,EAAE,CAAC;;IAGjE,MAAM,SAAS,GAAuB,YAAY,GAAG,OAAO,GAAG,QAAQ;AAEvE;;;AAGG;AACH,IAAA,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,KAAa,KAAY;QACxD,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;AAE1C,QAAA,IAAI,CAAA,MAAM,KAAN,IAAA,IAAA,MAAM,uBAAN,MAAM,CAAE,UAAU,KAAI,MAAM,CAAC,YAAY,KAAK,IAAI,EAAE;YACtD,OAAO,MAAM,CAAC,YAAY;;AAG5B,QAAA,OAAO,iBAAiB;AAC1B,KAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC;AAEvB;;AAEG;IACH,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,KAAa,EAAE,IAAY,KAAI;QAC9D,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;;QAG1C,IAAI,CAAA,MAAM,KAAN,IAAA,IAAA,MAAM,uBAAN,MAAM,CAAE,YAAY,KAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE;YACpE;;AAGF,QAAA,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE;AAC1B,YAAA,YAAY,EAAE,IAAI;AAClB,YAAA,aAAa,EAAE,iBAAiB;AAChC,YAAA,UAAU,EAAE;AACb,SAAA,CAAC;AACJ,KAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC;AAEvB;;;AAGG;AACH,IAAA,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,KAAa,KAAY;QAC1D,IAAI,MAAM,GAAG,CAAC;AAEd,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE;AAC9B,YAAA,MAAM,IAAI,WAAW,CAAC,CAAC,CAAC;;AAG1B,QAAA,OAAO,MAAM;AACf,KAAC,EAAE,CAAC,WAAW,CAAC,CAAC;AAEjB;;AAEG;AACH,IAAA,MAAM,UAAU,GAAG,WAAW,CAAC,MAAK;AAClC,QAAA,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE;KACzB,EAAE,EAAE,CAAC;AAEN;;AAEG;AACH,IAAA,MAAM,aAAa,GAAG,WAAW,CAAC,MAAK;QACrC,IAAI,aAAa,GAAG,CAAC;QACrB,IAAI,cAAc,GAAG,CAAC;AAEtB,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE;YAClC,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;YACtC,IAAI,MAAM,aAAN,MAAM,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAN,MAAM,CAAE,UAAU,EAAE;AACtB,gBAAA,aAAa,EAAE;;iBACV;AACL,gBAAA,cAAc,EAAE;;;QAIpB,OAAO;AACL,YAAA,UAAU,EAAE,SAAS;YACrB,aAAa;YACb;SACD;AACH,KAAC,EAAE,CAAC,SAAS,CAAC,CAAC;IAEf,OAAO;QACL,WAAW;QACX,WAAW;QACX,aAAa;QACb,UAAU;QACV,aAAa;QACb;KACD;AACH;;;;"}
1
+ {"version":3,"file":"useItemSizeCache.js","sources":["../../../../src/components/list/hooks/useItemSizeCache.ts"],"sourcesContent":["import { useCallback, useRef } from 'react'\n\n/**\n * useItemSizeCache Hook - 动态尺寸缓存管理\n *\n * 支持垂直滚动(高度)和水平滚动(宽度)两种模式\n */\n\n/** 单个项的尺寸缓存 */\ninterface ItemMeasureCache {\n measuredSize: number | null // 实际测量尺寸\n estimatedSize: number // 估算尺寸\n isMeasured: boolean // 是否已测量\n}\n\ninterface UseItemSizeCacheOptions {\n isHorizontal: boolean // 是否水平滚动\n estimatedItemSize: number // 估算尺寸\n itemCount: number // 项总数\n}\n\ninterface UseItemSizeCacheReturn {\n /** 获取项的尺寸(高度或宽度) */\n getItemSize: (index: number) => number\n\n /** 设置项的尺寸 */\n setItemSize: (index: number, size: number) => void\n}\n\nexport function useItemSizeCache(\n options: UseItemSizeCacheOptions\n): UseItemSizeCacheReturn {\n const { estimatedItemSize } = options\n\n // 缓存 Map:key = 索引,value = 尺寸信息\n const cacheRef = useRef<Map<number, ItemMeasureCache>>(new Map())\n\n /**\n * 获取项的尺寸\n * 优先返回实际测量值,否则返回估算值\n */\n const getItemSize = useCallback((index: number): number => {\n const cached = cacheRef.current.get(index)\n\n if (cached?.isMeasured && cached.measuredSize !== null) {\n return cached.measuredSize\n }\n\n return estimatedItemSize\n }, [estimatedItemSize])\n\n /**\n * 设置项的尺寸(实际测量后调用)\n */\n const setItemSize = useCallback((index: number, size: number) => {\n const cached = cacheRef.current.get(index)\n\n // 尺寸变化小于 1px,忽略(避免微小抖动)\n if (cached?.measuredSize && Math.abs(cached.measuredSize - size) < 1) {\n return\n }\n\n cacheRef.current.set(index, {\n measuredSize: size,\n estimatedSize: estimatedItemSize,\n isMeasured: true\n })\n }, [estimatedItemSize])\n\n return {\n getItemSize,\n setItemSize\n }\n}\n"],"names":[],"mappings":";;AA6BM,SAAU,gBAAgB,CAC9B,OAAgC,EAAA;AAEhC,IAAA,MAAM,EAAE,iBAAiB,EAAE,GAAG,OAAO;;IAGrC,MAAM,QAAQ,GAAG,MAAM,CAAgC,IAAI,GAAG,EAAE,CAAC;AAEjE;;;AAGG;AACH,IAAA,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,KAAa,KAAY;QACxD,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;AAE1C,QAAA,IAAI,CAAA,MAAM,KAAN,IAAA,IAAA,MAAM,uBAAN,MAAM,CAAE,UAAU,KAAI,MAAM,CAAC,YAAY,KAAK,IAAI,EAAE;YACtD,OAAO,MAAM,CAAC,YAAY;;AAG5B,QAAA,OAAO,iBAAiB;AAC1B,KAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC;AAEvB;;AAEG;IACH,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,KAAa,EAAE,IAAY,KAAI;QAC9D,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;;QAG1C,IAAI,CAAA,MAAM,KAAN,IAAA,IAAA,MAAM,uBAAN,MAAM,CAAE,YAAY,KAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE;YACpE;;AAGF,QAAA,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE;AAC1B,YAAA,YAAY,EAAE,IAAI;AAClB,YAAA,aAAa,EAAE,iBAAiB;AAChC,YAAA,UAAU,EAAE;AACb,SAAA,CAAC;AACJ,KAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC;IAEvB,OAAO;QACL,WAAW;QACX;KACD;AACH;;;;"}
@@ -41,6 +41,8 @@ function useResizeObserver(options) {
41
41
  }, [enabled, isHorizontal, onResize]);
42
42
  /**
43
43
  * 小程序实现:使用 IntersectionObserver + SelectorQuery
44
+ * 使用唯一 id 选择器,避免多 List 并存时误命中(index>=0 为 item,index<0 为 header)
45
+ * 使用 Taro.nextTick 延后 observe,确保 setData 已提交、节点已挂载,避免 "Node is not found" 报错
44
46
  */
45
47
  const observeMiniProgram = useCallback((_element, index) => {
46
48
  if (!enabled)
@@ -48,34 +50,38 @@ function useResizeObserver(options) {
48
50
  // 避免重复观察
49
51
  if (intersectionObserversRef.current.has(index))
50
52
  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
- }
53
+ const selector = index >= 0
54
+ ? `#${listId}-list-item-inner-${index}`
55
+ : `#${listId}-list-header-inner-${-index - 1}`;
56
+ const doObserve = () => {
57
+ try {
58
+ // 创建 IntersectionObserver
59
+ const observer = Taro.createIntersectionObserver(Taro.getCurrentInstance().page, {
60
+ observeAll: true
61
+ });
62
+ // 相对于 List 容器
63
+ observer.relativeTo(`#${listId}`);
64
+ // 观察元素进入可见区域(唯一 id 避免跨 List 误命中)
65
+ observer.observe(selector, (res) => {
66
+ if (res.intersectionRatio > 0) {
67
+ Taro.createSelectorQuery()
68
+ .select(selector)
69
+ .boundingClientRect((rect) => {
70
+ if (rect) {
71
+ const size = isHorizontal ? rect.width : rect.height;
72
+ onResize(index, size);
73
+ }
74
+ })
75
+ .exec();
76
+ }
77
+ });
78
+ intersectionObserversRef.current.set(index, observer);
79
+ }
80
+ catch (_a) {
81
+ // ignore observe failure
82
+ }
83
+ };
84
+ Taro.nextTick(doObserve);
79
85
  }, [enabled, isHorizontal, listId, onResize]);
80
86
  /**
81
87
  * 观察元素(平台自动适配)
@@ -1 +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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"}
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 * 使用唯一 id 选择器,避免多 List 并存时误命中(index>=0 为 item,index<0 为 header)\n * 使用 Taro.nextTick 延后 observe,确保 setData 已提交、节点已挂载,避免 \"Node is not found\" 报错\n */\n const observeMiniProgram = useCallback((_element: any, index: number) => {\n if (!enabled) return\n\n // 避免重复观察\n if (intersectionObserversRef.current.has(index)) return\n\n const selector = index >= 0\n ? `#${listId}-list-item-inner-${index}`\n : `#${listId}-list-header-inner-${-index - 1}`\n\n const doObserve = () => {\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 // 观察元素进入可见区域(唯一 id 避免跨 List 误命中)\n observer.observe(selector, (res) => {\n if (res.intersectionRatio > 0) {\n Taro.createSelectorQuery()\n .select(selector)\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 }\n\n Taro.nextTick(doObserve)\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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"}
@@ -1,8 +1,9 @@
1
+ import { type MutableRefObject } from 'react';
1
2
  interface UseScrollCorrectionOptions {
2
3
  /** 是否启用修正 */
3
4
  enabled: boolean;
4
- /** 可见区域起始索引 */
5
- visibleStartIndex: number;
5
+ /** 可见区域起始索引(取值时读 ref.current,避免 stale) */
6
+ visibleStartIndexRef: MutableRefObject<number>;
6
7
  /** 设置滚动位置的回调 */
7
8
  setScrollOffset: (offset: number | ((prev: number) => number)) => void;
8
9
  }
@@ -1,7 +1,7 @@
1
1
  import { useRef, useCallback } from 'react';
2
2
 
3
3
  function useScrollCorrection(options) {
4
- const { enabled, visibleStartIndex, setScrollOffset } = options;
4
+ const { enabled, visibleStartIndexRef, setScrollOffset } = options;
5
5
  // 修正队列
6
6
  const queueRef = useRef([]);
7
7
  // 修正定时器
@@ -32,9 +32,10 @@ function useScrollCorrection(options) {
32
32
  queueRef.current = [];
33
33
  return;
34
34
  }
35
- // 批量计算修正量(仅修正可见区域之前的变化)
35
+ // 批量计算修正量(仅修正可见区域之前的变化;读 ref 获取最新值)
36
+ const visibleStart = visibleStartIndexRef.current;
36
37
  const totalCorrection = queueRef.current
37
- .filter(item => item.index < visibleStartIndex)
38
+ .filter(item => item.index < visibleStart)
38
39
  .reduce((sum, item) => sum + item.diff, 0);
39
40
  // 应用修正
40
41
  if (Math.abs(totalCorrection) > 1) {
@@ -43,7 +44,7 @@ function useScrollCorrection(options) {
43
44
  // 清空队列
44
45
  queueRef.current = [];
45
46
  }, 100);
46
- }, [enabled, visibleStartIndex, setScrollOffset]);
47
+ }, [enabled, visibleStartIndexRef, setScrollOffset]);
47
48
  /**
48
49
  * 标记用户正在滚动
49
50
  * 在 onScroll 事件中调用
@@ -1 +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
+ {"version":3,"file":"useScrollCorrection.js","sources":["../../../../src/components/list/hooks/useScrollCorrection.ts"],"sourcesContent":["import { type MutableRefObject, 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 /** 可见区域起始索引(取值时读 ref.current,避免 stale) */\n visibleStartIndexRef: MutableRefObject<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, visibleStartIndexRef, 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 // 批量计算修正量(仅修正可见区域之前的变化;读 ref 获取最新值)\n const visibleStart = visibleStartIndexRef.current\n const totalCorrection = queueRef.current\n .filter(item => item.index < visibleStart)\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, visibleStartIndexRef, 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,oBAAoB,EAAE,eAAe,EAAE,GAAG,OAAO;;AAGlE,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,YAAY,GAAG,oBAAoB,CAAC,OAAO;AACjD,YAAA,MAAM,eAAe,GAAG,QAAQ,CAAC;iBAC9B,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,GAAG,YAAY;AACxC,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,oBAAoB,EAAE,eAAe,CAAC,CAAC;AAEpD;;;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;;;;"}
@@ -75,7 +75,6 @@ export interface ListHandle {
75
75
  scroll: (options?: {
76
76
  top?: number;
77
77
  left?: number;
78
- behavior?: 'auto' | 'smooth';
79
78
  }) => void;
80
79
  }
81
80
  export declare function accumulate(arr: number[]): number[];