@tarojs/components-advanced 4.1.12-beta.0 → 4.1.12-beta.10
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.
- package/dist/components/index.js +2 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/list/NoMore.d.ts +30 -0
- package/dist/components/list/NoMore.js +10 -0
- package/dist/components/list/NoMore.js.map +1 -0
- package/dist/components/list/hooks/useItemSizeCache.d.ts +13 -0
- package/dist/components/list/hooks/useItemSizeCache.js +40 -0
- package/dist/components/list/hooks/useItemSizeCache.js.map +1 -0
- package/dist/components/list/hooks/useListNestedScroll.d.ts +18 -0
- package/dist/components/list/hooks/useListNestedScroll.js +61 -0
- package/dist/components/list/hooks/useListNestedScroll.js.map +1 -0
- package/dist/components/list/hooks/useListScrollElementAttach.d.ts +21 -0
- package/dist/components/list/hooks/useListScrollElementAttach.js +86 -0
- package/dist/components/list/hooks/useListScrollElementAttach.js.map +1 -0
- package/dist/components/list/hooks/useListScrollElementAttachWeapp.d.ts +27 -0
- package/dist/components/list/hooks/useListScrollElementAttachWeapp.js +154 -0
- package/dist/components/list/hooks/useListScrollElementAttachWeapp.js.map +1 -0
- package/dist/components/list/hooks/useMeasureStartOffset.d.ts +12 -0
- package/dist/components/list/hooks/useMeasureStartOffset.js +84 -0
- package/dist/components/list/hooks/useMeasureStartOffset.js.map +1 -0
- package/dist/components/list/hooks/useMeasureStartOffsetWeapp.d.ts +13 -0
- package/dist/components/list/hooks/useMeasureStartOffsetWeapp.js +85 -0
- package/dist/components/list/hooks/useMeasureStartOffsetWeapp.js.map +1 -0
- package/dist/components/list/hooks/useRefresher.d.ts +74 -0
- package/dist/components/list/hooks/useRefresher.js +503 -0
- package/dist/components/list/hooks/useRefresher.js.map +1 -0
- package/dist/components/list/hooks/useResizeObserver.d.ts +26 -0
- package/dist/components/list/hooks/useResizeObserver.js +152 -0
- package/dist/components/list/hooks/useResizeObserver.js.map +1 -0
- package/dist/components/list/hooks/useScrollCorrection.d.ts +19 -0
- package/dist/components/list/hooks/useScrollCorrection.js +73 -0
- package/dist/components/list/hooks/useScrollCorrection.js.map +1 -0
- package/dist/components/list/hooks/useScrollParentAutoFind.d.ts +20 -0
- package/dist/components/list/hooks/useScrollParentAutoFind.js +81 -0
- package/dist/components/list/hooks/useScrollParentAutoFind.js.map +1 -0
- package/dist/components/list/index.d.ts +59 -3
- package/dist/components/list/index.js +997 -119
- package/dist/components/list/index.js.map +1 -1
- package/dist/components/list/utils.d.ts +16 -0
- package/dist/components/list/utils.js +19 -0
- package/dist/components/list/utils.js.map +1 -0
- package/dist/components/virtual-list/vue/list.d.ts +12 -12
- package/dist/components/virtual-waterfall/vue/waterfall.d.ts +11 -11
- package/dist/components/water-flow/flow-item.js +6 -4
- package/dist/components/water-flow/flow-item.js.map +1 -1
- package/dist/components/water-flow/flow-section.js +1 -1
- package/dist/components/water-flow/flow-section.js.map +1 -1
- package/dist/components/water-flow/interface.d.ts +14 -2
- package/dist/components/water-flow/root.d.ts +16 -2
- package/dist/components/water-flow/root.js +70 -28
- package/dist/components/water-flow/root.js.map +1 -1
- package/dist/components/water-flow/section.d.ts +1 -1
- package/dist/components/water-flow/section.js +12 -4
- package/dist/components/water-flow/section.js.map +1 -1
- package/dist/components/water-flow/utils.d.ts +4 -0
- package/dist/components/water-flow/utils.js +5 -1
- package/dist/components/water-flow/utils.js.map +1 -1
- package/dist/components/water-flow/water-flow.d.ts +1 -1
- package/dist/components/water-flow/water-flow.js +248 -27
- package/dist/components/water-flow/water-flow.js.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/scrollElementContext.d.ts +15 -0
- package/dist/utils/scrollElementContext.js +14 -0
- package/dist/utils/scrollElementContext.js.map +1 -0
- package/dist/utils/scrollParent.d.ts +33 -0
- package/dist/utils/scrollParent.js +88 -0
- package/dist/utils/scrollParent.js.map +1 -0
- package/package.json +9 -8
package/dist/components/index.js
CHANGED
|
@@ -2,7 +2,9 @@ export { List, accumulate, isShaking } from './list/index.js';
|
|
|
2
2
|
export { VirtualList } from './virtual-list/index.js';
|
|
3
3
|
export { VirtualWaterfall } from './virtual-waterfall/index.js';
|
|
4
4
|
import './water-flow/index.js';
|
|
5
|
+
export { ScrollElementContextOrFallback as ListScrollElementContext } from '../utils/scrollElementContext.js';
|
|
5
6
|
export { ListItem } from './list/ListItem.js';
|
|
7
|
+
export { NoMore } from './list/NoMore.js';
|
|
6
8
|
export { StickyHeader } from './list/StickyHeader.js';
|
|
7
9
|
export { StickySection } from './list/StickySection.js';
|
|
8
10
|
export { FlowItem, useFlowItemPositioner } from './water-flow/flow-item.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* NoMore 组件 - 底部"没有更多"提示
|
|
4
|
+
*
|
|
5
|
+
* 这是一个标记组件,不直接渲染内容,仅用于向 List 组件传递配置。
|
|
6
|
+
* 实际渲染由 List 组件内部处理。
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* <List>
|
|
11
|
+
* <ListItem>Item 1</ListItem>
|
|
12
|
+
* <NoMore visible={!hasMore} text="没有更多了" />
|
|
13
|
+
* </List>
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export interface NoMoreProps {
|
|
17
|
+
/** 是否显示(默认 true) */
|
|
18
|
+
visible?: boolean;
|
|
19
|
+
/** 提示文字(默认 "没有更多了") */
|
|
20
|
+
text?: string;
|
|
21
|
+
/** 自定义样式 */
|
|
22
|
+
style?: React.CSSProperties;
|
|
23
|
+
/** 自定义内容(优先级高于 text) */
|
|
24
|
+
children?: React.ReactNode;
|
|
25
|
+
/** NoMore 区域高度(用于动态高度计算,默认 60) */
|
|
26
|
+
height?: number;
|
|
27
|
+
}
|
|
28
|
+
declare const NoMore: React.FC<NoMoreProps>;
|
|
29
|
+
export { NoMore };
|
|
30
|
+
export default NoMore;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NoMore.js","sources":["../../../src/components/list/NoMore.tsx"],"sourcesContent":["import React from 'react'\n\n/**\n * NoMore 组件 - 底部\"没有更多\"提示\n *\n * 这是一个标记组件,不直接渲染内容,仅用于向 List 组件传递配置。\n * 实际渲染由 List 组件内部处理。\n *\n * @example\n * ```tsx\n * <List>\n * <ListItem>Item 1</ListItem>\n * <NoMore visible={!hasMore} text=\"没有更多了\" />\n * </List>\n * ```\n */\n\nexport interface NoMoreProps {\n /** 是否显示(默认 true) */\n visible?: boolean\n\n /** 提示文字(默认 \"没有更多了\") */\n text?: string\n\n /** 自定义样式 */\n style?: React.CSSProperties\n\n /** 自定义内容(优先级高于 text) */\n children?: React.ReactNode\n\n /** NoMore 区域高度(用于动态高度计算,默认 60) */\n height?: number\n}\n\nconst NoMore: React.FC<NoMoreProps> = () => {\n // 标记组件,不实际渲染\n // 实际渲染由 List 组件内部的 renderNoMoreContent() 处理\n return null\n}\n\n// 设置 displayName 便于调试\nNoMore.displayName = 'NoMore'\n\nexport { NoMore }\nexport default NoMore\n"],"names":[],"mappings":"AAkCM,MAAA,MAAM,GAA0B,MAAK;;;AAGzC,IAAA,OAAO,IAAI;AACb;AAEA;AACA,MAAM,CAAC,WAAW,GAAG,QAAQ;;;;"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface UseItemSizeCacheOptions {
|
|
2
|
+
isHorizontal: boolean;
|
|
3
|
+
estimatedItemSize: number;
|
|
4
|
+
itemCount: number;
|
|
5
|
+
}
|
|
6
|
+
interface UseItemSizeCacheReturn {
|
|
7
|
+
/** 获取项的尺寸(高度或宽度) */
|
|
8
|
+
getItemSize: (index: number) => number;
|
|
9
|
+
/** 设置项的尺寸 */
|
|
10
|
+
setItemSize: (index: number, size: number) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare function useItemSizeCache(options: UseItemSizeCacheOptions): UseItemSizeCacheReturn;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useRef, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
function useItemSizeCache(options) {
|
|
4
|
+
const { estimatedItemSize } = options;
|
|
5
|
+
// 缓存 Map:key = 索引,value = 尺寸信息
|
|
6
|
+
const cacheRef = useRef(new Map());
|
|
7
|
+
/**
|
|
8
|
+
* 获取项的尺寸
|
|
9
|
+
* 优先返回实际测量值,否则返回估算值
|
|
10
|
+
*/
|
|
11
|
+
const getItemSize = useCallback((index) => {
|
|
12
|
+
const cached = cacheRef.current.get(index);
|
|
13
|
+
if ((cached === null || cached === void 0 ? void 0 : cached.isMeasured) && cached.measuredSize !== null) {
|
|
14
|
+
return cached.measuredSize;
|
|
15
|
+
}
|
|
16
|
+
return estimatedItemSize;
|
|
17
|
+
}, [estimatedItemSize]);
|
|
18
|
+
/**
|
|
19
|
+
* 设置项的尺寸(实际测量后调用)
|
|
20
|
+
*/
|
|
21
|
+
const setItemSize = useCallback((index, size) => {
|
|
22
|
+
const cached = cacheRef.current.get(index);
|
|
23
|
+
// 尺寸变化小于 1px,忽略(避免微小抖动)
|
|
24
|
+
if ((cached === null || cached === void 0 ? void 0 : cached.measuredSize) && Math.abs(cached.measuredSize - size) < 1) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
cacheRef.current.set(index, {
|
|
28
|
+
measuredSize: size,
|
|
29
|
+
estimatedSize: estimatedItemSize,
|
|
30
|
+
isMeasured: true
|
|
31
|
+
});
|
|
32
|
+
}, [estimatedItemSize]);
|
|
33
|
+
return {
|
|
34
|
+
getItemSize,
|
|
35
|
+
setItemSize
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { useItemSizeCache };
|
|
40
|
+
//# sourceMappingURL=useItemSizeCache.js.map
|
|
@@ -0,0 +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\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;;;;"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { RefObject } from 'react';
|
|
3
|
+
export interface UseListNestedScrollResult {
|
|
4
|
+
effectiveScrollElement: RefObject<HTMLElement | null> | null;
|
|
5
|
+
effectiveStartOffset: number;
|
|
6
|
+
/** 小程序用:实时读取 startOffset,避免 useState 异步更新导致 useListScrollElementAttachWeapp 读到 0 */
|
|
7
|
+
effectiveStartOffsetRef: React.MutableRefObject<number>;
|
|
8
|
+
useScrollElementMode: boolean;
|
|
9
|
+
needAutoFind: boolean;
|
|
10
|
+
autoFindStatus: 'pending' | 'found' | 'not-found';
|
|
11
|
+
contentWrapperRef: RefObject<HTMLDivElement | null>;
|
|
12
|
+
/** 小程序自动查找用:content 节点的 id,需挂到 contentWrapper 对应的 View 上 */
|
|
13
|
+
contentId: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 合并嵌套滚动相关逻辑:Context、自动查找、startOffset 测量、scrollElement 解析
|
|
17
|
+
*/
|
|
18
|
+
export declare function useListNestedScroll(listType: 'default' | 'nested', scrollElement?: RefObject<HTMLElement | null>, startOffsetProp?: number, isHorizontal?: boolean): UseListNestedScrollResult;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { useRef, useMemo, useContext } from 'react';
|
|
2
|
+
import { ScrollElementContextOrFallback } from '../../../utils/scrollElementContext.js';
|
|
3
|
+
import { isH5, isWeapp } from '../utils.js';
|
|
4
|
+
import { useMeasureStartOffset } from './useMeasureStartOffset.js';
|
|
5
|
+
import { useMeasureStartOffsetWeapp } from './useMeasureStartOffsetWeapp.js';
|
|
6
|
+
import { useScrollParentAutoFind } from './useScrollParentAutoFind.js';
|
|
7
|
+
|
|
8
|
+
const EMPTY_SCROLL_REF = { current: null };
|
|
9
|
+
/**
|
|
10
|
+
* 合并嵌套滚动相关逻辑:Context、自动查找、startOffset 测量、scrollElement 解析
|
|
11
|
+
*/
|
|
12
|
+
function useListNestedScroll(listType, scrollElement, startOffsetProp, isHorizontal = false) {
|
|
13
|
+
var _a, _b, _c, _d;
|
|
14
|
+
const contentWrapperRef = useRef(null);
|
|
15
|
+
const contentId = useMemo(() => `list-content-${Math.random().toString(36).slice(2, 11)}`, []);
|
|
16
|
+
const scrollElementCtx = useContext(ScrollElementContextOrFallback);
|
|
17
|
+
const ctxStart = scrollElementCtx === null || scrollElementCtx === void 0 ? void 0 : scrollElementCtx.startOffset;
|
|
18
|
+
const hasExplicitCtxStartOffset = ctxStart != null && ctxStart > 0;
|
|
19
|
+
const needAutoFind = listType === 'nested' &&
|
|
20
|
+
!scrollElement &&
|
|
21
|
+
!(scrollElementCtx === null || scrollElementCtx === void 0 ? void 0 : scrollElementCtx.scrollRef) &&
|
|
22
|
+
(isH5 || isWeapp);
|
|
23
|
+
const { scrollParentRef: autoFoundRef, status: autoFindStatus } = useScrollParentAutoFind(contentWrapperRef, { enabled: !!needAutoFind, isHorizontal, contentId: isWeapp ? contentId : undefined });
|
|
24
|
+
const effectiveScrollElement = (_a = scrollElement !== null && scrollElement !== void 0 ? scrollElement : scrollElementCtx === null || scrollElementCtx === void 0 ? void 0 : scrollElementCtx.scrollRef) !== null && _a !== void 0 ? _a : (needAutoFind && autoFindStatus === 'found' ? autoFoundRef : null);
|
|
25
|
+
const needMeasure = listType === 'nested' &&
|
|
26
|
+
effectiveScrollElement &&
|
|
27
|
+
isH5 &&
|
|
28
|
+
startOffsetProp == null &&
|
|
29
|
+
!hasExplicitCtxStartOffset;
|
|
30
|
+
const needMeasureWeapp = listType === 'nested' &&
|
|
31
|
+
effectiveScrollElement &&
|
|
32
|
+
isWeapp &&
|
|
33
|
+
startOffsetProp == null &&
|
|
34
|
+
!hasExplicitCtxStartOffset;
|
|
35
|
+
const effectiveStartOffsetRef = useRef(0);
|
|
36
|
+
const measuredStartOffset = useMeasureStartOffset(effectiveScrollElement !== null && effectiveScrollElement !== void 0 ? effectiveScrollElement : EMPTY_SCROLL_REF, contentWrapperRef, { enabled: !!needMeasure, isHorizontal });
|
|
37
|
+
const measuredStartOffsetWeapp = useMeasureStartOffsetWeapp(effectiveScrollElement !== null && effectiveScrollElement !== void 0 ? effectiveScrollElement : EMPTY_SCROLL_REF, contentId, { enabled: !!needMeasureWeapp, isHorizontal, startOffsetRef: effectiveStartOffsetRef });
|
|
38
|
+
const effectiveStartOffset = (_d = (_c = (_b = startOffsetProp !== null && startOffsetProp !== void 0 ? startOffsetProp : (ctxStart != null && ctxStart > 0 ? ctxStart : null)) !== null && _b !== void 0 ? _b : measuredStartOffset) !== null && _c !== void 0 ? _c : measuredStartOffsetWeapp) !== null && _d !== void 0 ? _d : 0;
|
|
39
|
+
// needMeasureWeapp 时由 useMeasureStartOffsetWeapp 的 exec 回调更新 ref,不在此覆盖,避免 re-render 用 stale 0 覆盖已测量的值
|
|
40
|
+
if (!needMeasureWeapp) {
|
|
41
|
+
effectiveStartOffsetRef.current = effectiveStartOffset;
|
|
42
|
+
}
|
|
43
|
+
const useScrollElementMode = listType === 'nested' && !!(effectiveScrollElement && (isH5 || isWeapp));
|
|
44
|
+
if (listType === 'nested' && !effectiveScrollElement && (isH5 || isWeapp) && autoFindStatus === 'not-found') {
|
|
45
|
+
// eslint-disable-next-line no-console
|
|
46
|
+
console.warn('[List] nestedScroll 模式但无 scrollElement(props/Context/自动查找),回退为 default');
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
effectiveScrollElement,
|
|
50
|
+
effectiveStartOffset,
|
|
51
|
+
effectiveStartOffsetRef,
|
|
52
|
+
useScrollElementMode,
|
|
53
|
+
needAutoFind,
|
|
54
|
+
autoFindStatus,
|
|
55
|
+
contentWrapperRef,
|
|
56
|
+
contentId,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { useListNestedScroll };
|
|
61
|
+
//# sourceMappingURL=useListNestedScroll.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useListNestedScroll.js","sources":["../../../../src/components/list/hooks/useListNestedScroll.ts"],"sourcesContent":["import React, { useContext, useMemo, useRef } from 'react'\n\nimport {\n type ScrollElementContextValueShape,\n ScrollElementContextOrFallback,\n} from '../../../utils/scrollElementContext'\nimport { isH5, isWeapp } from '../utils'\nimport { useMeasureStartOffset } from './useMeasureStartOffset'\nimport { useMeasureStartOffsetWeapp } from './useMeasureStartOffsetWeapp'\nimport { useScrollParentAutoFind } from './useScrollParentAutoFind'\n\nimport type { RefObject } from 'react'\n\nconst EMPTY_SCROLL_REF = { current: null as HTMLElement | null }\n\nexport interface UseListNestedScrollResult {\n effectiveScrollElement: RefObject<HTMLElement | null> | null\n effectiveStartOffset: number\n /** 小程序用:实时读取 startOffset,避免 useState 异步更新导致 useListScrollElementAttachWeapp 读到 0 */\n effectiveStartOffsetRef: React.MutableRefObject<number>\n useScrollElementMode: boolean\n needAutoFind: boolean\n autoFindStatus: 'pending' | 'found' | 'not-found'\n contentWrapperRef: RefObject<HTMLDivElement | null>\n /** 小程序自动查找用:content 节点的 id,需挂到 contentWrapper 对应的 View 上 */\n contentId: string\n}\n\n/**\n * 合并嵌套滚动相关逻辑:Context、自动查找、startOffset 测量、scrollElement 解析\n */\nexport function useListNestedScroll(\n listType: 'default' | 'nested',\n scrollElement?: RefObject<HTMLElement | null>,\n startOffsetProp?: number,\n isHorizontal: boolean = false\n): UseListNestedScrollResult {\n const contentWrapperRef = useRef<HTMLDivElement>(null)\n const contentId = useMemo(() => `list-content-${Math.random().toString(36).slice(2, 11)}`, [])\n const scrollElementCtx = useContext(ScrollElementContextOrFallback) as ScrollElementContextValueShape | null\n const ctxStart = scrollElementCtx?.startOffset\n const hasExplicitCtxStartOffset = ctxStart != null && ctxStart > 0\n\n const needAutoFind =\n listType === 'nested' &&\n !scrollElement &&\n !scrollElementCtx?.scrollRef &&\n (isH5 || isWeapp)\n const { scrollParentRef: autoFoundRef, status: autoFindStatus } = useScrollParentAutoFind(\n contentWrapperRef,\n { enabled: !!needAutoFind, isHorizontal, contentId: isWeapp ? contentId : undefined }\n )\n\n const effectiveScrollElement =\n scrollElement ??\n scrollElementCtx?.scrollRef ??\n (needAutoFind && autoFindStatus === 'found' ? autoFoundRef : null)\n\n const needMeasure =\n listType === 'nested' &&\n effectiveScrollElement &&\n isH5 &&\n startOffsetProp == null &&\n !hasExplicitCtxStartOffset\n const needMeasureWeapp =\n listType === 'nested' &&\n effectiveScrollElement &&\n isWeapp &&\n startOffsetProp == null &&\n !hasExplicitCtxStartOffset\n const effectiveStartOffsetRef = useRef(0)\n const measuredStartOffset = useMeasureStartOffset(\n effectiveScrollElement ?? EMPTY_SCROLL_REF,\n contentWrapperRef,\n { enabled: !!needMeasure, isHorizontal }\n )\n const measuredStartOffsetWeapp = useMeasureStartOffsetWeapp(\n effectiveScrollElement ?? EMPTY_SCROLL_REF,\n contentId,\n { enabled: !!needMeasureWeapp, isHorizontal, startOffsetRef: effectiveStartOffsetRef }\n )\n\n const effectiveStartOffset =\n startOffsetProp ??\n (ctxStart != null && ctxStart > 0 ? ctxStart : null) ??\n measuredStartOffset ??\n measuredStartOffsetWeapp ??\n 0\n\n // needMeasureWeapp 时由 useMeasureStartOffsetWeapp 的 exec 回调更新 ref,不在此覆盖,避免 re-render 用 stale 0 覆盖已测量的值\n if (!needMeasureWeapp) {\n effectiveStartOffsetRef.current = effectiveStartOffset\n }\n\n const useScrollElementMode = listType === 'nested' && !!(effectiveScrollElement && (isH5 || isWeapp))\n\n if (listType === 'nested' && !effectiveScrollElement && (isH5 || isWeapp) && autoFindStatus === 'not-found') {\n // eslint-disable-next-line no-console\n console.warn('[List] nestedScroll 模式但无 scrollElement(props/Context/自动查找),回退为 default')\n }\n\n return {\n effectiveScrollElement,\n effectiveStartOffset,\n effectiveStartOffsetRef,\n useScrollElementMode,\n needAutoFind,\n autoFindStatus,\n contentWrapperRef,\n contentId,\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
export interface ListScrollElementAttachRefs {
|
|
3
|
+
scrollCorrection: {
|
|
4
|
+
markUserScrolling: () => void;
|
|
5
|
+
};
|
|
6
|
+
onScroll: ((e: {
|
|
7
|
+
scrollTop: number;
|
|
8
|
+
scrollLeft: number;
|
|
9
|
+
}) => void) | undefined;
|
|
10
|
+
onScrollToUpper: (() => void) | undefined;
|
|
11
|
+
onScrollToLower: (() => void) | undefined;
|
|
12
|
+
threshold: {
|
|
13
|
+
upper: number;
|
|
14
|
+
lower: number;
|
|
15
|
+
};
|
|
16
|
+
listContentLength: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* scrollElement 模式下:监听外部滚动驱动 renderOffset,并从 scrollElement 获取 containerLength
|
|
20
|
+
*/
|
|
21
|
+
export declare function useListScrollElementAttach(enabled: boolean, effectiveScrollElement: RefObject<HTMLElement | null> | null, effectiveStartOffset: number, isHorizontal: boolean, setContainerLength: (v: number) => void, updateRenderOffset: (offset: number, sync?: boolean, source?: string) => void, scrollRefProp: React.MutableRefObject<HTMLElement | null> | undefined, refsRef: RefObject<ListScrollElementAttachRefs>): void;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useRef, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* scrollElement 模式下:监听外部滚动驱动 renderOffset,并从 scrollElement 获取 containerLength
|
|
5
|
+
*/
|
|
6
|
+
function useListScrollElementAttach(enabled, effectiveScrollElement, effectiveStartOffset, isHorizontal, setContainerLength, updateRenderOffset, scrollRefProp, refsRef) {
|
|
7
|
+
const effectiveStartOffsetRef = useRef(effectiveStartOffset);
|
|
8
|
+
effectiveStartOffsetRef.current = effectiveStartOffset;
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!enabled || !effectiveScrollElement)
|
|
11
|
+
return;
|
|
12
|
+
const el = effectiveScrollElement.current;
|
|
13
|
+
if (!el || typeof ResizeObserver === 'undefined')
|
|
14
|
+
return;
|
|
15
|
+
const update = () => {
|
|
16
|
+
const measured = isHorizontal ? el.clientWidth : el.clientHeight;
|
|
17
|
+
if (measured > 0)
|
|
18
|
+
setContainerLength(measured);
|
|
19
|
+
if (scrollRefProp)
|
|
20
|
+
scrollRefProp.current = el;
|
|
21
|
+
};
|
|
22
|
+
update();
|
|
23
|
+
const ro = new ResizeObserver(update);
|
|
24
|
+
ro.observe(el);
|
|
25
|
+
return () => ro.disconnect();
|
|
26
|
+
}, [enabled, effectiveScrollElement, isHorizontal, scrollRefProp, setContainerLength]);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!enabled || !effectiveScrollElement)
|
|
29
|
+
return;
|
|
30
|
+
let cancelled = false;
|
|
31
|
+
let teardown = null;
|
|
32
|
+
const maxRetries = 20;
|
|
33
|
+
const tryAttach = (retryCount = 0) => {
|
|
34
|
+
if (cancelled)
|
|
35
|
+
return;
|
|
36
|
+
const target = effectiveScrollElement.current;
|
|
37
|
+
if (!target) {
|
|
38
|
+
if (retryCount < maxRetries) {
|
|
39
|
+
requestAnimationFrame(() => tryAttach(retryCount + 1));
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const refs = refsRef.current;
|
|
44
|
+
if (!refs)
|
|
45
|
+
return;
|
|
46
|
+
let inUpperZone = true;
|
|
47
|
+
let inLowerZone = false;
|
|
48
|
+
const handler = () => {
|
|
49
|
+
var _a, _b, _c;
|
|
50
|
+
const r = refsRef.current;
|
|
51
|
+
if (!r)
|
|
52
|
+
return;
|
|
53
|
+
const scrollPos = isHorizontal ? target.scrollLeft : target.scrollTop;
|
|
54
|
+
const adjustedPos = scrollPos - effectiveStartOffsetRef.current;
|
|
55
|
+
const effectiveAdjusted = Math.max(0, adjustedPos);
|
|
56
|
+
const clientSize = isHorizontal ? target.clientWidth : target.clientHeight;
|
|
57
|
+
r.scrollCorrection.markUserScrolling();
|
|
58
|
+
updateRenderOffset(effectiveAdjusted, false, 'scrollElement');
|
|
59
|
+
(_a = r.onScroll) === null || _a === void 0 ? void 0 : _a.call(r, { scrollTop: isHorizontal ? 0 : scrollPos, scrollLeft: isHorizontal ? scrollPos : 0 });
|
|
60
|
+
const { upper, lower } = r.threshold;
|
|
61
|
+
const innerContentLen = r.listContentLength;
|
|
62
|
+
const nowInUpper = effectiveAdjusted <= upper;
|
|
63
|
+
const nowInLower = innerContentLen > 0 && effectiveAdjusted + clientSize >= innerContentLen - lower;
|
|
64
|
+
if (nowInUpper && !inUpperZone)
|
|
65
|
+
(_b = r.onScrollToUpper) === null || _b === void 0 ? void 0 : _b.call(r);
|
|
66
|
+
if (nowInLower && !inLowerZone)
|
|
67
|
+
(_c = r.onScrollToLower) === null || _c === void 0 ? void 0 : _c.call(r);
|
|
68
|
+
inUpperZone = nowInUpper;
|
|
69
|
+
inLowerZone = nowInLower;
|
|
70
|
+
};
|
|
71
|
+
const initialScroll = isHorizontal ? target.scrollLeft : target.scrollTop;
|
|
72
|
+
const initialAdjusted = Math.max(0, initialScroll - effectiveStartOffsetRef.current);
|
|
73
|
+
updateRenderOffset(initialAdjusted, false, 'scrollElement');
|
|
74
|
+
target.addEventListener('scroll', handler, { passive: true });
|
|
75
|
+
teardown = () => target.removeEventListener('scroll', handler);
|
|
76
|
+
};
|
|
77
|
+
tryAttach();
|
|
78
|
+
return () => {
|
|
79
|
+
cancelled = true;
|
|
80
|
+
teardown === null || teardown === void 0 ? void 0 : teardown();
|
|
81
|
+
};
|
|
82
|
+
}, [enabled, effectiveScrollElement, isHorizontal, effectiveStartOffset, updateRenderOffset, refsRef]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { useListScrollElementAttach };
|
|
86
|
+
//# sourceMappingURL=useListScrollElementAttach.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useListScrollElementAttach.js","sources":["../../../../src/components/list/hooks/useListScrollElementAttach.ts"],"sourcesContent":["import { useEffect, useRef } from 'react'\n\nimport type { RefObject } from 'react'\n\nexport interface ListScrollElementAttachRefs {\n scrollCorrection: { markUserScrolling: () => void }\n onScroll: ((e: { scrollTop: number, scrollLeft: number }) => void) | undefined\n onScrollToUpper: (() => void) | undefined\n onScrollToLower: (() => void) | undefined\n threshold: { upper: number, lower: number }\n listContentLength: number\n}\n\n/**\n * scrollElement 模式下:监听外部滚动驱动 renderOffset,并从 scrollElement 获取 containerLength\n */\nexport function useListScrollElementAttach(\n enabled: boolean,\n effectiveScrollElement: RefObject<HTMLElement | null> | null,\n effectiveStartOffset: number,\n isHorizontal: boolean,\n setContainerLength: (v: number) => void,\n updateRenderOffset: (offset: number, sync?: boolean, source?: string) => void,\n scrollRefProp: React.MutableRefObject<HTMLElement | null> | undefined,\n refsRef: RefObject<ListScrollElementAttachRefs>\n) {\n const effectiveStartOffsetRef = useRef(effectiveStartOffset)\n effectiveStartOffsetRef.current = effectiveStartOffset\n\n useEffect(() => {\n if (!enabled || !effectiveScrollElement) return\n const el = effectiveScrollElement.current\n if (!el || typeof ResizeObserver === 'undefined') return\n\n const update = () => {\n const measured = isHorizontal ? el.clientWidth : el.clientHeight\n if (measured > 0) setContainerLength(measured)\n if (scrollRefProp) scrollRefProp.current = el\n }\n update()\n const ro = new ResizeObserver(update)\n ro.observe(el)\n return () => ro.disconnect()\n }, [enabled, effectiveScrollElement, isHorizontal, scrollRefProp, setContainerLength])\n\n useEffect(() => {\n if (!enabled || !effectiveScrollElement) return\n\n let cancelled = false\n let teardown: (() => void) | null = null\n const maxRetries = 20\n\n const tryAttach = (retryCount = 0) => {\n if (cancelled) return\n const target = effectiveScrollElement.current\n if (!target) {\n if (retryCount < maxRetries) {\n requestAnimationFrame(() => tryAttach(retryCount + 1))\n }\n return\n }\n\n const refs = refsRef.current\n if (!refs) return\n\n let inUpperZone = true\n let inLowerZone = false\n\n const handler = () => {\n const r = refsRef.current\n if (!r) return\n const scrollPos = isHorizontal ? target.scrollLeft : target.scrollTop\n const adjustedPos = scrollPos - effectiveStartOffsetRef.current\n const effectiveAdjusted = Math.max(0, adjustedPos)\n const clientSize = isHorizontal ? target.clientWidth : target.clientHeight\n r.scrollCorrection.markUserScrolling()\n updateRenderOffset(effectiveAdjusted, false, 'scrollElement')\n r.onScroll?.({ scrollTop: isHorizontal ? 0 : scrollPos, scrollLeft: isHorizontal ? scrollPos : 0 })\n\n const { upper, lower } = r.threshold\n const innerContentLen = r.listContentLength\n const nowInUpper = effectiveAdjusted <= upper\n const nowInLower = innerContentLen > 0 && effectiveAdjusted + clientSize >= innerContentLen - lower\n if (nowInUpper && !inUpperZone) r.onScrollToUpper?.()\n if (nowInLower && !inLowerZone) r.onScrollToLower?.()\n inUpperZone = nowInUpper\n inLowerZone = nowInLower\n }\n\n const initialScroll = isHorizontal ? target.scrollLeft : target.scrollTop\n const initialAdjusted = Math.max(0, initialScroll - effectiveStartOffsetRef.current)\n updateRenderOffset(initialAdjusted, false, 'scrollElement')\n\n target.addEventListener('scroll', handler, { passive: true })\n teardown = () => target.removeEventListener('scroll', handler)\n }\n\n tryAttach()\n return () => {\n cancelled = true\n teardown?.()\n }\n }, [enabled, effectiveScrollElement, isHorizontal, effectiveStartOffset, updateRenderOffset, refsRef])\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { MutableRefObject, RefObject } from 'react';
|
|
2
|
+
import type { ListScrollElementAttachRefs } from './useListScrollElementAttach';
|
|
3
|
+
/**
|
|
4
|
+
* 小程序版 scrollElement 模式:监听外部 scroll-view 的滚动事件驱动 renderOffset,
|
|
5
|
+
* 并通过 createSelectorQuery 测量容器尺寸。
|
|
6
|
+
*
|
|
7
|
+
* 与 H5 版 useListScrollElementAttach 的区别:
|
|
8
|
+
* - 容器尺寸通过 createSelectorQuery 测量(无 ResizeObserver)
|
|
9
|
+
* - 滚动事件通过 TaroElement.addEventListener,格式为 TaroEvent(detail 合并到 target 上)
|
|
10
|
+
* - 若 scroll-view 无 id,自动分配临时 id 供 SelectorQuery 使用
|
|
11
|
+
*/
|
|
12
|
+
export declare function useListScrollElementAttachWeapp(enabled: boolean, effectiveScrollElement: RefObject<HTMLElement | null> | null, effectiveStartOffsetRef: MutableRefObject<number>, effectiveStartOffset: number, isHorizontal: boolean, setContainerLength: (v: number) => void, updateRenderOffset: (offset: number, sync?: boolean, source?: string) => void, scrollRefProp: React.MutableRefObject<HTMLElement | null> | undefined, refsRef: RefObject<ListScrollElementAttachRefs>,
|
|
13
|
+
/**
|
|
14
|
+
* 【兜底】容器尺寸测量失败或未完成时的回退值。
|
|
15
|
+
*
|
|
16
|
+
* 使用场景:
|
|
17
|
+
* 1. 首次 scroll:SelectorQuery.exec 异步,scroll 事件可能早于测量回调,containerLengthRef 仍为 0
|
|
18
|
+
* 2. 测量长期失败:SelectorQuery 返回空或 rect 尺寸为 0(如 id 错误、节点未挂载)
|
|
19
|
+
*
|
|
20
|
+
* 兜底逻辑:
|
|
21
|
+
* - measure 回调:rect 为空或 measured≤0 时,若有 fallback 则写入 containerLengthRef 与 setContainerLength
|
|
22
|
+
* - scroll handler:containerLengthRef 为 0 时,用 fallback 参与 onScrollToUpper/Lower 判断
|
|
23
|
+
*
|
|
24
|
+
* 取值建议:与 List 的 initialContainerLength 一致(默认 400,或 height/width prop),避免与 renderRange 等逻辑冲突。
|
|
25
|
+
* 注意:fallback 与真实视口差异大时,onScrollToUpper/Lower 可能误触发;measure 每 150ms 轮询,正常会很快覆盖。
|
|
26
|
+
*/
|
|
27
|
+
fallbackContainerLength?: number): void;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import Taro from '@tarojs/taro';
|
|
2
|
+
import { useRef, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 小程序版 scrollElement 模式:监听外部 scroll-view 的滚动事件驱动 renderOffset,
|
|
6
|
+
* 并通过 createSelectorQuery 测量容器尺寸。
|
|
7
|
+
*
|
|
8
|
+
* 与 H5 版 useListScrollElementAttach 的区别:
|
|
9
|
+
* - 容器尺寸通过 createSelectorQuery 测量(无 ResizeObserver)
|
|
10
|
+
* - 滚动事件通过 TaroElement.addEventListener,格式为 TaroEvent(detail 合并到 target 上)
|
|
11
|
+
* - 若 scroll-view 无 id,自动分配临时 id 供 SelectorQuery 使用
|
|
12
|
+
*/
|
|
13
|
+
function useListScrollElementAttachWeapp(enabled, effectiveScrollElement, effectiveStartOffsetRef, effectiveStartOffset, isHorizontal, setContainerLength, updateRenderOffset, scrollRefProp, refsRef,
|
|
14
|
+
/**
|
|
15
|
+
* 【兜底】容器尺寸测量失败或未完成时的回退值。
|
|
16
|
+
*
|
|
17
|
+
* 使用场景:
|
|
18
|
+
* 1. 首次 scroll:SelectorQuery.exec 异步,scroll 事件可能早于测量回调,containerLengthRef 仍为 0
|
|
19
|
+
* 2. 测量长期失败:SelectorQuery 返回空或 rect 尺寸为 0(如 id 错误、节点未挂载)
|
|
20
|
+
*
|
|
21
|
+
* 兜底逻辑:
|
|
22
|
+
* - measure 回调:rect 为空或 measured≤0 时,若有 fallback 则写入 containerLengthRef 与 setContainerLength
|
|
23
|
+
* - scroll handler:containerLengthRef 为 0 时,用 fallback 参与 onScrollToUpper/Lower 判断
|
|
24
|
+
*
|
|
25
|
+
* 取值建议:与 List 的 initialContainerLength 一致(默认 400,或 height/width prop),避免与 renderRange 等逻辑冲突。
|
|
26
|
+
* 注意:fallback 与真实视口差异大时,onScrollToUpper/Lower 可能误触发;measure 每 150ms 轮询,正常会很快覆盖。
|
|
27
|
+
*/
|
|
28
|
+
fallbackContainerLength) {
|
|
29
|
+
const containerLengthRef = useRef(0);
|
|
30
|
+
const autoIdRef = useRef(`_ls_${Math.random().toString(36).slice(2, 9)}`);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!enabled || !effectiveScrollElement)
|
|
33
|
+
return;
|
|
34
|
+
const el = effectiveScrollElement.current;
|
|
35
|
+
if (!el)
|
|
36
|
+
return;
|
|
37
|
+
if (scrollRefProp)
|
|
38
|
+
scrollRefProp.current = el;
|
|
39
|
+
if (!el.id) {
|
|
40
|
+
el.id = autoIdRef.current;
|
|
41
|
+
}
|
|
42
|
+
const scrollViewId = el.id;
|
|
43
|
+
const measure = () => {
|
|
44
|
+
const instance = Taro.getCurrentInstance();
|
|
45
|
+
const query = (instance === null || instance === void 0 ? void 0 : instance.page)
|
|
46
|
+
? Taro.createSelectorQuery().in(instance.page)
|
|
47
|
+
: Taro.createSelectorQuery();
|
|
48
|
+
query
|
|
49
|
+
.select(`#${scrollViewId}`)
|
|
50
|
+
.boundingClientRect()
|
|
51
|
+
.exec((res) => {
|
|
52
|
+
const rect = res === null || res === void 0 ? void 0 : res[0];
|
|
53
|
+
if (rect) {
|
|
54
|
+
const measured = isHorizontal ? rect.width : rect.height;
|
|
55
|
+
if (measured > 0) {
|
|
56
|
+
containerLengthRef.current = measured;
|
|
57
|
+
setContainerLength(measured);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// 兜底:rect 为空或 measured≤0 时,使用 fallback 避免 containerLengthRef 长期为 0
|
|
62
|
+
if (fallbackContainerLength != null && fallbackContainerLength > 0) {
|
|
63
|
+
containerLengthRef.current = fallbackContainerLength;
|
|
64
|
+
setContainerLength(fallbackContainerLength);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
measure();
|
|
69
|
+
const interval = setInterval(measure, 150);
|
|
70
|
+
return () => clearInterval(interval);
|
|
71
|
+
}, [enabled, effectiveScrollElement, isHorizontal, scrollRefProp, setContainerLength, fallbackContainerLength]);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!enabled || !effectiveScrollElement)
|
|
74
|
+
return;
|
|
75
|
+
let cancelled = false;
|
|
76
|
+
let teardown = null;
|
|
77
|
+
const maxRetries = 20;
|
|
78
|
+
const tryAttach = (retryCount = 0) => {
|
|
79
|
+
if (cancelled)
|
|
80
|
+
return;
|
|
81
|
+
const target = effectiveScrollElement.current;
|
|
82
|
+
if (!target) {
|
|
83
|
+
if (retryCount < maxRetries) {
|
|
84
|
+
setTimeout(() => tryAttach(retryCount + 1), 50);
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
let inUpperZone = true;
|
|
89
|
+
let inLowerZone = false;
|
|
90
|
+
const handler = (e) => {
|
|
91
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
92
|
+
const r = refsRef.current;
|
|
93
|
+
if (!r)
|
|
94
|
+
return;
|
|
95
|
+
const scrollPos = isHorizontal
|
|
96
|
+
? ((_e = (_b = (_a = e === null || e === void 0 ? void 0 : e.target) === null || _a === void 0 ? void 0 : _a.scrollLeft) !== null && _b !== void 0 ? _b : (_d = (_c = e === null || e === void 0 ? void 0 : e.mpEvent) === null || _c === void 0 ? void 0 : _c.detail) === null || _d === void 0 ? void 0 : _d.scrollLeft) !== null && _e !== void 0 ? _e : 0)
|
|
97
|
+
: ((_k = (_g = (_f = e === null || e === void 0 ? void 0 : e.target) === null || _f === void 0 ? void 0 : _f.scrollTop) !== null && _g !== void 0 ? _g : (_j = (_h = e === null || e === void 0 ? void 0 : e.mpEvent) === null || _h === void 0 ? void 0 : _h.detail) === null || _j === void 0 ? void 0 : _j.scrollTop) !== null && _k !== void 0 ? _k : 0);
|
|
98
|
+
const startOffset = effectiveStartOffsetRef.current;
|
|
99
|
+
const adjustedPos = scrollPos - startOffset;
|
|
100
|
+
const effectiveAdjusted = Math.max(0, adjustedPos);
|
|
101
|
+
r.scrollCorrection.markUserScrolling();
|
|
102
|
+
updateRenderOffset(effectiveAdjusted, false, 'scrollElement');
|
|
103
|
+
(_l = r.onScroll) === null || _l === void 0 ? void 0 : _l.call(r, {
|
|
104
|
+
scrollTop: isHorizontal ? 0 : scrollPos,
|
|
105
|
+
scrollLeft: isHorizontal ? scrollPos : 0,
|
|
106
|
+
});
|
|
107
|
+
// 兜底:measure 未完成时 containerLengthRef 为 0,用 fallback 参与触顶/触底判断,避免漏触发
|
|
108
|
+
const clientSize = containerLengthRef.current || fallbackContainerLength || 0;
|
|
109
|
+
if (clientSize > 0) {
|
|
110
|
+
const { upper, lower } = r.threshold;
|
|
111
|
+
const innerContentLen = r.listContentLength;
|
|
112
|
+
const nowInUpper = effectiveAdjusted <= upper;
|
|
113
|
+
const nowInLower = innerContentLen > 0 && effectiveAdjusted + clientSize >= innerContentLen - lower;
|
|
114
|
+
if (nowInUpper && !inUpperZone)
|
|
115
|
+
(_m = r.onScrollToUpper) === null || _m === void 0 ? void 0 : _m.call(r);
|
|
116
|
+
if (nowInLower && !inLowerZone)
|
|
117
|
+
(_o = r.onScrollToLower) === null || _o === void 0 ? void 0 : _o.call(r);
|
|
118
|
+
inUpperZone = nowInUpper;
|
|
119
|
+
inLowerZone = nowInLower;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
target.addEventListener('scroll', handler);
|
|
123
|
+
teardown = () => target.removeEventListener('scroll', handler);
|
|
124
|
+
// 初始 renderOffset:weapp 无法直接读 target.scrollTop,需通过 SelectorQuery 查询
|
|
125
|
+
const scrollViewId = target.id || autoIdRef.current;
|
|
126
|
+
if (!target.id)
|
|
127
|
+
target.id = scrollViewId;
|
|
128
|
+
const instance = Taro.getCurrentInstance();
|
|
129
|
+
const query = (instance === null || instance === void 0 ? void 0 : instance.page)
|
|
130
|
+
? Taro.createSelectorQuery().in(instance.page)
|
|
131
|
+
: Taro.createSelectorQuery();
|
|
132
|
+
query.select(`#${scrollViewId}`).scrollOffset().exec((res) => {
|
|
133
|
+
var _a, _b;
|
|
134
|
+
const info = res === null || res === void 0 ? void 0 : res[0];
|
|
135
|
+
if (info) {
|
|
136
|
+
const scrollTopVal = (_a = info.scrollTop) !== null && _a !== void 0 ? _a : 0;
|
|
137
|
+
const scrollLeftVal = (_b = info.scrollLeft) !== null && _b !== void 0 ? _b : 0;
|
|
138
|
+
const scrollPos = isHorizontal ? scrollLeftVal : scrollTopVal;
|
|
139
|
+
const startOffset = effectiveStartOffsetRef.current;
|
|
140
|
+
const initialAdjusted = Math.max(0, scrollPos - startOffset);
|
|
141
|
+
updateRenderOffset(initialAdjusted, false, 'scrollElement');
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
tryAttach();
|
|
146
|
+
return () => {
|
|
147
|
+
cancelled = true;
|
|
148
|
+
teardown === null || teardown === void 0 ? void 0 : teardown();
|
|
149
|
+
};
|
|
150
|
+
}, [enabled, effectiveScrollElement, isHorizontal, effectiveStartOffsetRef, effectiveStartOffset, updateRenderOffset, refsRef, fallbackContainerLength]);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export { useListScrollElementAttachWeapp };
|
|
154
|
+
//# sourceMappingURL=useListScrollElementAttachWeapp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useListScrollElementAttachWeapp.js","sources":["../../../../src/components/list/hooks/useListScrollElementAttachWeapp.ts"],"sourcesContent":["import Taro from '@tarojs/taro'\nimport { useEffect, useRef } from 'react'\n\nimport type { MutableRefObject, RefObject } from 'react'\nimport type { ListScrollElementAttachRefs } from './useListScrollElementAttach'\n\n/**\n * 小程序版 scrollElement 模式:监听外部 scroll-view 的滚动事件驱动 renderOffset,\n * 并通过 createSelectorQuery 测量容器尺寸。\n *\n * 与 H5 版 useListScrollElementAttach 的区别:\n * - 容器尺寸通过 createSelectorQuery 测量(无 ResizeObserver)\n * - 滚动事件通过 TaroElement.addEventListener,格式为 TaroEvent(detail 合并到 target 上)\n * - 若 scroll-view 无 id,自动分配临时 id 供 SelectorQuery 使用\n */\nexport function useListScrollElementAttachWeapp(\n enabled: boolean,\n effectiveScrollElement: RefObject<HTMLElement | null> | null,\n effectiveStartOffsetRef: MutableRefObject<number>,\n effectiveStartOffset: number,\n isHorizontal: boolean,\n setContainerLength: (v: number) => void,\n updateRenderOffset: (offset: number, sync?: boolean, source?: string) => void,\n scrollRefProp: React.MutableRefObject<HTMLElement | null> | undefined,\n refsRef: RefObject<ListScrollElementAttachRefs>,\n /**\n * 【兜底】容器尺寸测量失败或未完成时的回退值。\n *\n * 使用场景:\n * 1. 首次 scroll:SelectorQuery.exec 异步,scroll 事件可能早于测量回调,containerLengthRef 仍为 0\n * 2. 测量长期失败:SelectorQuery 返回空或 rect 尺寸为 0(如 id 错误、节点未挂载)\n *\n * 兜底逻辑:\n * - measure 回调:rect 为空或 measured≤0 时,若有 fallback 则写入 containerLengthRef 与 setContainerLength\n * - scroll handler:containerLengthRef 为 0 时,用 fallback 参与 onScrollToUpper/Lower 判断\n *\n * 取值建议:与 List 的 initialContainerLength 一致(默认 400,或 height/width prop),避免与 renderRange 等逻辑冲突。\n * 注意:fallback 与真实视口差异大时,onScrollToUpper/Lower 可能误触发;measure 每 150ms 轮询,正常会很快覆盖。\n */\n fallbackContainerLength?: number\n) {\n const containerLengthRef = useRef(0)\n const autoIdRef = useRef(`_ls_${Math.random().toString(36).slice(2, 9)}`)\n\n useEffect(() => {\n if (!enabled || !effectiveScrollElement) return\n const el = effectiveScrollElement.current as any\n if (!el) return\n if (scrollRefProp) scrollRefProp.current = el\n\n if (!el.id) {\n el.id = autoIdRef.current\n }\n const scrollViewId = el.id\n\n const measure = () => {\n const instance = Taro.getCurrentInstance()\n const query = instance?.page\n ? Taro.createSelectorQuery().in(instance.page as any)\n : Taro.createSelectorQuery()\n query\n .select(`#${scrollViewId}`)\n .boundingClientRect()\n .exec((res) => {\n const rect = res?.[0]\n if (rect) {\n const measured = isHorizontal ? rect.width : rect.height\n if (measured > 0) {\n containerLengthRef.current = measured\n setContainerLength(measured)\n return\n }\n }\n // 兜底:rect 为空或 measured≤0 时,使用 fallback 避免 containerLengthRef 长期为 0\n if (fallbackContainerLength != null && fallbackContainerLength > 0) {\n containerLengthRef.current = fallbackContainerLength\n setContainerLength(fallbackContainerLength)\n }\n })\n }\n\n measure()\n const interval = setInterval(measure, 150)\n return () => clearInterval(interval)\n }, [enabled, effectiveScrollElement, isHorizontal, scrollRefProp, setContainerLength, fallbackContainerLength])\n\n useEffect(() => {\n if (!enabled || !effectiveScrollElement) return\n\n let cancelled = false\n let teardown: (() => void) | null = null\n const maxRetries = 20\n\n const tryAttach = (retryCount = 0) => {\n if (cancelled) return\n const target = effectiveScrollElement.current as any\n if (!target) {\n if (retryCount < maxRetries) {\n setTimeout(() => tryAttach(retryCount + 1), 50)\n }\n return\n }\n\n let inUpperZone = true\n let inLowerZone = false\n\n const handler = (e: any) => {\n const r = refsRef.current\n if (!r) return\n\n const scrollPos = isHorizontal\n ? (e?.target?.scrollLeft ?? e?.mpEvent?.detail?.scrollLeft ?? 0)\n : (e?.target?.scrollTop ?? e?.mpEvent?.detail?.scrollTop ?? 0)\n const startOffset = effectiveStartOffsetRef.current\n const adjustedPos = scrollPos - startOffset\n const effectiveAdjusted = Math.max(0, adjustedPos)\n\n r.scrollCorrection.markUserScrolling()\n updateRenderOffset(effectiveAdjusted, false, 'scrollElement')\n r.onScroll?.({\n scrollTop: isHorizontal ? 0 : scrollPos,\n scrollLeft: isHorizontal ? scrollPos : 0,\n })\n\n // 兜底:measure 未完成时 containerLengthRef 为 0,用 fallback 参与触顶/触底判断,避免漏触发\n const clientSize = containerLengthRef.current || fallbackContainerLength || 0\n if (clientSize > 0) {\n const { upper, lower } = r.threshold\n const innerContentLen = r.listContentLength\n const nowInUpper = effectiveAdjusted <= upper\n const nowInLower = innerContentLen > 0 && effectiveAdjusted + clientSize >= innerContentLen - lower\n if (nowInUpper && !inUpperZone) r.onScrollToUpper?.()\n if (nowInLower && !inLowerZone) r.onScrollToLower?.()\n inUpperZone = nowInUpper\n inLowerZone = nowInLower\n }\n }\n\n target.addEventListener('scroll', handler)\n teardown = () => target.removeEventListener('scroll', handler)\n\n // 初始 renderOffset:weapp 无法直接读 target.scrollTop,需通过 SelectorQuery 查询\n const scrollViewId = target.id || autoIdRef.current\n if (!target.id) target.id = scrollViewId\n const instance = Taro.getCurrentInstance()\n const query = instance?.page\n ? Taro.createSelectorQuery().in(instance.page as any)\n : Taro.createSelectorQuery()\n query.select(`#${scrollViewId}`).scrollOffset().exec((res) => {\n const info = res?.[0]\n if (info) {\n const scrollTopVal = info.scrollTop ?? 0\n const scrollLeftVal = info.scrollLeft ?? 0\n const scrollPos = isHorizontal ? scrollLeftVal : scrollTopVal\n const startOffset = effectiveStartOffsetRef.current\n const initialAdjusted = Math.max(0, scrollPos - startOffset)\n updateRenderOffset(initialAdjusted, false, 'scrollElement')\n }\n })\n }\n\n tryAttach()\n return () => {\n cancelled = true\n teardown?.()\n }\n }, [enabled, effectiveScrollElement, isHorizontal, effectiveStartOffsetRef, effectiveStartOffset, updateRenderOffset, refsRef, fallbackContainerLength])\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* 测量 scrollElement 内 content 节点之前所有兄弟的高度/宽度之和,作为 startOffset。
|
|
4
|
+
* 用于 scrollElement 模式下无 Context/props 时自动计算上方内容高度。
|
|
5
|
+
* - 直接子节点:累加前兄弟尺寸(更稳定)
|
|
6
|
+
* - 非直接子节点:getBoundingClientRect 回退,并 warning 建议传 startOffset
|
|
7
|
+
* - ref 未就绪时自动重试,确保绑定到最外层滚动容器后能正确测量
|
|
8
|
+
*/
|
|
9
|
+
export declare function useMeasureStartOffset(scrollElRef: RefObject<HTMLElement | null>, contentRef: RefObject<HTMLElement | null>, options: {
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
isHorizontal?: boolean;
|
|
12
|
+
}): number;
|