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

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 (73) hide show
  1. package/dist/components/index.js +1 -0
  2. package/dist/components/index.js.map +1 -1
  3. package/dist/components/list/hooks/useItemSizeCache.d.ts +1 -13
  4. package/dist/components/list/hooks/useItemSizeCache.js +6 -50
  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 +14 -0
  13. package/dist/components/list/hooks/useListScrollElementAttachWeapp.js +134 -0
  14. package/dist/components/list/hooks/useListScrollElementAttachWeapp.js.map +1 -0
  15. package/dist/components/list/hooks/useMeasureStartOffset.d.ts +12 -0
  16. package/dist/components/list/hooks/useMeasureStartOffset.js +84 -0
  17. package/dist/components/list/hooks/useMeasureStartOffset.js.map +1 -0
  18. package/dist/components/list/hooks/useMeasureStartOffsetWeapp.d.ts +14 -0
  19. package/dist/components/list/hooks/useMeasureStartOffsetWeapp.js +82 -0
  20. package/dist/components/list/hooks/useMeasureStartOffsetWeapp.js.map +1 -0
  21. package/dist/components/list/hooks/useRefresher.d.ts +10 -2
  22. package/dist/components/list/hooks/useRefresher.js +150 -69
  23. package/dist/components/list/hooks/useRefresher.js.map +1 -1
  24. package/dist/components/list/hooks/useResizeObserver.d.ts +1 -0
  25. package/dist/components/list/hooks/useResizeObserver.js +37 -31
  26. package/dist/components/list/hooks/useResizeObserver.js.map +1 -1
  27. package/dist/components/list/hooks/useScrollCorrection.d.ts +3 -2
  28. package/dist/components/list/hooks/useScrollCorrection.js +7 -5
  29. package/dist/components/list/hooks/useScrollCorrection.js.map +1 -1
  30. package/dist/components/list/hooks/useScrollParentAutoFind.d.ts +20 -0
  31. package/dist/components/list/hooks/useScrollParentAutoFind.js +81 -0
  32. package/dist/components/list/hooks/useScrollParentAutoFind.js.map +1 -0
  33. package/dist/components/list/index.d.ts +16 -6
  34. package/dist/components/list/index.js +435 -498
  35. package/dist/components/list/index.js.map +1 -1
  36. package/dist/components/list/utils.d.ts +5 -0
  37. package/dist/components/list/utils.js +17 -1
  38. package/dist/components/list/utils.js.map +1 -1
  39. package/dist/components/virtual-list/vue/list.d.ts +10 -10
  40. package/dist/components/virtual-waterfall/vue/waterfall.d.ts +10 -10
  41. package/dist/components/water-flow/flow-item.js +6 -4
  42. package/dist/components/water-flow/flow-item.js.map +1 -1
  43. package/dist/components/water-flow/flow-section.js +1 -1
  44. package/dist/components/water-flow/flow-section.js.map +1 -1
  45. package/dist/components/water-flow/index.d.ts +1 -1
  46. package/dist/components/water-flow/interface.d.ts +18 -2
  47. package/dist/components/water-flow/root.d.ts +35 -4
  48. package/dist/components/water-flow/root.js +114 -42
  49. package/dist/components/water-flow/root.js.map +1 -1
  50. package/dist/components/water-flow/section.d.ts +7 -1
  51. package/dist/components/water-flow/section.js +54 -9
  52. package/dist/components/water-flow/section.js.map +1 -1
  53. package/dist/components/water-flow/utils.d.ts +4 -0
  54. package/dist/components/water-flow/utils.js +5 -1
  55. package/dist/components/water-flow/utils.js.map +1 -1
  56. package/dist/components/water-flow/water-flow-node-cache.d.ts +24 -0
  57. package/dist/components/water-flow/water-flow-node-cache.js +161 -0
  58. package/dist/components/water-flow/water-flow-node-cache.js.map +1 -0
  59. package/dist/components/water-flow/water-flow.d.ts +2 -3
  60. package/dist/components/water-flow/water-flow.js +286 -31
  61. package/dist/components/water-flow/water-flow.js.map +1 -1
  62. package/dist/index.js +2 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/utils/index.d.ts +1 -0
  65. package/dist/utils/index.js +1 -0
  66. package/dist/utils/index.js.map +1 -1
  67. package/dist/utils/scrollElementContext.d.ts +15 -0
  68. package/dist/utils/scrollElementContext.js +14 -0
  69. package/dist/utils/scrollElementContext.js.map +1 -0
  70. package/dist/utils/scrollParent.d.ts +33 -0
  71. package/dist/utils/scrollParent.js +88 -0
  72. package/dist/utils/scrollParent.js.map +1 -0
  73. package/package.json +9 -8
@@ -1,39 +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
- scrollTopAtLogicalTop) {
58
+ scrollTopAtLogicalTop,
59
+ /** scrollElement 模式下可选:下拉起始点须在 List 内才触发刷新;未传时默认 true(沿用原逻辑) */
60
+ getIsTouchInListArea,
61
+ /** H5 等:开启刷新区时是否测量自定义内容高度(与 List 侧 isH5 && refresher 一致) */
62
+ enableH5SlotMeasure) {
37
63
  var _a, _b, _c, _d, _e, _f, _g;
38
64
  const [internalRefreshing, setInternalRefreshing] = useState(false);
39
65
  const [pullDistance, setPullDistance] = useState(0);
@@ -59,7 +85,6 @@ scrollTopAtLogicalTop) {
59
85
  thresholdRef.current = (_c = config === null || config === void 0 ? void 0 : config.refresherThreshold) !== null && _c !== void 0 ? _c : 45;
60
86
  const configRef = useRef(config);
61
87
  configRef.current = config;
62
- /** H5:触发 onRefresherStatusChange 回调(仅状态变化时触发) */
63
88
  const emitStatusChangeRef = useRef((status, dy) => {
64
89
  var _a, _b;
65
90
  if (refreshStatusRef.current !== status) {
@@ -74,9 +99,52 @@ scrollTopAtLogicalTop) {
74
99
  const topThresholdPx = scrollTopWhenAtTop + AT_TOP_THRESHOLD;
75
100
  const topThresholdPxRef = useRef(topThresholdPx);
76
101
  topThresholdPxRef.current = topThresholdPx;
77
- // ========================================
78
- // 小程序:原生 refresher-* API
79
- // ========================================
102
+ const getIsTouchInListAreaRef = useRef(getIsTouchInListArea);
103
+ getIsTouchInListAreaRef.current = getIsTouchInListArea;
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]);
80
148
  const scrollViewRefresherProps = config && supportsNativeRefresher ? {
81
149
  refresherEnabled: (_d = config.refresherEnabled) !== null && _d !== void 0 ? _d : true,
82
150
  refresherThreshold: (_e = config.refresherThreshold) !== null && _e !== void 0 ? _e : 45,
@@ -90,12 +158,16 @@ scrollTopAtLogicalTop) {
90
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 } });
91
159
  },
92
160
  onRefresherRefresh: async () => {
93
- var _a;
161
+ var _a, _b;
94
162
  if (!isControlled)
95
163
  setInternalRefreshing(true);
96
164
  try {
97
165
  await ((_a = config.onRefresherRefresh) === null || _a === void 0 ? void 0 : _a.call(config));
98
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
+ }
99
171
  finally {
100
172
  if (!isControlled)
101
173
  setInternalRefreshing(false);
@@ -111,11 +183,7 @@ scrollTopAtLogicalTop) {
111
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 } });
112
184
  },
113
185
  } : {};
114
- // ========================================
115
- // H5:单一结构 + 原生 touch 监听(passive: false);refresherEnabled=false 时不挂
116
- // ========================================
117
186
  const addImperativeTouchListeners = React.useCallback((el) => {
118
- var _a, _b;
119
187
  if (supportsNativeRefresher || !configRef.current || !h5RefresherEnabled)
120
188
  return () => { };
121
189
  const scrollEl = el;
@@ -123,12 +191,13 @@ scrollTopAtLogicalTop) {
123
191
  const startX = { current: 0 };
124
192
  const lastY = { current: 0 };
125
193
  let touchStartedAtTop = false;
194
+ /** 下拉起始点是否在 List 内(scrollElement 模式);未传 getIsTouchInListArea 时恒为 true */
195
+ let touchStartedInListArea = true;
126
196
  let dragOnEdge = false;
127
197
  let lastPull = 0;
128
198
  /** 用于 onTouchEnd 判断是否触发刷新;刷新完成后必须置 0,否则下次点击(无 touchMove)会误用上次的 lastPull 再次触发 */
129
199
  const lastPullRef = { current: 0 };
130
200
  const pullAtReleaseRef = { current: 0 };
131
- const screenHeight = typeof window !== 'undefined' ? (_b = (_a = window.screen) === null || _a === void 0 ? void 0 : _a.height) !== null && _b !== void 0 ? _b : 600 : 600;
132
201
  const setPull = (v) => {
133
202
  lastPull = v;
134
203
  lastPullRef.current = v;
@@ -137,9 +206,9 @@ scrollTopAtLogicalTop) {
137
206
  const runBounceBack = (fromValue, onComplete) => {
138
207
  if (rafRef.current != null)
139
208
  cancelAnimationFrame(rafRef.current);
140
- const startTime = performance.now();
209
+ const startTime = nowMs();
141
210
  const animate = () => {
142
- const t = Math.min((performance.now() - startTime) / BOUNCE_MS, 1);
211
+ const t = Math.min((nowMs() - startTime) / BOUNCE_MS, 1);
143
212
  const ease = 1 - Math.pow(1 - t, 3);
144
213
  const v = fromValue * (1 - ease);
145
214
  setPullDistanceRef.current(v);
@@ -150,7 +219,6 @@ scrollTopAtLogicalTop) {
150
219
  }
151
220
  else {
152
221
  setPullDistanceRef.current(0);
153
- // 修复闭包问题:使用 ref 获取最新值
154
222
  if (!isControlledRef.current)
155
223
  setInternalRefreshingRef.current(false);
156
224
  rafRef.current = null;
@@ -163,10 +231,10 @@ scrollTopAtLogicalTop) {
163
231
  const runBounceBackAbort = (fromValue) => {
164
232
  if (rafRef.current != null)
165
233
  cancelAnimationFrame(rafRef.current);
166
- const startTime = performance.now();
234
+ const startTime = nowMs();
167
235
  const animate = () => {
168
236
  var _a, _b;
169
- const t = Math.min((performance.now() - startTime) / BOUNCE_MS, 1);
237
+ const t = Math.min((nowMs() - startTime) / BOUNCE_MS, 1);
170
238
  const ease = 1 - Math.pow(1 - t, 3);
171
239
  const v = fromValue * (1 - ease);
172
240
  setPullDistanceRef.current(v);
@@ -180,19 +248,18 @@ scrollTopAtLogicalTop) {
180
248
  lastPullRef.current = 0;
181
249
  lastPull = 0;
182
250
  (_b = (_a = configRef.current) === null || _a === void 0 ? void 0 : _a.onRefresherAbort) === null || _b === void 0 ? void 0 : _b.call(_a);
183
- // 状态变化:回到 Idle
184
251
  emitStatusChangeRef.current(0 /* RefreshStatus.Idle */, 0);
185
252
  }
186
253
  };
187
254
  rafRef.current = requestAnimationFrame(animate);
188
255
  };
189
- /** 先动画到 refresherHeight(加载中保持高度),再执行刷新,完成后回弹;与 2.x release 时 setContentStyle(distanceToRefresh+1) 一致 */
256
+ /** 收起到加载高度后再执行 onRefresherRefresh */
190
257
  const runBounceToLoading = (fromValue, toValue, onReach) => {
191
258
  if (rafRef.current != null)
192
259
  cancelAnimationFrame(rafRef.current);
193
- const startTime = performance.now();
260
+ const startTime = nowMs();
194
261
  const animate = () => {
195
- const t = Math.min((performance.now() - startTime) / BOUNCE_MS, 1);
262
+ const t = Math.min((nowMs() - startTime) / BOUNCE_MS, 1);
196
263
  const ease = 1 - Math.pow(1 - t, 3);
197
264
  setPullDistanceRef.current(fromValue + (toValue - fromValue) * ease);
198
265
  if (t < 1) {
@@ -208,6 +275,7 @@ scrollTopAtLogicalTop) {
208
275
  };
209
276
  const isEdge = () => { var _a; return ((_a = scrollEl.scrollTop) !== null && _a !== void 0 ? _a : 0) <= topThresholdPxRef.current; };
210
277
  const onTouchStart = (e) => {
278
+ var _a, _b;
211
279
  if (refreshingLockRef.current || isRefreshingRef.current)
212
280
  return;
213
281
  if (rafRef.current != null) {
@@ -216,6 +284,7 @@ scrollTopAtLogicalTop) {
216
284
  }
217
285
  lastPull = lastPullRef.current;
218
286
  touchStartedAtTop = isEdge();
287
+ touchStartedInListArea = (_b = (_a = getIsTouchInListAreaRef.current) === null || _a === void 0 ? void 0 : _a.call(getIsTouchInListAreaRef, e)) !== null && _b !== void 0 ? _b : true;
219
288
  const t0 = e.touches[0];
220
289
  if (t0) {
221
290
  startY.current = t0.clientY;
@@ -227,16 +296,29 @@ scrollTopAtLogicalTop) {
227
296
  var _a, _b, _c, _d;
228
297
  if (refreshingLockRef.current || isRefreshingRef.current)
229
298
  return;
299
+ if (!touchStartedInListArea)
300
+ return;
230
301
  const touch = ev.touches[0];
231
302
  if (!touch)
232
303
  return;
233
304
  const currentY = touch.clientY;
234
305
  const currentX = touch.clientX;
235
- if (!touchStartedAtTop || !isEdge()) {
306
+ if (!isEdge()) {
236
307
  setPull(0);
237
308
  dragOnEdge = false;
238
309
  return;
239
310
  }
311
+ // 到顶后:若 touch 开始时不在顶部,在首次下拉(dy>0)时「采纳」该手势,使同一手势内滚到顶后继续下拉可触发刷新
312
+ if (!touchStartedAtTop && !dragOnEdge) {
313
+ const dyCheck = currentY - lastY.current;
314
+ if (dyCheck <= 0)
315
+ return;
316
+ touchStartedAtTop = true;
317
+ // 重置起点,避免采纳时 dy 过大导致刷新层瞬间拉满
318
+ startY.current = currentY;
319
+ startX.current = currentX;
320
+ lastY.current = currentY;
321
+ }
240
322
  const dy = currentY - lastY.current;
241
323
  // 还没进入下拉拖拽阶段(dragOnEdge=false)时,先看第一下是往上还是往下:
242
324
  // - 第一笔就是往上滑(dy<=0):视为正常滚动,直接交给原生 Scroll,不拦截、不启动下拉逻辑
@@ -271,8 +353,11 @@ scrollTopAtLogicalTop) {
271
353
  return;
272
354
  }
273
355
  lastY.current = currentY;
274
- const damped = dampIncrement(dy, totalMove, lastPull, screenHeight);
275
- 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);
276
361
  setPull(next);
277
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 } });
278
363
  if (next >= thresholdRef.current) {
@@ -289,11 +374,10 @@ scrollTopAtLogicalTop) {
289
374
  return;
290
375
  refreshingLockRef.current = true;
291
376
  const pullValue = lastPull;
292
- const height = DEFAULT_REFRESHER_HEIGHT;
377
+ const height = slotHeightRef.current;
293
378
  pullAtReleaseRef.current = pullValue;
294
379
  if (!isControlledRef.current)
295
380
  setInternalRefreshingRef.current(true);
296
- // 状态变化:Refreshing
297
381
  emitStatusChangeRef.current(2 /* RefreshStatus.Refreshing */, pullValue);
298
382
  runBounceToLoading(pullValue, height, () => {
299
383
  var _a, _b;
@@ -307,30 +391,26 @@ scrollTopAtLogicalTop) {
307
391
  lastPull = 0;
308
392
  pullAtReleaseRef.current = 0;
309
393
  (_b = (_a = configRef.current) === null || _a === void 0 ? void 0 : _a.onRefresherRestore) === null || _b === void 0 ? void 0 : _b.call(_a);
310
- // 状态变化:回到 Idle
311
394
  emitStatusChangeRef.current(0 /* RefreshStatus.Idle */, 0);
312
395
  };
313
- const timeoutId = setTimeout(safeReset, BOUNCE_MS + 400);
314
396
  Promise.resolve((_b = (_a = configRef.current) === null || _a === void 0 ? void 0 : _a.onRefresherRefresh) === null || _b === void 0 ? void 0 : _b.call(_a))
315
397
  .then(() => {
316
- // 若已经通过 safeReset 提前结束刷新(例如超时),则不再执行回弹动画,避免二次「吐舌头」闪现
317
398
  if (!refreshingLockRef.current)
318
399
  return;
319
- // 状态变化:Completed
320
- emitStatusChangeRef.current(3 /* RefreshStatus.Completed */, DEFAULT_REFRESHER_HEIGHT);
321
- requestAnimationFrame(() => runBounceBack(DEFAULT_REFRESHER_HEIGHT, () => {
400
+ const slotH = slotHeightRef.current;
401
+ emitStatusChangeRef.current(3 /* RefreshStatus.Completed */, slotH);
402
+ requestAnimationFrame(() => runBounceBack(slotH, () => {
322
403
  var _a, _b;
323
- clearTimeout(timeoutId);
324
404
  refreshingLockRef.current = false;
325
405
  lastPullRef.current = 0;
326
406
  lastPull = 0;
327
407
  pullAtReleaseRef.current = 0;
328
408
  (_b = (_a = configRef.current) === null || _a === void 0 ? void 0 : _a.onRefresherRestore) === null || _b === void 0 ? void 0 : _b.call(_a);
329
- // 状态变化:回到 Idle
330
409
  emitStatusChangeRef.current(0 /* RefreshStatus.Idle */, 0);
331
410
  }));
332
411
  }, () => {
333
- clearTimeout(timeoutId);
412
+ emitStatusChangeRef.current(4 /* RefreshStatus.Failed */, pullValue);
413
+ emitStatusChangeRef.current(3 /* RefreshStatus.Completed */, slotHeightRef.current);
334
414
  safeReset();
335
415
  });
336
416
  });
@@ -338,6 +418,8 @@ scrollTopAtLogicalTop) {
338
418
  const onTouchEnd = () => {
339
419
  if (refreshingLockRef.current)
340
420
  return;
421
+ if (!touchStartedInListArea)
422
+ return;
341
423
  dragOnEdge = false;
342
424
  const current = lastPullRef.current;
343
425
  if (current >= thresholdRef.current) {
@@ -400,7 +482,7 @@ scrollTopAtLogicalTop) {
400
482
  const defaultText = showDefaultText
401
483
  ? (isRefreshing ? '加载中...' : pullDistance >= threshold ? '释放刷新' : '下拉刷新')
402
484
  : null;
403
- return (jsx(View, { style: {
485
+ return (jsx(View, { ref: !supportsNativeRefresher && hasCustomChildren ? setH5MeasureRef : undefined, style: {
404
486
  width: '100%',
405
487
  minHeight: hasCustomChildren ? undefined : DEFAULT_REFRESHER_HEIGHT,
406
488
  height: hasCustomChildren ? 'auto' : DEFAULT_REFRESHER_HEIGHT,
@@ -409,20 +491,20 @@ scrollTopAtLogicalTop) {
409
491
  justifyContent: 'center',
410
492
  alignItems: 'center',
411
493
  background,
412
- }, "data-testid": "list-refresher", children: hasCustomChildren ? config.children : (defaultText ? jsx(View, { style: textColor ? { color: textColor } : undefined, children: defaultText }) : null) }));
413
- }, [config, pullDistance, isRefreshing]);
414
- // 受控 refresherTriggered:设为 true 时立即展示顶部加载指示器(对齐小程序:无需下拉即显示、固定在顶部)
494
+ }, children: hasCustomChildren ? config.children : (defaultText ? jsx(View, { style: textColor ? { color: textColor } : undefined, children: defaultText }) : null) }));
495
+ }, [config, pullDistance, isRefreshing, setH5MeasureRef]);
496
+ /** 受控 refresherTriggered=true:无下拉也显示加载条 */
415
497
  React.useEffect(() => {
416
498
  if (!isControlled || !config || config.refresherTriggered !== true)
417
499
  return;
418
- if (pullDistanceRef.current >= DEFAULT_REFRESHER_HEIGHT)
500
+ const h = slotHeightRef.current;
501
+ if (pullDistanceRef.current >= h)
419
502
  return;
420
- setPullDistanceRef.current(DEFAULT_REFRESHER_HEIGHT);
421
- pullDistanceRef.current = DEFAULT_REFRESHER_HEIGHT;
422
- // 触发 Refreshing 状态变化
423
- emitStatusChangeRef.current(2 /* RefreshStatus.Refreshing */, DEFAULT_REFRESHER_HEIGHT);
424
- }, [isControlled, config === null || config === void 0 ? void 0 : config.refresherTriggered]);
425
- // 受控 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 */
426
508
  const prevTriggeredRef = useRef(config === null || config === void 0 ? void 0 : config.refresherTriggered);
427
509
  React.useEffect(() => {
428
510
  if (!isControlled || !config || config.refresherTriggered !== false) {
@@ -434,13 +516,12 @@ scrollTopAtLogicalTop) {
434
516
  if (prev === true && (pullDistanceRef.current > 0 || isRefreshingRef.current)) {
435
517
  if (rafRef.current != null)
436
518
  cancelAnimationFrame(rafRef.current);
437
- const from = pullDistanceRef.current > 0 ? pullDistanceRef.current : DEFAULT_REFRESHER_HEIGHT;
438
- // 触发 Completed 状态变化
519
+ const from = pullDistanceRef.current > 0 ? pullDistanceRef.current : slotHeightRef.current;
439
520
  emitStatusChangeRef.current(3 /* RefreshStatus.Completed */, from);
440
- const startTime = performance.now();
521
+ const startTime = nowMs();
441
522
  const animate = () => {
442
523
  var _a;
443
- const t = Math.min((performance.now() - startTime) / BOUNCE_MS, 1);
524
+ const t = Math.min((nowMs() - startTime) / BOUNCE_MS, 1);
444
525
  const ease = 1 - Math.pow(1 - t, 3);
445
526
  const v = from * (1 - ease);
446
527
  setPullDistanceRef.current(v);
@@ -450,18 +531,17 @@ scrollTopAtLogicalTop) {
450
531
  else {
451
532
  setPullDistanceRef.current(0);
452
533
  pullDistanceRef.current = 0;
453
- // 修复闭包问题:使用 ref 获取最新值
454
534
  if (!isControlledRef.current)
455
535
  setInternalRefreshingRef.current(false);
456
536
  rafRef.current = null;
537
+ refreshingLockRef.current = false;
457
538
  (_a = config.onRefresherRestore) === null || _a === void 0 ? void 0 : _a.call(config);
458
- // 触发 Idle 状态变化
459
539
  emitStatusChangeRef.current(0 /* RefreshStatus.Idle */, 0);
460
540
  }
461
541
  };
462
542
  rafRef.current = requestAnimationFrame(animate);
463
543
  }
464
- }, [isControlled, config === null || config === void 0 ? void 0 : config.refresherTriggered]);
544
+ }, [isControlled, config === null || config === void 0 ? void 0 : config.refresherTriggered, slotHeight]);
465
545
  return {
466
546
  scrollViewRefresherProps,
467
547
  scrollViewRefresherHandlers,
@@ -473,6 +553,7 @@ scrollTopAtLogicalTop) {
473
553
  },
474
554
  addImperativeTouchListeners: !supportsNativeRefresher && config && h5RefresherEnabled ? addImperativeTouchListeners : undefined,
475
555
  renderRefresherContent,
556
+ slotHeight,
476
557
  };
477
558
  }
478
559