@tarojs/components-advanced 4.1.12-beta.7 → 4.1.12-beta.70

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 (84) hide show
  1. package/dist/components/index.js +2 -1
  2. package/dist/components/index.js.map +1 -1
  3. package/dist/components/list/hooks/useItemSizeCache.d.ts +1 -1
  4. package/dist/components/list/hooks/useItemSizeCache.js +5 -5
  5. package/dist/components/list/hooks/useItemSizeCache.js.map +1 -1
  6. package/dist/components/list/hooks/useListNestedScroll.d.ts +18 -0
  7. package/dist/components/list/hooks/useListNestedScroll.js +61 -0
  8. package/dist/components/list/hooks/useListNestedScroll.js.map +1 -0
  9. package/dist/components/list/hooks/useListScrollElementAttach.d.ts +25 -0
  10. package/dist/components/list/hooks/useListScrollElementAttach.js +88 -0
  11. package/dist/components/list/hooks/useListScrollElementAttach.js.map +1 -0
  12. package/dist/components/list/hooks/useListScrollElementAttachWeapp.d.ts +15 -0
  13. package/dist/components/list/hooks/useListScrollElementAttachWeapp.js +135 -0
  14. package/dist/components/list/hooks/useListScrollElementAttachWeapp.js.map +1 -0
  15. package/dist/components/list/hooks/useMeasureStartOffset.d.ts +4 -2
  16. package/dist/components/list/hooks/useMeasureStartOffset.js +56 -15
  17. package/dist/components/list/hooks/useMeasureStartOffset.js.map +1 -1
  18. package/dist/components/list/hooks/useMeasureStartOffsetWeapp.d.ts +14 -0
  19. package/dist/components/list/hooks/useMeasureStartOffsetWeapp.js +87 -0
  20. package/dist/components/list/hooks/useMeasureStartOffsetWeapp.js.map +1 -0
  21. package/dist/components/list/hooks/useRefresher.d.ts +8 -2
  22. package/dist/components/list/hooks/useRefresher.js +125 -67
  23. package/dist/components/list/hooks/useRefresher.js.map +1 -1
  24. package/dist/components/list/hooks/useResizeObserver.d.ts +4 -1
  25. package/dist/components/list/hooks/useResizeObserver.js +8 -7
  26. package/dist/components/list/hooks/useResizeObserver.js.map +1 -1
  27. package/dist/components/list/hooks/useScrollCorrection.js +2 -1
  28. package/dist/components/list/hooks/useScrollCorrection.js.map +1 -1
  29. package/dist/components/list/hooks/useScrollParentAutoFind.d.ts +20 -0
  30. package/dist/components/list/hooks/useScrollParentAutoFind.js +81 -0
  31. package/dist/components/list/hooks/useScrollParentAutoFind.js.map +1 -0
  32. package/dist/components/list/index.d.ts +12 -17
  33. package/dist/components/list/index.js +266 -286
  34. package/dist/components/list/index.js.map +1 -1
  35. package/dist/components/list/utils.d.ts +12 -0
  36. package/dist/components/list/utils.js +23 -1
  37. package/dist/components/list/utils.js.map +1 -1
  38. package/dist/components/virtual-list/vue/list.d.ts +10 -10
  39. package/dist/components/virtual-waterfall/vue/waterfall.d.ts +10 -10
  40. package/dist/components/water-flow/feed-column.d.ts +2 -0
  41. package/dist/components/water-flow/feed-column.js +10 -0
  42. package/dist/components/water-flow/feed-column.js.map +1 -0
  43. package/dist/components/water-flow/flow-item.js +18 -11
  44. package/dist/components/water-flow/flow-item.js.map +1 -1
  45. package/dist/components/water-flow/flow-section.js +1 -1
  46. package/dist/components/water-flow/flow-section.js.map +1 -1
  47. package/dist/components/water-flow/index.d.ts +1 -1
  48. package/dist/components/water-flow/interface.d.ts +31 -4
  49. package/dist/components/water-flow/node.d.ts +3 -0
  50. package/dist/components/water-flow/node.js +34 -1
  51. package/dist/components/water-flow/node.js.map +1 -1
  52. package/dist/components/water-flow/root.d.ts +36 -3
  53. package/dist/components/water-flow/root.js +143 -44
  54. package/dist/components/water-flow/root.js.map +1 -1
  55. package/dist/components/water-flow/section.d.ts +23 -1
  56. package/dist/components/water-flow/section.js +120 -20
  57. package/dist/components/water-flow/section.js.map +1 -1
  58. package/dist/components/water-flow/utils.d.ts +2 -0
  59. package/dist/components/water-flow/utils.js +3 -1
  60. package/dist/components/water-flow/utils.js.map +1 -1
  61. package/dist/components/water-flow/water-flow-node-cache.d.ts +24 -0
  62. package/dist/components/water-flow/water-flow-node-cache.js +161 -0
  63. package/dist/components/water-flow/water-flow-node-cache.js.map +1 -0
  64. package/dist/components/water-flow/water-flow.d.ts +2 -3
  65. package/dist/components/water-flow/water-flow.js +257 -77
  66. package/dist/components/water-flow/water-flow.js.map +1 -1
  67. package/dist/index.js +4 -1
  68. package/dist/index.js.map +1 -1
  69. package/dist/utils/dom.d.ts +2 -2
  70. package/dist/utils/dom.js +7 -6
  71. package/dist/utils/dom.js.map +1 -1
  72. package/dist/utils/index.d.ts +2 -0
  73. package/dist/utils/index.js +2 -0
  74. package/dist/utils/index.js.map +1 -1
  75. package/dist/utils/scrollElementContext.d.ts +15 -0
  76. package/dist/utils/scrollElementContext.js +14 -0
  77. package/dist/utils/scrollElementContext.js.map +1 -0
  78. package/dist/utils/scrollParent.d.ts +33 -0
  79. package/dist/utils/scrollParent.js +88 -0
  80. package/dist/utils/scrollParent.js.map +1 -0
  81. package/dist/utils/weapp-scope.d.ts +7 -0
  82. package/dist/utils/weapp-scope.js +20 -0
  83. package/dist/utils/weapp-scope.js.map +1 -0
  84. package/package.json +9 -9
@@ -1 +1 @@
1
- {"version":3,"file":"useMeasureStartOffset.js","sources":["../../../../src/components/list/hooks/useMeasureStartOffset.ts"],"sourcesContent":["import { useEffect, useState } from 'react'\n\nimport type { RefObject } from 'react'\n\n/**\n * 测量 scrollElement 内 content 节点之前所有兄弟的高度/宽度之和,作为 startOffset。\n * 用于 scrollElement 模式下无 Context 时自动计算上方内容高度(与下方 DOM stacking 对称)。\n * 用「累加前兄弟尺寸」替代 offsetTop/offsetLeft,避免 offsetParent 包含外部区域导致偏大。\n */\nexport function useMeasureStartOffset(\n scrollElRef: RefObject<HTMLElement | null>,\n contentRef: RefObject<HTMLElement | null>,\n options: { enabled: boolean, isHorizontal?: boolean }\n): number {\n const { enabled, isHorizontal = false } = options\n const [measuredStartOffset, setMeasuredStartOffset] = useState(0)\n\n useEffect(() => {\n if (!enabled) return\n const scrollEl = scrollElRef.current\n const contentEl = contentRef.current\n if (!scrollEl || !contentEl) return\n\n const measure = () => {\n const el = contentRef.current\n if (!el || el.parentElement !== scrollEl) return\n let offset = 0\n let prev: Element | null = el.previousElementSibling\n while (prev) {\n offset += isHorizontal ? (prev as HTMLElement).offsetWidth : (prev as HTMLElement).offsetHeight\n prev = prev.previousElementSibling\n }\n setMeasuredStartOffset((prevVal) => (Math.abs(prevVal - offset) < 1 ? prevVal : offset))\n }\n measure()\n const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(measure) : null\n if (ro) {\n ro.observe(scrollEl)\n // 同时观察 scrollEl 的直系子节点:图片加载、展开/收起等会改变兄弟高度,但 scrollEl 自身 clientHeight 不变,需观察子节点\n Array.from(scrollEl.children).forEach((child) => ro.observe(child))\n return () => ro.disconnect()\n }\n }, [enabled, scrollElRef, contentRef, isHorizontal])\n\n return measuredStartOffset\n}\n"],"names":[],"mappings":";;AAIA;;;;AAIG;SACa,qBAAqB,CACnC,WAA0C,EAC1C,UAAyC,EACzC,OAAqD,EAAA;IAErD,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,KAAK,EAAE,GAAG,OAAO;IACjD,MAAM,CAAC,mBAAmB,EAAE,sBAAsB,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;IAEjE,SAAS,CAAC,MAAK;AACb,QAAA,IAAI,CAAC,OAAO;YAAE;AACd,QAAA,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO;AACpC,QAAA,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO;AACpC,QAAA,IAAI,CAAC,QAAQ,IAAI,CAAC,SAAS;YAAE;QAE7B,MAAM,OAAO,GAAG,MAAK;AACnB,YAAA,MAAM,EAAE,GAAG,UAAU,CAAC,OAAO;AAC7B,YAAA,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,aAAa,KAAK,QAAQ;gBAAE;YAC1C,IAAI,MAAM,GAAG,CAAC;AACd,YAAA,IAAI,IAAI,GAAmB,EAAE,CAAC,sBAAsB;YACpD,OAAO,IAAI,EAAE;AACX,gBAAA,MAAM,IAAI,YAAY,GAAI,IAAoB,CAAC,WAAW,GAAI,IAAoB,CAAC,YAAY;AAC/F,gBAAA,IAAI,GAAG,IAAI,CAAC,sBAAsB;;YAEpC,sBAAsB,CAAC,CAAC,OAAO,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,OAAO,GAAG,MAAM,CAAC,CAAC;AAC1F,SAAC;AACD,QAAA,OAAO,EAAE;AACT,QAAA,MAAM,EAAE,GAAG,OAAO,cAAc,KAAK,WAAW,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC,GAAG,IAAI;QACrF,IAAI,EAAE,EAAE;AACN,YAAA,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;;YAEpB,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AACnE,YAAA,OAAO,MAAM,EAAE,CAAC,UAAU,EAAE;;KAE/B,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;AAEpD,IAAA,OAAO,mBAAmB;AAC5B;;;;"}
1
+ {"version":3,"file":"useMeasureStartOffset.js","sources":["../../../../src/components/list/hooks/useMeasureStartOffset.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react'\n\nimport type { RefObject } from 'react'\n\nconst WARNED_NESTED = new WeakSet<Element>()\nconst MAX_RETRY = 20\nconst RETRY_DELAY = 50\n\n/**\n * 测量 scrollElement 内 content 节点之前所有兄弟的高度/宽度之和,作为 startOffset。\n * 用于 scrollElement 模式下无 Context/props 时自动计算上方内容高度。\n * - 直接子节点:累加前兄弟尺寸(更稳定)\n * - 非直接子节点:getBoundingClientRect 回退,并 warning 建议传 startOffset\n * - ref 未就绪时自动重试,确保绑定到最外层滚动容器后能正确测量\n */\nexport function useMeasureStartOffset(\n scrollElRef: RefObject<HTMLElement | null>,\n contentRef: RefObject<HTMLElement | null>,\n options: { enabled: boolean, isHorizontal?: boolean }\n): number {\n const { enabled, isHorizontal = false } = options\n const [measuredStartOffset, setMeasuredStartOffset] = useState(0)\n const [retryTrigger, setRetryTrigger] = useState(0)\n const retryCountRef = useRef(0)\n\n useEffect(() => {\n if (!enabled) {\n retryCountRef.current = 0\n return\n }\n const scrollEl = scrollElRef.current\n const contentEl = contentRef.current\n\n // ref 未就绪时重试(如 ScrollView 与 WaterFlow 异步挂载)\n if (!scrollEl || !contentEl) {\n if (retryCountRef.current < MAX_RETRY) {\n retryCountRef.current += 1\n const id = setTimeout(() => setRetryTrigger((t) => t + 1), RETRY_DELAY)\n return () => clearTimeout(id)\n }\n return\n }\n retryCountRef.current = 0\n\n const measure = () => {\n const el = contentRef.current\n const scroll = scrollElRef.current\n if (!el || !scroll) return\n\n if (el.parentElement === scroll) {\n // 直接子节点:累加前兄弟尺寸\n let offset = 0\n let prev: Element | null = el.previousElementSibling\n while (prev) {\n offset += isHorizontal ? (prev as HTMLElement).offsetWidth : (prev as HTMLElement).offsetHeight\n prev = prev.previousElementSibling\n }\n setMeasuredStartOffset((prevVal) => (Math.abs(prevVal - offset) < 1 ? prevVal : offset))\n return\n }\n\n // 非直接子节点:getBoundingClientRect 回退\n const scrollRect = scroll.getBoundingClientRect()\n const contentRect = el.getBoundingClientRect()\n const offset = isHorizontal\n ? contentRect.left - scrollRect.left + scroll.scrollLeft\n : contentRect.top - scrollRect.top + scroll.scrollTop\n const value = Math.max(0, offset)\n setMeasuredStartOffset((prevVal) => (Math.abs(prevVal - value) < 1 ? prevVal : value))\n\n if (!WARNED_NESTED.has(el)) {\n WARNED_NESTED.add(el)\n // eslint-disable-next-line no-console\n console.warn(\n '[useMeasureStartOffset] content 不是 scrollElement 的直接子节点,使用 getBoundingClientRect 测量。' +\n '建议通过 props 或 Context 传入 startOffset 以获得更稳定的布局。'\n )\n }\n }\n\n measure()\n scrollEl.addEventListener('scroll', measure, { passive: true })\n const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(measure) : null\n if (ro) {\n ro.observe(scrollEl)\n Array.from(scrollEl.children).forEach((child) => ro.observe(child))\n // 非直接子节点:content 自身或其祖先变化时也需重测\n ro.observe(contentEl)\n }\n return () => {\n scrollEl.removeEventListener('scroll', measure)\n ro?.disconnect()\n }\n }, [enabled, scrollElRef, contentRef, isHorizontal, retryTrigger])\n\n return measuredStartOffset\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { MutableRefObject, RefObject } from 'react';
2
+ /**
3
+ * 小程序版 startOffset:对外层 scroll-view 与带 `contentId` 的外包各执行一次 SelectorQuery(各自 `weappScope(ref)`),
4
+ * 再合并结果;避免二者不在同一自定义组件时,单次 `in` 链式 `select` 查不全。
5
+ *
6
+ * 公式:startOffset = contentRect.top - scrollRect.top + scrollTop
7
+ */
8
+ export declare function useMeasureStartOffsetWeapp(scrollElRef: RefObject<HTMLElement | null>, contentId: string, options: {
9
+ enabled: boolean;
10
+ isHorizontal?: boolean;
11
+ startOffsetRef?: MutableRefObject<number>;
12
+ /** 挂载 `id={contentId}` 的嵌套内容外包 View;第二次 query 用其 ref 取 content 侧 `_scope` */
13
+ contentWrapperRef: RefObject<HTMLElement | null>;
14
+ }): number;
@@ -0,0 +1,87 @@
1
+ import { useState, useRef, useEffect } from 'react';
2
+ import { createSelectorQueryForRef } from '../utils.js';
3
+
4
+ const MAX_RETRY = 20;
5
+ const RETRY_DELAY = 50;
6
+ /** 与 useListScrollElementAttachWeapp 容器测量间隔一致(H5 无 interval,用 ResizeObserver+scroll 事件驱动) */
7
+ const MEASURE_INTERVAL = 150;
8
+ /**
9
+ * 小程序版 startOffset:对外层 scroll-view 与带 `contentId` 的外包各执行一次 SelectorQuery(各自 `weappScope(ref)`),
10
+ * 再合并结果;避免二者不在同一自定义组件时,单次 `in` 链式 `select` 查不全。
11
+ *
12
+ * 公式:startOffset = contentRect.top - scrollRect.top + scrollTop
13
+ */
14
+ function useMeasureStartOffsetWeapp(scrollElRef, contentId, options) {
15
+ const { enabled, isHorizontal = false, startOffsetRef, contentWrapperRef } = options;
16
+ const [measuredStartOffset, setMeasuredStartOffset] = useState(0);
17
+ const [retryTrigger, setRetryTrigger] = useState(0);
18
+ const retryCountRef = useRef(0);
19
+ const autoIdRef = useRef(`_ls_so_${Math.random().toString(36).slice(2, 9)}`);
20
+ useEffect(() => {
21
+ if (!enabled || !contentId) {
22
+ retryCountRef.current = 0;
23
+ return;
24
+ }
25
+ const applyRects = (scrollRect, scrollInfo, contentRect) => {
26
+ var _a, _b;
27
+ if (!scrollRect || !scrollInfo || !contentRect)
28
+ return;
29
+ const scrollTop = (_a = scrollInfo.scrollTop) !== null && _a !== void 0 ? _a : 0;
30
+ const scrollLeft = (_b = scrollInfo.scrollLeft) !== null && _b !== void 0 ? _b : 0;
31
+ const value = isHorizontal
32
+ ? Math.max(0, contentRect.left - scrollRect.left + scrollLeft)
33
+ : Math.max(0, contentRect.top - scrollRect.top + scrollTop);
34
+ if (startOffsetRef)
35
+ startOffsetRef.current = value;
36
+ setMeasuredStartOffset((prevVal) => (Math.abs(prevVal - value) < 1 ? prevVal : value));
37
+ };
38
+ const measure = () => {
39
+ const scrollEl = scrollElRef.current;
40
+ if (!scrollEl)
41
+ return;
42
+ if (!scrollEl.id) {
43
+ scrollEl.id = autoIdRef.current;
44
+ }
45
+ const scrollViewId = scrollEl.id;
46
+ createSelectorQueryForRef(scrollElRef)
47
+ .select(`#${scrollViewId}`)
48
+ .boundingClientRect()
49
+ .select(`#${scrollViewId}`)
50
+ .scrollOffset()
51
+ .exec((scrollRes) => {
52
+ const scrollRect = scrollRes === null || scrollRes === void 0 ? void 0 : scrollRes[0];
53
+ const scrollInfo = scrollRes === null || scrollRes === void 0 ? void 0 : scrollRes[1];
54
+ if (!scrollRect || !scrollInfo)
55
+ return;
56
+ createSelectorQueryForRef(contentWrapperRef)
57
+ .select(`#${contentId}`)
58
+ .boundingClientRect()
59
+ .exec((contentRes) => {
60
+ const contentRect = contentRes === null || contentRes === void 0 ? void 0 : contentRes[0];
61
+ applyRects(scrollRect, scrollInfo, contentRect);
62
+ });
63
+ });
64
+ };
65
+ const scrollEl = scrollElRef.current;
66
+ if (!scrollEl) {
67
+ if (retryCountRef.current < MAX_RETRY) {
68
+ retryCountRef.current += 1;
69
+ const id = setTimeout(() => setRetryTrigger((t) => t + 1), RETRY_DELAY);
70
+ return () => clearTimeout(id);
71
+ }
72
+ return;
73
+ }
74
+ retryCountRef.current = 0;
75
+ measure();
76
+ scrollEl.addEventListener('scroll', measure);
77
+ const interval = setInterval(measure, MEASURE_INTERVAL);
78
+ return () => {
79
+ scrollEl.removeEventListener('scroll', measure);
80
+ clearInterval(interval);
81
+ };
82
+ }, [enabled, scrollElRef, contentId, contentWrapperRef, isHorizontal, retryTrigger]);
83
+ return measuredStartOffset;
84
+ }
85
+
86
+ export { useMeasureStartOffsetWeapp };
87
+ //# sourceMappingURL=useMeasureStartOffsetWeapp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMeasureStartOffsetWeapp.js","sources":["../../../../src/components/list/hooks/useMeasureStartOffsetWeapp.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react'\n\nimport { createSelectorQueryForRef } from '../utils'\n\nimport type { MutableRefObject, RefObject } from 'react'\n\nconst MAX_RETRY = 20\nconst RETRY_DELAY = 50\n/** 与 useListScrollElementAttachWeapp 容器测量间隔一致(H5 无 interval,用 ResizeObserver+scroll 事件驱动) */\nconst MEASURE_INTERVAL = 150\n\n/**\n * 小程序版 startOffset:对外层 scroll-view 与带 `contentId` 的外包各执行一次 SelectorQuery(各自 `weappScope(ref)`),\n * 再合并结果;避免二者不在同一自定义组件时,单次 `in` 链式 `select` 查不全。\n *\n * 公式:startOffset = contentRect.top - scrollRect.top + scrollTop\n */\nexport function useMeasureStartOffsetWeapp(\n scrollElRef: RefObject<HTMLElement | null>,\n contentId: string,\n options: {\n enabled: boolean\n isHorizontal?: boolean\n startOffsetRef?: MutableRefObject<number>\n /** 挂载 `id={contentId}` 的嵌套内容外包 View;第二次 query 用其 ref 取 content 侧 `_scope` */\n contentWrapperRef: RefObject<HTMLElement | null>\n }\n): number {\n const { enabled, isHorizontal = false, startOffsetRef, contentWrapperRef } = options\n const [measuredStartOffset, setMeasuredStartOffset] = useState(0)\n const [retryTrigger, setRetryTrigger] = useState(0)\n const retryCountRef = useRef(0)\n const autoIdRef = useRef(`_ls_so_${Math.random().toString(36).slice(2, 9)}`)\n\n useEffect(() => {\n if (!enabled || !contentId) {\n retryCountRef.current = 0\n return\n }\n\n const applyRects = (scrollRect: any, scrollInfo: any, contentRect: any) => {\n if (!scrollRect || !scrollInfo || !contentRect) return\n const scrollTop = scrollInfo.scrollTop ?? 0\n const scrollLeft = scrollInfo.scrollLeft ?? 0\n const value = isHorizontal\n ? Math.max(0, contentRect.left - scrollRect.left + scrollLeft)\n : Math.max(0, contentRect.top - scrollRect.top + scrollTop)\n if (startOffsetRef) startOffsetRef.current = value\n setMeasuredStartOffset((prevVal) => (Math.abs(prevVal - value) < 1 ? prevVal : value))\n }\n\n const measure = () => {\n const scrollEl = scrollElRef.current as any\n if (!scrollEl) return\n\n if (!scrollEl.id) {\n scrollEl.id = autoIdRef.current\n }\n const scrollViewId = scrollEl.id\n\n createSelectorQueryForRef(scrollElRef)\n .select(`#${scrollViewId}`)\n .boundingClientRect()\n .select(`#${scrollViewId}`)\n .scrollOffset()\n .exec((scrollRes) => {\n const scrollRect = scrollRes?.[0]\n const scrollInfo = scrollRes?.[1]\n if (!scrollRect || !scrollInfo) return\n createSelectorQueryForRef(contentWrapperRef)\n .select(`#${contentId}`)\n .boundingClientRect()\n .exec((contentRes) => {\n const contentRect = contentRes?.[0]\n applyRects(scrollRect, scrollInfo, contentRect)\n })\n })\n }\n\n const scrollEl = scrollElRef.current\n if (!scrollEl) {\n if (retryCountRef.current < MAX_RETRY) {\n retryCountRef.current += 1\n const id = setTimeout(() => setRetryTrigger((t) => t + 1), RETRY_DELAY)\n return () => clearTimeout(id)\n }\n return\n }\n retryCountRef.current = 0\n measure()\n scrollEl.addEventListener('scroll', measure)\n const interval = setInterval(measure, MEASURE_INTERVAL)\n return () => {\n scrollEl.removeEventListener('scroll', measure)\n clearInterval(interval)\n }\n }, [enabled, scrollElRef, contentId, contentWrapperRef, isHorizontal, retryTrigger])\n\n return measuredStartOffset\n}\n"],"names":[],"mappings":"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"}
@@ -27,7 +27,6 @@ export interface ListRefresherConfig {
27
27
  };
28
28
  }) => void;
29
29
  }
30
- /** 默认刷新层高度(对齐 Dynamic),无自定义 children 时使用 */
31
30
  export declare const DEFAULT_REFRESHER_HEIGHT = 50;
32
31
  /** 下拉刷新状态枚举(对齐微信小程序 RefreshStatus) */
33
32
  export declare const enum RefreshStatus {
@@ -65,10 +64,17 @@ interface UseRefresherReturn {
65
64
  };
66
65
  addImperativeTouchListeners?: (el: HTMLElement) => () => void;
67
66
  renderRefresherContent: () => React.ReactNode | null;
67
+ /**
68
+ * 刷新区几何高度:H5 等非小程序原生路径下与 List 绝对层 height / translateY 及本 hook 内下拉收尾动画一致。
69
+ * 小程序原生 refresher 由 ScrollView 负责展示与布局,List 侧槽位样式不依赖此字段。
70
+ */
71
+ slotHeight: number;
68
72
  }
69
73
  export declare function useRefresher(config: ListRefresherConfig | null,
70
74
  /** 列表逻辑顶部对应的 scrollTop,用于触顶判断;H5「顶栏悬浮+只滚列表」时传 0,imperative 内以 DOM scrollTop 为准 */
71
75
  scrollTopAtLogicalTop: number,
72
76
  /** scrollElement 模式下可选:下拉起始点须在 List 内才触发刷新;未传时默认 true(沿用原逻辑) */
73
- getIsTouchInListArea?: (ev: TouchEvent) => boolean): UseRefresherReturn;
77
+ getIsTouchInListArea?: (ev: TouchEvent) => boolean,
78
+ /** H5 等:开启刷新区时是否测量自定义内容高度(与 List 侧 isH5 && refresher 一致) */
79
+ enableH5SlotMeasure?: boolean): UseRefresherReturn;
74
80
  export {};
@@ -1,41 +1,65 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { Slot, View } from '@tarojs/components';
3
- import React, { useState, useRef, useCallback } from 'react';
3
+ import React, { useState, useRef, useCallback, useMemo, useLayoutEffect } from 'react';
4
4
  import { supportsNativeRefresher } from '../utils.js';
5
5
 
6
6
  /**
7
- * useRefresher - 平台适配的下拉刷新
7
+ * 下拉刷新(List 内部)
8
8
  *
9
- * - 小程序:weapp / jd / tt 使用 ScrollView 原生 refresher-*
10
- * - H5:对齐 taro-components-react pull-down-refresh(2.x 同源)逻辑:触顶 + 增量拖拽 + 阻尼 + 释放保持高度再回弹
9
+ * - 小程序:ScrollView 原生 refresher-*;业务 Promise reject 时补发 Failed(4)(原生不派发)
10
+ * - H5:触顶 + touch 拖拽;最大行程 / 阻尼停拉线为视口高的 0.9 / 0.8(innerHeight);收尾仅依赖 onRefresherRefresh 的 Promise
11
+ * - 动画时间戳用 nowMs():小程序部分环境无 performance,避免抛错中断回弹
12
+ * - rafRef 与受控 refresherTriggered→false 的 effect 共用:effect 内 cancel 可能打断 H5 成功回弹,须在 effect 动画结束处释放 refreshingLockRef
13
+ * - H5 reject:onRefresherStatusChange 顺序 Failed(4) → Completed(3) → Idle(0)
14
+ * - emitStatusChange:仅 status 变化时回调(同 status 不重复)
11
15
  */
12
16
  const BOUNCE_MS = 300;
13
17
  const AT_TOP_THRESHOLD = 3;
14
- /** 最大下拉距离(px) */
15
- const DISTANCE_Y_MAX_LIMIT = 150;
16
- /** 阻尼系数:超过此值不再累加位移,与 2.x default damping 一致 */
17
- const DAMPING = 100;
18
- /** 角度上限(度),小于此值才视为下拉意图 */
18
+ const H5_PULL_DISTANCE_MAX_VH = 0.9;
19
+ const H5_DAMPING_LIMIT_VH = 0.8;
20
+ const VIEWPORT_HEIGHT_FALLBACK = 800;
19
21
  const DEG_LIMIT = 40;
20
- /** 默认刷新层高度(对齐 Dynamic),无自定义 children 时使用 */
21
22
  const DEFAULT_REFRESHER_HEIGHT = 50;
22
- /**
23
- * 单次位移的阻尼(与 2.x PullDownRefresh.damping 一致)
24
- * ratio = 已拖拽总距离 / 屏幕高度,damped = diff * (1 - ratio) * 0.6;且当已超过 DAMPING 时不再加
25
- */
26
- function dampIncrement(diff, totalMove, currentPull, screenHeight) {
23
+ function resolveTaroMeasureElement(ref) {
24
+ var _a;
25
+ const n = ref.current;
26
+ if (!n)
27
+ return null;
28
+ if (typeof Element !== 'undefined' && n instanceof Element)
29
+ return n;
30
+ const el = (_a = n.$el) !== null && _a !== void 0 ? _a : n;
31
+ if (el && typeof Element !== 'undefined' && el instanceof Element)
32
+ return el;
33
+ return null;
34
+ }
35
+ function nowMs() {
36
+ if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
37
+ return performance.now();
38
+ }
39
+ return Date.now();
40
+ }
41
+ function getViewportHeightPx() {
42
+ if (typeof window === 'undefined' || !Number.isFinite(window.innerHeight) || window.innerHeight <= 0) {
43
+ return VIEWPORT_HEIGHT_FALLBACK;
44
+ }
45
+ return window.innerHeight;
46
+ }
47
+ /** 阻尼增量:ratio=总下拉位移/视口高;超过 dampingLimit 后不再累加 */
48
+ function dampIncrement(diff, totalMove, currentPull, viewportHeight, dampingLimit) {
27
49
  if (diff <= 0)
28
50
  return 0;
29
- if (currentPull >= DAMPING)
51
+ if (currentPull >= dampingLimit)
30
52
  return 0;
31
- const ratio = Math.min(totalMove / screenHeight, 1);
53
+ const ratio = Math.min(totalMove / viewportHeight, 1);
32
54
  return diff * (1 - ratio) * 0.6;
33
55
  }
34
56
  function useRefresher(config,
35
57
  /** 列表逻辑顶部对应的 scrollTop,用于触顶判断;H5「顶栏悬浮+只滚列表」时传 0,imperative 内以 DOM scrollTop 为准 */
36
58
  scrollTopAtLogicalTop,
37
59
  /** scrollElement 模式下可选:下拉起始点须在 List 内才触发刷新;未传时默认 true(沿用原逻辑) */
38
- getIsTouchInListArea) {
60
+ getIsTouchInListArea,
61
+ /** H5 等:开启刷新区时是否测量自定义内容高度(与 List 侧 isH5 && refresher 一致) */
62
+ enableH5SlotMeasure) {
39
63
  var _a, _b, _c, _d, _e, _f, _g;
40
64
  const [internalRefreshing, setInternalRefreshing] = useState(false);
41
65
  const [pullDistance, setPullDistance] = useState(0);
@@ -61,7 +85,6 @@ getIsTouchInListArea) {
61
85
  thresholdRef.current = (_c = config === null || config === void 0 ? void 0 : config.refresherThreshold) !== null && _c !== void 0 ? _c : 45;
62
86
  const configRef = useRef(config);
63
87
  configRef.current = config;
64
- /** H5:触发 onRefresherStatusChange 回调(仅状态变化时触发) */
65
88
  const emitStatusChangeRef = useRef((status, dy) => {
66
89
  var _a, _b;
67
90
  if (refreshStatusRef.current !== status) {
@@ -78,9 +101,50 @@ getIsTouchInListArea) {
78
101
  topThresholdPxRef.current = topThresholdPx;
79
102
  const getIsTouchInListAreaRef = useRef(getIsTouchInListArea);
80
103
  getIsTouchInListAreaRef.current = getIsTouchInListArea;
81
- // ========================================
82
- // 小程序:原生 refresher-* API
83
- // ========================================
104
+ const [measuredSlotHeight, setMeasuredSlotHeight] = useState(0);
105
+ const h5MeasureRef = useRef(null);
106
+ const setH5MeasureRef = useCallback((el) => {
107
+ h5MeasureRef.current = el;
108
+ }, []);
109
+ const hasCustomRefresherChildren = (config === null || config === void 0 ? void 0 : config.children) != null;
110
+ const slotHeight = useMemo(() => {
111
+ if (!config || config.refresherEnabled === false)
112
+ return DEFAULT_REFRESHER_HEIGHT;
113
+ if (!hasCustomRefresherChildren)
114
+ return DEFAULT_REFRESHER_HEIGHT;
115
+ if (supportsNativeRefresher)
116
+ return DEFAULT_REFRESHER_HEIGHT;
117
+ if (!enableH5SlotMeasure)
118
+ return DEFAULT_REFRESHER_HEIGHT;
119
+ return measuredSlotHeight > 0 ? measuredSlotHeight : DEFAULT_REFRESHER_HEIGHT;
120
+ }, [
121
+ config,
122
+ hasCustomRefresherChildren,
123
+ measuredSlotHeight,
124
+ enableH5SlotMeasure,
125
+ ]);
126
+ const slotHeightRef = useRef(slotHeight);
127
+ slotHeightRef.current = slotHeight;
128
+ useLayoutEffect(() => {
129
+ if (!enableH5SlotMeasure) {
130
+ setMeasuredSlotHeight(0);
131
+ return;
132
+ }
133
+ if (!hasCustomRefresherChildren)
134
+ return;
135
+ const node = resolveTaroMeasureElement(h5MeasureRef);
136
+ if (!node || typeof ResizeObserver === 'undefined')
137
+ return;
138
+ const ro = new ResizeObserver((entries) => {
139
+ const entry = entries[0];
140
+ if (!entry)
141
+ return;
142
+ const h = Math.max(1, Math.ceil(entry.contentRect.height));
143
+ setMeasuredSlotHeight(prev => (prev === h ? prev : h));
144
+ });
145
+ ro.observe(node);
146
+ return () => ro.disconnect();
147
+ }, [enableH5SlotMeasure, hasCustomRefresherChildren, config === null || config === void 0 ? void 0 : config.children]);
84
148
  const scrollViewRefresherProps = config && supportsNativeRefresher ? {
85
149
  refresherEnabled: (_d = config.refresherEnabled) !== null && _d !== void 0 ? _d : true,
86
150
  refresherThreshold: (_e = config.refresherThreshold) !== null && _e !== void 0 ? _e : 45,
@@ -94,12 +158,16 @@ getIsTouchInListArea) {
94
158
  (_a = config.onRefresherPulling) === null || _a === void 0 ? void 0 : _a.call(config, { detail: { deltaY: (_c = (_b = e.detail) === null || _b === void 0 ? void 0 : _b.deltaY) !== null && _c !== void 0 ? _c : 0 } });
95
159
  },
96
160
  onRefresherRefresh: async () => {
97
- var _a;
161
+ var _a, _b;
98
162
  if (!isControlled)
99
163
  setInternalRefreshing(true);
100
164
  try {
101
165
  await ((_a = config.onRefresherRefresh) === null || _a === void 0 ? void 0 : _a.call(config));
102
166
  }
167
+ catch (_c) {
168
+ // 原生不派发 Failed(4),补发便于与 H5 一致
169
+ (_b = config.onRefresherStatusChange) === null || _b === void 0 ? void 0 : _b.call(config, { detail: { status: 4 /* RefreshStatus.Failed */, dy: 0 } });
170
+ }
103
171
  finally {
104
172
  if (!isControlled)
105
173
  setInternalRefreshing(false);
@@ -115,11 +183,7 @@ getIsTouchInListArea) {
115
183
  (_a = config.onRefresherStatusChange) === null || _a === void 0 ? void 0 : _a.call(config, { detail: { status: (_b = e.detail) === null || _b === void 0 ? void 0 : _b.status, dy: (_c = e.detail) === null || _c === void 0 ? void 0 : _c.dy } });
116
184
  },
117
185
  } : {};
118
- // ========================================
119
- // H5:单一结构 + 原生 touch 监听(passive: false);refresherEnabled=false 时不挂
120
- // ========================================
121
186
  const addImperativeTouchListeners = React.useCallback((el) => {
122
- var _a, _b;
123
187
  if (supportsNativeRefresher || !configRef.current || !h5RefresherEnabled)
124
188
  return () => { };
125
189
  const scrollEl = el;
@@ -134,7 +198,6 @@ getIsTouchInListArea) {
134
198
  /** 用于 onTouchEnd 判断是否触发刷新;刷新完成后必须置 0,否则下次点击(无 touchMove)会误用上次的 lastPull 再次触发 */
135
199
  const lastPullRef = { current: 0 };
136
200
  const pullAtReleaseRef = { current: 0 };
137
- const screenHeight = typeof window !== 'undefined' ? (_b = (_a = window.screen) === null || _a === void 0 ? void 0 : _a.height) !== null && _b !== void 0 ? _b : 600 : 600;
138
201
  const setPull = (v) => {
139
202
  lastPull = v;
140
203
  lastPullRef.current = v;
@@ -143,9 +206,9 @@ getIsTouchInListArea) {
143
206
  const runBounceBack = (fromValue, onComplete) => {
144
207
  if (rafRef.current != null)
145
208
  cancelAnimationFrame(rafRef.current);
146
- const startTime = performance.now();
209
+ const startTime = nowMs();
147
210
  const animate = () => {
148
- const t = Math.min((performance.now() - startTime) / BOUNCE_MS, 1);
211
+ const t = Math.min((nowMs() - startTime) / BOUNCE_MS, 1);
149
212
  const ease = 1 - Math.pow(1 - t, 3);
150
213
  const v = fromValue * (1 - ease);
151
214
  setPullDistanceRef.current(v);
@@ -156,7 +219,6 @@ getIsTouchInListArea) {
156
219
  }
157
220
  else {
158
221
  setPullDistanceRef.current(0);
159
- // 修复闭包问题:使用 ref 获取最新值
160
222
  if (!isControlledRef.current)
161
223
  setInternalRefreshingRef.current(false);
162
224
  rafRef.current = null;
@@ -169,10 +231,10 @@ getIsTouchInListArea) {
169
231
  const runBounceBackAbort = (fromValue) => {
170
232
  if (rafRef.current != null)
171
233
  cancelAnimationFrame(rafRef.current);
172
- const startTime = performance.now();
234
+ const startTime = nowMs();
173
235
  const animate = () => {
174
236
  var _a, _b;
175
- const t = Math.min((performance.now() - startTime) / BOUNCE_MS, 1);
237
+ const t = Math.min((nowMs() - startTime) / BOUNCE_MS, 1);
176
238
  const ease = 1 - Math.pow(1 - t, 3);
177
239
  const v = fromValue * (1 - ease);
178
240
  setPullDistanceRef.current(v);
@@ -186,19 +248,18 @@ getIsTouchInListArea) {
186
248
  lastPullRef.current = 0;
187
249
  lastPull = 0;
188
250
  (_b = (_a = configRef.current) === null || _a === void 0 ? void 0 : _a.onRefresherAbort) === null || _b === void 0 ? void 0 : _b.call(_a);
189
- // 状态变化:回到 Idle
190
251
  emitStatusChangeRef.current(0 /* RefreshStatus.Idle */, 0);
191
252
  }
192
253
  };
193
254
  rafRef.current = requestAnimationFrame(animate);
194
255
  };
195
- /** 先动画到 refresherHeight(加载中保持高度),再执行刷新,完成后回弹;与 2.x release 时 setContentStyle(distanceToRefresh+1) 一致 */
256
+ /** 收起到加载高度后再执行 onRefresherRefresh */
196
257
  const runBounceToLoading = (fromValue, toValue, onReach) => {
197
258
  if (rafRef.current != null)
198
259
  cancelAnimationFrame(rafRef.current);
199
- const startTime = performance.now();
260
+ const startTime = nowMs();
200
261
  const animate = () => {
201
- const t = Math.min((performance.now() - startTime) / BOUNCE_MS, 1);
262
+ const t = Math.min((nowMs() - startTime) / BOUNCE_MS, 1);
202
263
  const ease = 1 - Math.pow(1 - t, 3);
203
264
  setPullDistanceRef.current(fromValue + (toValue - fromValue) * ease);
204
265
  if (t < 1) {
@@ -292,8 +353,11 @@ getIsTouchInListArea) {
292
353
  return;
293
354
  }
294
355
  lastY.current = currentY;
295
- const damped = dampIncrement(dy, totalMove, lastPull, screenHeight);
296
- const next = Math.min(lastPull + damped, DISTANCE_Y_MAX_LIMIT);
356
+ const vh = getViewportHeightPx();
357
+ const maxPull = vh * H5_PULL_DISTANCE_MAX_VH;
358
+ const dampingLimit = vh * H5_DAMPING_LIMIT_VH;
359
+ const damped = dampIncrement(dy, totalMove, lastPull, vh, dampingLimit);
360
+ const next = Math.min(lastPull + damped, maxPull);
297
361
  setPull(next);
298
362
  (_b = (_a = configRef.current) === null || _a === void 0 ? void 0 : _a.onRefresherPulling) === null || _b === void 0 ? void 0 : _b.call(_a, { detail: { deltaY: next } });
299
363
  if (next >= thresholdRef.current) {
@@ -310,11 +374,10 @@ getIsTouchInListArea) {
310
374
  return;
311
375
  refreshingLockRef.current = true;
312
376
  const pullValue = lastPull;
313
- const height = DEFAULT_REFRESHER_HEIGHT;
377
+ const height = slotHeightRef.current;
314
378
  pullAtReleaseRef.current = pullValue;
315
379
  if (!isControlledRef.current)
316
380
  setInternalRefreshingRef.current(true);
317
- // 状态变化:Refreshing
318
381
  emitStatusChangeRef.current(2 /* RefreshStatus.Refreshing */, pullValue);
319
382
  runBounceToLoading(pullValue, height, () => {
320
383
  var _a, _b;
@@ -328,30 +391,26 @@ getIsTouchInListArea) {
328
391
  lastPull = 0;
329
392
  pullAtReleaseRef.current = 0;
330
393
  (_b = (_a = configRef.current) === null || _a === void 0 ? void 0 : _a.onRefresherRestore) === null || _b === void 0 ? void 0 : _b.call(_a);
331
- // 状态变化:回到 Idle
332
394
  emitStatusChangeRef.current(0 /* RefreshStatus.Idle */, 0);
333
395
  };
334
- const timeoutId = setTimeout(safeReset, BOUNCE_MS + 400);
335
396
  Promise.resolve((_b = (_a = configRef.current) === null || _a === void 0 ? void 0 : _a.onRefresherRefresh) === null || _b === void 0 ? void 0 : _b.call(_a))
336
397
  .then(() => {
337
- // 若已经通过 safeReset 提前结束刷新(例如超时),则不再执行回弹动画,避免二次「吐舌头」闪现
338
398
  if (!refreshingLockRef.current)
339
399
  return;
340
- // 状态变化:Completed
341
- emitStatusChangeRef.current(3 /* RefreshStatus.Completed */, DEFAULT_REFRESHER_HEIGHT);
342
- requestAnimationFrame(() => runBounceBack(DEFAULT_REFRESHER_HEIGHT, () => {
400
+ const slotH = slotHeightRef.current;
401
+ emitStatusChangeRef.current(3 /* RefreshStatus.Completed */, slotH);
402
+ requestAnimationFrame(() => runBounceBack(slotH, () => {
343
403
  var _a, _b;
344
- clearTimeout(timeoutId);
345
404
  refreshingLockRef.current = false;
346
405
  lastPullRef.current = 0;
347
406
  lastPull = 0;
348
407
  pullAtReleaseRef.current = 0;
349
408
  (_b = (_a = configRef.current) === null || _a === void 0 ? void 0 : _a.onRefresherRestore) === null || _b === void 0 ? void 0 : _b.call(_a);
350
- // 状态变化:回到 Idle
351
409
  emitStatusChangeRef.current(0 /* RefreshStatus.Idle */, 0);
352
410
  }));
353
411
  }, () => {
354
- clearTimeout(timeoutId);
412
+ emitStatusChangeRef.current(4 /* RefreshStatus.Failed */, pullValue);
413
+ emitStatusChangeRef.current(3 /* RefreshStatus.Completed */, slotHeightRef.current);
355
414
  safeReset();
356
415
  });
357
416
  });
@@ -423,7 +482,7 @@ getIsTouchInListArea) {
423
482
  const defaultText = showDefaultText
424
483
  ? (isRefreshing ? '加载中...' : pullDistance >= threshold ? '释放刷新' : '下拉刷新')
425
484
  : null;
426
- return (jsx(View, { style: {
485
+ return (jsx(View, { ref: !supportsNativeRefresher && hasCustomChildren ? setH5MeasureRef : undefined, style: {
427
486
  width: '100%',
428
487
  minHeight: hasCustomChildren ? undefined : DEFAULT_REFRESHER_HEIGHT,
429
488
  height: hasCustomChildren ? 'auto' : DEFAULT_REFRESHER_HEIGHT,
@@ -433,19 +492,19 @@ getIsTouchInListArea) {
433
492
  alignItems: 'center',
434
493
  background,
435
494
  }, children: hasCustomChildren ? config.children : (defaultText ? jsx(View, { style: textColor ? { color: textColor } : undefined, children: defaultText }) : null) }));
436
- }, [config, pullDistance, isRefreshing]);
437
- // 受控 refresherTriggered:设为 true 时立即展示顶部加载指示器(对齐小程序:无需下拉即显示、固定在顶部)
495
+ }, [config, pullDistance, isRefreshing, setH5MeasureRef]);
496
+ /** 受控 refresherTriggered=true:无下拉也显示加载条 */
438
497
  React.useEffect(() => {
439
498
  if (!isControlled || !config || config.refresherTriggered !== true)
440
499
  return;
441
- if (pullDistanceRef.current >= DEFAULT_REFRESHER_HEIGHT)
500
+ const h = slotHeightRef.current;
501
+ if (pullDistanceRef.current >= h)
442
502
  return;
443
- setPullDistanceRef.current(DEFAULT_REFRESHER_HEIGHT);
444
- pullDistanceRef.current = DEFAULT_REFRESHER_HEIGHT;
445
- // 触发 Refreshing 状态变化
446
- emitStatusChangeRef.current(2 /* RefreshStatus.Refreshing */, DEFAULT_REFRESHER_HEIGHT);
447
- }, [isControlled, config === null || config === void 0 ? void 0 : config.refresherTriggered]);
448
- // 受控 refresherTriggered:父组件设为 false 时同步回弹并清零
503
+ setPullDistanceRef.current(h);
504
+ pullDistanceRef.current = h;
505
+ emitStatusChangeRef.current(2 /* RefreshStatus.Refreshing */, h);
506
+ }, [isControlled, config === null || config === void 0 ? void 0 : config.refresherTriggered, slotHeight]);
507
+ /** 受控 refresherTriggered=false:回弹清零;与 touch 共用 rafRef,cancel 时须在此释放 refreshingLockRef */
449
508
  const prevTriggeredRef = useRef(config === null || config === void 0 ? void 0 : config.refresherTriggered);
450
509
  React.useEffect(() => {
451
510
  if (!isControlled || !config || config.refresherTriggered !== false) {
@@ -457,13 +516,12 @@ getIsTouchInListArea) {
457
516
  if (prev === true && (pullDistanceRef.current > 0 || isRefreshingRef.current)) {
458
517
  if (rafRef.current != null)
459
518
  cancelAnimationFrame(rafRef.current);
460
- const from = pullDistanceRef.current > 0 ? pullDistanceRef.current : DEFAULT_REFRESHER_HEIGHT;
461
- // 触发 Completed 状态变化
519
+ const from = pullDistanceRef.current > 0 ? pullDistanceRef.current : slotHeightRef.current;
462
520
  emitStatusChangeRef.current(3 /* RefreshStatus.Completed */, from);
463
- const startTime = performance.now();
521
+ const startTime = nowMs();
464
522
  const animate = () => {
465
523
  var _a;
466
- const t = Math.min((performance.now() - startTime) / BOUNCE_MS, 1);
524
+ const t = Math.min((nowMs() - startTime) / BOUNCE_MS, 1);
467
525
  const ease = 1 - Math.pow(1 - t, 3);
468
526
  const v = from * (1 - ease);
469
527
  setPullDistanceRef.current(v);
@@ -473,18 +531,17 @@ getIsTouchInListArea) {
473
531
  else {
474
532
  setPullDistanceRef.current(0);
475
533
  pullDistanceRef.current = 0;
476
- // 修复闭包问题:使用 ref 获取最新值
477
534
  if (!isControlledRef.current)
478
535
  setInternalRefreshingRef.current(false);
479
536
  rafRef.current = null;
537
+ refreshingLockRef.current = false;
480
538
  (_a = config.onRefresherRestore) === null || _a === void 0 ? void 0 : _a.call(config);
481
- // 触发 Idle 状态变化
482
539
  emitStatusChangeRef.current(0 /* RefreshStatus.Idle */, 0);
483
540
  }
484
541
  };
485
542
  rafRef.current = requestAnimationFrame(animate);
486
543
  }
487
- }, [isControlled, config === null || config === void 0 ? void 0 : config.refresherTriggered]);
544
+ }, [isControlled, config === null || config === void 0 ? void 0 : config.refresherTriggered, slotHeight]);
488
545
  return {
489
546
  scrollViewRefresherProps,
490
547
  scrollViewRefresherHandlers,
@@ -496,6 +553,7 @@ getIsTouchInListArea) {
496
553
  },
497
554
  addImperativeTouchListeners: !supportsNativeRefresher && config && h5RefresherEnabled ? addImperativeTouchListeners : undefined,
498
555
  renderRefresherContent,
556
+ slotHeight,
499
557
  };
500
558
  }
501
559