@fjyueke/arco-mcp 1.0.0
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/README.md +85 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +244 -0
- package/metaData/components/action-sheet/doc.md +102 -0
- package/metaData/components/action-sheet/example/index.tsx +199 -0
- package/metaData/components/action-sheet/example/methods.tsx +6 -0
- package/metaData/components/avatar/doc.md +57 -0
- package/metaData/components/avatar/example/group.tsx +69 -0
- package/metaData/components/avatar/example/index.tsx +196 -0
- package/metaData/components/badge/doc.md +28 -0
- package/metaData/components/badge/example/index.tsx +128 -0
- package/metaData/components/button/doc.md +45 -0
- package/metaData/components/button/example/index.tsx +264 -0
- package/metaData/components/carousel/doc.md +89 -0
- package/metaData/components/carousel/example/index.tsx +1481 -0
- package/metaData/components/cell/doc.md +68 -0
- package/metaData/components/cell/example/arrow.tsx +13 -0
- package/metaData/components/cell/example/cell.tsx +71 -0
- package/metaData/components/cell/example/group.tsx +63 -0
- package/metaData/components/cell/example/index.tsx +19 -0
- package/metaData/components/checkbox/doc.md +91 -0
- package/metaData/components/checkbox/example/checkbox.tsx +158 -0
- package/metaData/components/checkbox/example/group.tsx +103 -0
- package/metaData/components/checkbox/example/index.tsx +25 -0
- package/metaData/components/circle-progress/doc.md +40 -0
- package/metaData/components/circle-progress/example/index.tsx +323 -0
- package/metaData/components/collapse/doc.md +74 -0
- package/metaData/components/collapse/example/collapse.tsx +183 -0
- package/metaData/components/collapse/example/group.tsx +136 -0
- package/metaData/components/collapse/example/index.tsx +16 -0
- package/metaData/components/collapse/example/utils.tsx +20 -0
- package/metaData/components/context-provider/doc.md +44 -0
- package/metaData/components/context-provider/example/index.tsx +208 -0
- package/metaData/components/count-down/doc.md +40 -0
- package/metaData/components/count-down/example/hooks.tsx +118 -0
- package/metaData/components/count-down/example/index.tsx +63 -0
- package/metaData/components/date-picker/doc.md +102 -0
- package/metaData/components/date-picker/example/index.tsx +545 -0
- package/metaData/components/dialog/doc.md +200 -0
- package/metaData/components/dialog/example/index.tsx +328 -0
- package/metaData/components/dialog/example/methods.tsx +56 -0
- package/metaData/components/divider/doc.md +25 -0
- package/metaData/components/divider/example/index.tsx +87 -0
- package/metaData/components/dropdown/doc.md +80 -0
- package/metaData/components/dropdown/example/dropdown.tsx +308 -0
- package/metaData/components/dropdown/example/index.tsx +19 -0
- package/metaData/components/dropdown/example/options.tsx +103 -0
- package/metaData/components/dropdown-menu/doc.md +91 -0
- package/metaData/components/dropdown-menu/example/dropdown-menu.tsx +270 -0
- package/metaData/components/dropdown-menu/example/index.tsx +16 -0
- package/metaData/components/ellipsis/doc.md +33 -0
- package/metaData/components/ellipsis/example/index.tsx +93 -0
- package/metaData/components/ellipsis/example/js-ellipsis.tsx +229 -0
- package/metaData/components/ellipsis/example/native-ellipsis.tsx +48 -0
- package/metaData/components/form/doc.md +90 -0
- package/metaData/components/form/example/form-item-context.tsx +8 -0
- package/metaData/components/form/example/form-item.tsx +378 -0
- package/metaData/components/form/example/index.tsx +87 -0
- package/metaData/components/form/example/linked-container.tsx +66 -0
- package/metaData/components/grid/doc.md +39 -0
- package/metaData/components/grid/example/index.tsx +252 -0
- package/metaData/components/icon/doc.md +15 -0
- package/metaData/components/icon/example/index.tsx +32 -0
- package/metaData/components/image/doc.md +62 -0
- package/metaData/components/image/example/index.tsx +528 -0
- package/metaData/components/image-picker/doc.md +116 -0
- package/metaData/components/image-picker/example/add-icon.tsx +14 -0
- package/metaData/components/image-picker/example/index.tsx +175 -0
- package/metaData/components/image-preview/doc.md +119 -0
- package/metaData/components/image-preview/example/index.tsx +1290 -0
- package/metaData/components/image-preview/example/methods.tsx +72 -0
- package/metaData/components/index-bar/doc.md +83 -0
- package/metaData/components/index-bar/example/group.tsx +93 -0
- package/metaData/components/index-bar/example/index.tsx +271 -0
- package/metaData/components/index-bar/example/side-bar.tsx +82 -0
- package/metaData/components/input/doc.md +60 -0
- package/metaData/components/input/example/hooks.tsx +259 -0
- package/metaData/components/input/example/index.tsx +134 -0
- package/metaData/components/input/example/props.tsx +183 -0
- package/metaData/components/keyboard/doc.md +73 -0
- package/metaData/components/keyboard/example/index.tsx +238 -0
- package/metaData/components/load-more/doc.md +48 -0
- package/metaData/components/load-more/example/index.tsx +395 -0
- package/metaData/components/loading/doc.md +34 -0
- package/metaData/components/loading/example/index.tsx +312 -0
- package/metaData/components/masking/doc.md +93 -0
- package/metaData/components/masking/example/index.tsx +410 -0
- package/metaData/components/masking/example/methods.tsx +88 -0
- package/metaData/components/nav-bar/doc.md +40 -0
- package/metaData/components/nav-bar/example/back-icon.tsx +28 -0
- package/metaData/components/nav-bar/example/index.tsx +305 -0
- package/metaData/components/notice-bar/doc.md +34 -0
- package/metaData/components/notice-bar/example/index.tsx +294 -0
- package/metaData/components/notify/doc.md +81 -0
- package/metaData/components/notify/example/index.tsx +171 -0
- package/metaData/components/notify/example/methods.tsx +83 -0
- package/metaData/components/pagination/doc.md +41 -0
- package/metaData/components/pagination/example/arrow.tsx +14 -0
- package/metaData/components/pagination/example/index.tsx +292 -0
- package/metaData/components/picker/doc.md +89 -0
- package/metaData/components/picker/example/index.tsx +217 -0
- package/metaData/components/picker-view/doc.md +57 -0
- package/metaData/components/picker-view/example/cascader.tsx +141 -0
- package/metaData/components/picker-view/example/index.tsx +297 -0
- package/metaData/components/picker-view/example/multi-picker.tsx +66 -0
- package/metaData/components/picker-view/example/picker-cell.tsx +416 -0
- package/metaData/components/popover/doc.md +144 -0
- package/metaData/components/popover/example/index.tsx +23 -0
- package/metaData/components/popover/example/menu.tsx +145 -0
- package/metaData/components/popover/example/popover-inner.tsx +147 -0
- package/metaData/components/popover/example/popover.tsx +268 -0
- package/metaData/components/popup/doc.md +101 -0
- package/metaData/components/popup/example/index.tsx +125 -0
- package/metaData/components/popup/example/methods.tsx +6 -0
- package/metaData/components/popup-swiper/doc.md +108 -0
- package/metaData/components/popup-swiper/example/index.tsx +332 -0
- package/metaData/components/popup-swiper/example/methods.tsx +6 -0
- package/metaData/components/portal/doc.md +14 -0
- package/metaData/components/portal/example/index.tsx +35 -0
- package/metaData/components/progress/doc.md +42 -0
- package/metaData/components/progress/example/index.tsx +256 -0
- package/metaData/components/pull-refresh/doc.md +37 -0
- package/metaData/components/pull-refresh/example/android-pull-refresh.tsx +260 -0
- package/metaData/components/pull-refresh/example/index.tsx +44 -0
- package/metaData/components/pull-refresh/example/ios-pull-refresh.tsx +216 -0
- package/metaData/components/radio/doc.md +89 -0
- package/metaData/components/radio/example/group.tsx +92 -0
- package/metaData/components/radio/example/index.tsx +25 -0
- package/metaData/components/radio/example/radio.tsx +22 -0
- package/metaData/components/rate/doc.md +33 -0
- package/metaData/components/rate/example/index.tsx +281 -0
- package/metaData/components/search-bar/doc.md +97 -0
- package/metaData/components/search-bar/example/association.tsx +89 -0
- package/metaData/components/search-bar/example/cancel-button.tsx +34 -0
- package/metaData/components/search-bar/example/highlight.tsx +66 -0
- package/metaData/components/search-bar/example/index.tsx +251 -0
- package/metaData/components/show-monitor/doc.md +34 -0
- package/metaData/components/show-monitor/example/index.tsx +646 -0
- package/metaData/components/skeleton/doc.md +101 -0
- package/metaData/components/skeleton/example/elements.tsx +290 -0
- package/metaData/components/skeleton/example/index.tsx +96 -0
- package/metaData/components/skeleton/example/skeleton-context.tsx +7 -0
- package/metaData/components/slider/doc.md +42 -0
- package/metaData/components/slider/example/index.tsx +326 -0
- package/metaData/components/slider/example/marks.tsx +50 -0
- package/metaData/components/slider/example/popover.tsx +38 -0
- package/metaData/components/slider/example/thumb.tsx +48 -0
- package/metaData/components/slider/example/useSliderIcon.tsx +38 -0
- package/metaData/components/slider/example/useSliderInit.tsx +170 -0
- package/metaData/components/stepper/doc.md +46 -0
- package/metaData/components/stepper/example/index.tsx +166 -0
- package/metaData/components/stepper/example/useButtonClick.tsx +74 -0
- package/metaData/components/stepper/example/useInputEvent.tsx +65 -0
- package/metaData/components/stepper/example/useValue.tsx +43 -0
- package/metaData/components/steps/doc.md +57 -0
- package/metaData/components/steps/example/index.tsx +123 -0
- package/metaData/components/steps/example/step.tsx +126 -0
- package/metaData/components/sticky/doc.md +45 -0
- package/metaData/components/sticky/example/index.tsx +430 -0
- package/metaData/components/swipe-action/doc.md +44 -0
- package/metaData/components/swipe-action/example/index.tsx +338 -0
- package/metaData/components/swipe-action/example/item.tsx +40 -0
- package/metaData/components/swipe-load/doc.md +43 -0
- package/metaData/components/swipe-load/example/index.tsx +339 -0
- package/metaData/components/swipe-load/example/type.tsx +153 -0
- package/metaData/components/switch/doc.md +38 -0
- package/metaData/components/switch/example/index.tsx +237 -0
- package/metaData/components/tab-bar/doc.md +57 -0
- package/metaData/components/tab-bar/example/index.tsx +18 -0
- package/metaData/components/tab-bar/example/item.tsx +74 -0
- package/metaData/components/tab-bar/example/tab-bar.tsx +105 -0
- package/metaData/components/tabs/doc.md +187 -0
- package/metaData/components/tabs/example/index.tsx +514 -0
- package/metaData/components/tabs/example/tab-cell-underline.tsx +320 -0
- package/metaData/components/tabs/example/tab-cell.tsx +433 -0
- package/metaData/components/tabs/example/tab-pane.tsx +349 -0
- package/metaData/components/tag/doc.md +80 -0
- package/metaData/components/tag/example/index.tsx +23 -0
- package/metaData/components/tag/example/list.tsx +97 -0
- package/metaData/components/tag/example/tag.tsx +85 -0
- package/metaData/components/textarea/doc.md +67 -0
- package/metaData/components/textarea/example/index.tsx +258 -0
- package/metaData/components/toast/doc.md +90 -0
- package/metaData/components/toast/example/index.tsx +335 -0
- package/metaData/components/toast/example/methods.tsx +87 -0
- package/metaData/components/transition/doc.md +19 -0
- package/metaData/components/transition/example/index.tsx +113 -0
- package/metaData/components/uploader/doc.md +82 -0
- package/metaData/components/uploader/example/index.tsx +208 -0
- package/metaData/components-index.json +242 -0
- package/package.json +56 -0
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useRef,
|
|
3
|
+
forwardRef,
|
|
4
|
+
Ref,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
useEffect,
|
|
7
|
+
useCallback,
|
|
8
|
+
useMemo,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import lodashThrottle from 'lodash.throttle';
|
|
11
|
+
import {
|
|
12
|
+
scrollParent,
|
|
13
|
+
checkOverflowVisible,
|
|
14
|
+
checkNormalVisible,
|
|
15
|
+
nextTick,
|
|
16
|
+
cls,
|
|
17
|
+
} from '@arco-design/mobile-utils';
|
|
18
|
+
import { ContextLayout } from '../context-provider';
|
|
19
|
+
|
|
20
|
+
export interface ShowMonitorProps {
|
|
21
|
+
/**
|
|
22
|
+
* 自定义类名
|
|
23
|
+
* @en Custom classname
|
|
24
|
+
*/
|
|
25
|
+
className?: string;
|
|
26
|
+
/**
|
|
27
|
+
* 自定义样式
|
|
28
|
+
* @en Custom stylesheet
|
|
29
|
+
*/
|
|
30
|
+
style?: React.CSSProperties;
|
|
31
|
+
/**
|
|
32
|
+
* 滚动视口选择器
|
|
33
|
+
* @en Scroll viewport selector
|
|
34
|
+
* @default () => window
|
|
35
|
+
*/
|
|
36
|
+
getScrollContainer?: () => HTMLElement | Window | null;
|
|
37
|
+
/**
|
|
38
|
+
* 节流粒度(ms)
|
|
39
|
+
* @en Throttle granularity(ms)
|
|
40
|
+
* @default 300
|
|
41
|
+
*/
|
|
42
|
+
throttle?: number;
|
|
43
|
+
/**
|
|
44
|
+
* 是否监听 resize
|
|
45
|
+
* @en Whether to monitor resize
|
|
46
|
+
* @default true
|
|
47
|
+
*/
|
|
48
|
+
listenResize?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* 是否监听 scroll
|
|
51
|
+
* @en Whether to monitor scroll
|
|
52
|
+
* @default true
|
|
53
|
+
*/
|
|
54
|
+
listenScroll?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* 只监听变化一次,为 true 时仅在进入视口时触发一次 onVisibleChange
|
|
57
|
+
* @en Only listen for changes once, if true, only trigger onVisibleChange once when entering the viewport
|
|
58
|
+
* @default false
|
|
59
|
+
*/
|
|
60
|
+
once?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* 是否为局部滚动
|
|
63
|
+
* @en Whether it is a local scroll
|
|
64
|
+
* @default false
|
|
65
|
+
*/
|
|
66
|
+
overflow?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* 元素进入视口区域触发回调比例,[0-1]
|
|
69
|
+
* @en Threshold of callback triggered when element enters viewport area, [0-1]
|
|
70
|
+
* @default 0
|
|
71
|
+
*/
|
|
72
|
+
threshold?: number;
|
|
73
|
+
/**
|
|
74
|
+
* 预加载提前量 [offsetTop, offsetRight, offsetBottom, offsetLeft]。
|
|
75
|
+
* 如果类型为 number,等效为[number, number, number, number];
|
|
76
|
+
* 如果类型为 [number1, number2], 等效为[number1, 0, number2, 0];
|
|
77
|
+
* 支持 Intersection Observer 的浏览器需要搭配 getScrollContainer, 设置父级容器
|
|
78
|
+
* @en Preload advance amount [offsetTop, offsetRight, offsetBottom, offsetLeft].
|
|
79
|
+
* @en If the type is number, it is equivalent to [number, number, number, number];
|
|
80
|
+
* @en If the type is [number1, number2], it is equivalent to [number1, 0, number2, 0];
|
|
81
|
+
* @en Browsers that support Intersection Observer need to use getScrollContainer to set the parent container
|
|
82
|
+
* @default 0
|
|
83
|
+
*/
|
|
84
|
+
offset?: number | [number, number] | [number, number, number, number];
|
|
85
|
+
/**
|
|
86
|
+
* 子节点
|
|
87
|
+
* @en Children nodes
|
|
88
|
+
*/
|
|
89
|
+
children: React.ReactNode;
|
|
90
|
+
/**
|
|
91
|
+
* 是否禁用监听
|
|
92
|
+
* @en Whether to disable monitoring
|
|
93
|
+
* @default false
|
|
94
|
+
*/
|
|
95
|
+
disabled?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* 进入或离开视口的回调函数
|
|
98
|
+
* @en Callback for entering or leaving the viewport
|
|
99
|
+
*/
|
|
100
|
+
onVisibleChange: (visible: boolean, node: HTMLDivElement) => void;
|
|
101
|
+
/**
|
|
102
|
+
* 点击组件的回调事件
|
|
103
|
+
* @en Callback when clicked
|
|
104
|
+
*/
|
|
105
|
+
onClick?: (e: React.MouseEvent) => void;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface ShowMonitorRef {
|
|
109
|
+
/**
|
|
110
|
+
* 最外层元素 DOM
|
|
111
|
+
* @en The outermost element DOM
|
|
112
|
+
*/
|
|
113
|
+
dom: HTMLDivElement | null;
|
|
114
|
+
/**
|
|
115
|
+
* 忽略元素前后状态,手动检查元素是否在视口内,触发onVisibleChange回调函数
|
|
116
|
+
* @en Ignore the state of the element before and after, manually check whether the element is in the viewport, trigger the onVisibleChange callback function
|
|
117
|
+
*/
|
|
118
|
+
checkVisible: () => boolean;
|
|
119
|
+
/**
|
|
120
|
+
* 重置元素初始可见态为false,并重新对元素可见度发起检测,优先级低于disabled(通常用在对ShowMonitor内部元素变化时发起的重新监听)
|
|
121
|
+
* @en Reset the initial visible state of the element to false, and re-detect the visibility of the element, the priority is lower than 'disabled'(Usually used to re-listen when elements inside ShowMonitor change)
|
|
122
|
+
*/
|
|
123
|
+
flushVisibleStatus: () => void;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
type TListenerEntity = {
|
|
127
|
+
node: HTMLDivElement;
|
|
128
|
+
isVisible: boolean;
|
|
129
|
+
overflow: boolean;
|
|
130
|
+
once: boolean;
|
|
131
|
+
offset: number | [number, number] | [number, number, number, number];
|
|
132
|
+
threshold: number;
|
|
133
|
+
} & Pick<ShowMonitorProps, 'onVisibleChange'>;
|
|
134
|
+
|
|
135
|
+
type TListenerEntityList = {
|
|
136
|
+
[key: string]: TListenerEntity[];
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const LISTEN_FLAG = 'data-show-listened';
|
|
140
|
+
/**
|
|
141
|
+
* 父 dom 节点集合
|
|
142
|
+
* @en Parent dom node collection
|
|
143
|
+
*/
|
|
144
|
+
const wrapperNodeList: (HTMLElement | Window)[] = [];
|
|
145
|
+
/**
|
|
146
|
+
* 同一父节点下监听队列
|
|
147
|
+
* @en Listening queue under the same parent node
|
|
148
|
+
*/
|
|
149
|
+
const listeners: TListenerEntityList = {};
|
|
150
|
+
const onOnceEmittedListeners: TListenerEntityList = {};
|
|
151
|
+
/**
|
|
152
|
+
* Intersection Observer 同一 root 节点下的监听队列
|
|
153
|
+
* @en Intersection Observer Listening queue under the same root node
|
|
154
|
+
*/
|
|
155
|
+
const ioListeners: {
|
|
156
|
+
root: HTMLElement;
|
|
157
|
+
key: string;
|
|
158
|
+
listener: IntersectionObserver;
|
|
159
|
+
}[] = [];
|
|
160
|
+
/**
|
|
161
|
+
* Intersection Observer 监听 visible 状态队列
|
|
162
|
+
* @en Intersection Observer listens to the visible status queue
|
|
163
|
+
*/
|
|
164
|
+
const ioVisibleList: ({
|
|
165
|
+
node: HTMLDivElement;
|
|
166
|
+
isVisible: boolean;
|
|
167
|
+
once: boolean;
|
|
168
|
+
} & Pick<ShowMonitorProps, 'onVisibleChange'>)[] = [];
|
|
169
|
+
let throttlingVisibleChange: () => void;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 通过滚动事件监测 children 是否进入视口或离开视口。
|
|
173
|
+
* @en Use scroll events to monitor whether children enter or leave the viewport.
|
|
174
|
+
* @type 其他
|
|
175
|
+
* @type_en Others
|
|
176
|
+
* @name 滚动视口监听
|
|
177
|
+
* @name_en ShowMonitor
|
|
178
|
+
*/
|
|
179
|
+
const ShowMonitor = forwardRef((props: ShowMonitorProps, ref: Ref<ShowMonitorRef>) => {
|
|
180
|
+
const {
|
|
181
|
+
className,
|
|
182
|
+
style,
|
|
183
|
+
getScrollContainer,
|
|
184
|
+
throttle = 300,
|
|
185
|
+
listenResize = true,
|
|
186
|
+
listenScroll = true,
|
|
187
|
+
overflow = false,
|
|
188
|
+
offset = 0,
|
|
189
|
+
threshold = 0,
|
|
190
|
+
once = false,
|
|
191
|
+
children = null,
|
|
192
|
+
disabled = false,
|
|
193
|
+
onVisibleChange,
|
|
194
|
+
onClick,
|
|
195
|
+
} = props;
|
|
196
|
+
const domRef = useRef<HTMLDivElement | null>(null);
|
|
197
|
+
/**
|
|
198
|
+
* 滚动容器父级元素
|
|
199
|
+
* @en Scroll container parent element
|
|
200
|
+
*/
|
|
201
|
+
const scrollPort = useRef<HTMLElement | Window | null>(null);
|
|
202
|
+
/**
|
|
203
|
+
* 局部滚动元素父节点
|
|
204
|
+
* @en Parent node of the local scroll element
|
|
205
|
+
*/
|
|
206
|
+
const domRefParent = useRef<HTMLElement | Document | null>(null);
|
|
207
|
+
/**
|
|
208
|
+
* 保存当前节点信息,类似于 class component 中 this
|
|
209
|
+
* @en Save current node information, similar to this in class component
|
|
210
|
+
*/
|
|
211
|
+
const listener = useRef<TListenerEntity | null>(null);
|
|
212
|
+
/**
|
|
213
|
+
* Intersection Observer 实例化对象
|
|
214
|
+
* @en Instantiated object of Intersection Observer
|
|
215
|
+
*/
|
|
216
|
+
const io = useRef<IntersectionObserver | null>(null);
|
|
217
|
+
/**
|
|
218
|
+
* 父容器唯一 key(wrapperNodeList index)
|
|
219
|
+
* @en The unique key of the parent container (wrapperNodeList index)
|
|
220
|
+
*/
|
|
221
|
+
const wrapperKey = useRef<number>(-1);
|
|
222
|
+
const isChildrenExist = useRef(Boolean(children));
|
|
223
|
+
const isSupportNativeApi = useMemo(() => typeof IntersectionObserver === 'function', []); // 是否支持 Intersection Observer
|
|
224
|
+
|
|
225
|
+
useImperativeHandle(ref, () => ({
|
|
226
|
+
dom: domRef.current,
|
|
227
|
+
/**
|
|
228
|
+
* 提供检查元素是否可见的api
|
|
229
|
+
* @en Provides an api to check whether the element is visible
|
|
230
|
+
*/
|
|
231
|
+
checkVisible: () =>
|
|
232
|
+
checkVisible(
|
|
233
|
+
{
|
|
234
|
+
node: domRef.current!,
|
|
235
|
+
overflow,
|
|
236
|
+
onVisibleChange,
|
|
237
|
+
offset,
|
|
238
|
+
threshold,
|
|
239
|
+
} as TListenerEntity,
|
|
240
|
+
true,
|
|
241
|
+
) as boolean,
|
|
242
|
+
/**
|
|
243
|
+
* 重置元素初始可见态为false,并重新对元素可见度发起检测,优先级低于disabled(通常用在对ShowMonitor内部元素变化时发起的重新监听)
|
|
244
|
+
* @en Reset the initial visible state of the element to false, and re-detect the visibility of the element, the priority is lower than 'disabled'(Usually used to re-listen when elements inside ShowMonitor change)
|
|
245
|
+
*/
|
|
246
|
+
flushVisibleStatus() {
|
|
247
|
+
if (isSupportNativeApi) {
|
|
248
|
+
disabled ? ioUnobserve() : ioObserve();
|
|
249
|
+
} else if (listener.current) {
|
|
250
|
+
const key = wrapperKey.current;
|
|
251
|
+
if (once && onOnceEmittedListeners?.[key]) {
|
|
252
|
+
onOnceEmittedListeners[key] = onOnceEmittedListeners[key].filter(
|
|
253
|
+
emitListener => emitListener !== listener.current,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
if (
|
|
257
|
+
!disabled &&
|
|
258
|
+
!listeners[key].find(_listener => _listener === listener.current)
|
|
259
|
+
) {
|
|
260
|
+
listeners[key].push(listener.current);
|
|
261
|
+
}
|
|
262
|
+
!disabled && checkVisible(listener.current);
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
}));
|
|
266
|
+
|
|
267
|
+
function checkVisible(component: TListenerEntity, ignoreCheckPreVisibleStatus = false) {
|
|
268
|
+
const {
|
|
269
|
+
node,
|
|
270
|
+
overflow: compOverflow,
|
|
271
|
+
once: compOnce,
|
|
272
|
+
onVisibleChange: onCompVisibleChange,
|
|
273
|
+
isVisible: preVisible,
|
|
274
|
+
} = component;
|
|
275
|
+
if (!(node instanceof HTMLElement)) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const parent = scrollParent(node);
|
|
279
|
+
/**
|
|
280
|
+
* 当前元素为非 document 元素,即为局部滚动
|
|
281
|
+
* @en If the current element is a non-document element, it's local scrolling
|
|
282
|
+
*/
|
|
283
|
+
const isOverflow =
|
|
284
|
+
compOverflow &&
|
|
285
|
+
[node.ownerDocument, document, document.documentElement].indexOf(parent!) === -1;
|
|
286
|
+
/**
|
|
287
|
+
* 检查元素是否可见
|
|
288
|
+
* @en Check if element is visible
|
|
289
|
+
*/
|
|
290
|
+
const curVisible = isOverflow
|
|
291
|
+
? checkOverflowVisible<TListenerEntity>(component, parent)
|
|
292
|
+
: checkNormalVisible<TListenerEntity>(component);
|
|
293
|
+
/**
|
|
294
|
+
* 忽略前后可见状态判断
|
|
295
|
+
* @en Ignore the visible state judgment before and after
|
|
296
|
+
*/
|
|
297
|
+
if (ignoreCheckPreVisibleStatus) {
|
|
298
|
+
return curVisible;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* 当前元素 visible 对比之前发生改变,触发回调函数
|
|
302
|
+
* @en The current element visible is changed before the comparison, and the callback function is triggered
|
|
303
|
+
*/
|
|
304
|
+
curVisible !== preVisible &&
|
|
305
|
+
handleCheckChildrenExist() &&
|
|
306
|
+
onCompVisibleChange(curVisible, node);
|
|
307
|
+
component.isVisible = curVisible;
|
|
308
|
+
const key = wrapperKey.current;
|
|
309
|
+
/**
|
|
310
|
+
* 监听一次后加入 pendingList 队列,随后被 listeners 过滤掉
|
|
311
|
+
* @en After listening once, it is added to the pendingList queue, and then filtered out by listeners
|
|
312
|
+
*/
|
|
313
|
+
if (!onOnceEmittedListeners[key]) {
|
|
314
|
+
onOnceEmittedListeners[key] = [];
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* 当前元素不可见 -> 可见,且 once, 触发回调函数
|
|
318
|
+
* @en The current element is invisible -> visible, and once, triggers the callback function
|
|
319
|
+
*/
|
|
320
|
+
curVisible && !preVisible && compOnce && onOnceEmittedListeners[key].push(component);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const checkVisibleHandler = useCallback(() => {
|
|
324
|
+
const key = wrapperKey.current;
|
|
325
|
+
const curListeners = listeners[key] || [];
|
|
326
|
+
curListeners.forEach(listen => checkVisible(listen));
|
|
327
|
+
listeners[key] = curListeners.filter(
|
|
328
|
+
item => onOnceEmittedListeners[key].indexOf(item) === -1,
|
|
329
|
+
);
|
|
330
|
+
onOnceEmittedListeners[key] = [];
|
|
331
|
+
}, []);
|
|
332
|
+
|
|
333
|
+
function handleCheckChildrenExist(target?: Element) {
|
|
334
|
+
const dom = target || domRef.current;
|
|
335
|
+
return isChildrenExist.current && dom?.children.length;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function handleObserverStatusChange(entries: IntersectionObserverEntry[]) {
|
|
339
|
+
entries.forEach(entry => {
|
|
340
|
+
const { isIntersecting, target } = entry;
|
|
341
|
+
const visibleItem = ioVisibleList.find(item => item.node === target);
|
|
342
|
+
if (visibleItem) {
|
|
343
|
+
const {
|
|
344
|
+
isVisible: curVisible,
|
|
345
|
+
onVisibleChange: onCompVisibleChange,
|
|
346
|
+
once: onceEmit,
|
|
347
|
+
} = visibleItem;
|
|
348
|
+
/**
|
|
349
|
+
* 当前元素 visible 对比之前发生改变,触发回调函数
|
|
350
|
+
* @en Callback when the visible status of current element changes before the comparison
|
|
351
|
+
*/
|
|
352
|
+
isIntersecting !== curVisible &&
|
|
353
|
+
handleCheckChildrenExist(target) &&
|
|
354
|
+
onCompVisibleChange(isIntersecting, target as HTMLDivElement);
|
|
355
|
+
/**
|
|
356
|
+
* 当前元素状态由不可见变为可见,且只触发一次
|
|
357
|
+
* @en The current element is invisible -> visible, and once, triggers the callback
|
|
358
|
+
*/
|
|
359
|
+
isIntersecting &&
|
|
360
|
+
!curVisible &&
|
|
361
|
+
onceEmit &&
|
|
362
|
+
target &&
|
|
363
|
+
ioUnobserve(target as HTMLDivElement);
|
|
364
|
+
visibleItem.isVisible = isIntersecting;
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* 获取 io 单例
|
|
371
|
+
* @en Get the io singleton
|
|
372
|
+
*/
|
|
373
|
+
function getIOSingleton(ioOptions: {
|
|
374
|
+
root: HTMLElement;
|
|
375
|
+
rootMargin: string;
|
|
376
|
+
threshold: number;
|
|
377
|
+
}) {
|
|
378
|
+
const { root, rootMargin, threshold: ioThreshold } = ioOptions;
|
|
379
|
+
const ioKey = JSON.stringify({ rootMargin, threshold: ioThreshold });
|
|
380
|
+
|
|
381
|
+
const _ioListener = ioListeners.find(
|
|
382
|
+
ioListener => ioListener.root === root && ioListener.key === ioKey,
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
if (!_ioListener) {
|
|
386
|
+
ioListeners.push({
|
|
387
|
+
root,
|
|
388
|
+
key: ioKey,
|
|
389
|
+
listener: (io.current = new IntersectionObserver(
|
|
390
|
+
handleObserverStatusChange,
|
|
391
|
+
ioOptions,
|
|
392
|
+
)),
|
|
393
|
+
});
|
|
394
|
+
} else {
|
|
395
|
+
io.current = _ioListener.listener;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function ioObserve() {
|
|
400
|
+
if (domRef.current && io.current) {
|
|
401
|
+
const curIdx = ioVisibleList.findIndex(
|
|
402
|
+
ioVisibleItem => ioVisibleItem.node === domRef.current,
|
|
403
|
+
);
|
|
404
|
+
if (curIdx !== -1) {
|
|
405
|
+
ioVisibleList[curIdx].isVisible = false;
|
|
406
|
+
} else {
|
|
407
|
+
ioVisibleList.push({
|
|
408
|
+
node: domRef.current,
|
|
409
|
+
isVisible: false,
|
|
410
|
+
once,
|
|
411
|
+
onVisibleChange,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
io.current.observe(domRef.current);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function ioUnobserve(target?: HTMLDivElement) {
|
|
419
|
+
const targetNode = target || domRef.current;
|
|
420
|
+
if (io.current && targetNode) {
|
|
421
|
+
const curIdx = ioVisibleList.findIndex(
|
|
422
|
+
ioVisibleItem => ioVisibleItem.node === targetNode,
|
|
423
|
+
);
|
|
424
|
+
if (curIdx !== -1) {
|
|
425
|
+
ioVisibleList.splice(curIdx, 1);
|
|
426
|
+
}
|
|
427
|
+
io.current.unobserve(targetNode!);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* 非首次下disabled变化时,重新监听元素
|
|
433
|
+
* @en Re-listen to the element when it is not the first time that the disabled status changes,
|
|
434
|
+
*/
|
|
435
|
+
useEffect(() => {
|
|
436
|
+
if (isSupportNativeApi) {
|
|
437
|
+
// 非首次 render 下
|
|
438
|
+
// @en Not for the first time
|
|
439
|
+
disabled ? ioUnobserve() : ioObserve();
|
|
440
|
+
} else {
|
|
441
|
+
// 非首次 render 下
|
|
442
|
+
// @en Not for the first time
|
|
443
|
+
if (wrapperKey.current !== -1 && listener.current && listeners[wrapperKey.current]) {
|
|
444
|
+
// 禁用监听,找到对应listener并删除
|
|
445
|
+
// @en Disable listening, find the corresponding listener and delete it
|
|
446
|
+
if (disabled) {
|
|
447
|
+
const isExist = listeners[wrapperKey.current].findIndex(
|
|
448
|
+
item => item.node === domRef.current,
|
|
449
|
+
);
|
|
450
|
+
if (isExist !== -1) {
|
|
451
|
+
listeners[wrapperKey.current].splice(0, 1);
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
// 开启监听
|
|
455
|
+
// @en Start to listen
|
|
456
|
+
listeners[wrapperKey.current].push(listener.current);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}, [disabled]);
|
|
461
|
+
|
|
462
|
+
useEffect(() => {
|
|
463
|
+
isChildrenExist.current = Boolean(children);
|
|
464
|
+
}, [children]);
|
|
465
|
+
|
|
466
|
+
useEffect(() => {
|
|
467
|
+
scrollPort.current = getScrollContainer ? getScrollContainer() : window;
|
|
468
|
+
if (!scrollPort.current) return;
|
|
469
|
+
const wrapperNodeIndex = wrapperNodeList.indexOf(scrollPort.current);
|
|
470
|
+
if (wrapperNodeIndex === -1) {
|
|
471
|
+
// 当前滚动视口元素不在 wrapperNodeList 中, 入队列
|
|
472
|
+
// @en The current scroll viewport element is not in the wrapperNodeList, push it into the list
|
|
473
|
+
wrapperNodeList.push(scrollPort.current);
|
|
474
|
+
wrapperKey.current = wrapperNodeList.length - 1;
|
|
475
|
+
} else {
|
|
476
|
+
// 取当前 index 作为 key
|
|
477
|
+
// @en Take the current index as the key
|
|
478
|
+
wrapperKey.current = wrapperNodeIndex;
|
|
479
|
+
}
|
|
480
|
+
const curWrapperKey = wrapperKey.current;
|
|
481
|
+
if (isSupportNativeApi) {
|
|
482
|
+
if (domRef.current) {
|
|
483
|
+
domRefParent.current = scrollParent(domRef.current);
|
|
484
|
+
const parent = domRefParent.current;
|
|
485
|
+
const isHTMLElement =
|
|
486
|
+
[domRef.current.ownerDocument, document, document.documentElement].indexOf(
|
|
487
|
+
parent!,
|
|
488
|
+
) === -1;
|
|
489
|
+
const overflowRoot = isHTMLElement ? parent : null;
|
|
490
|
+
const root = (
|
|
491
|
+
overflow
|
|
492
|
+
? overflowRoot
|
|
493
|
+
: scrollPort.current === window
|
|
494
|
+
? null
|
|
495
|
+
: scrollPort.current
|
|
496
|
+
) as HTMLElement;
|
|
497
|
+
let rootMargin = '';
|
|
498
|
+
if (Array.isArray(offset)) {
|
|
499
|
+
if (offset.length === 2) {
|
|
500
|
+
rootMargin = `${offset[0]}px 0px ${offset[1]}px`;
|
|
501
|
+
} else {
|
|
502
|
+
offset.forEach((val, index) => {
|
|
503
|
+
const space = index === 0 ? '' : ' ';
|
|
504
|
+
rootMargin += `${space}${val}px`;
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
} else {
|
|
508
|
+
rootMargin = `${offset}px`;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
getIOSingleton({
|
|
512
|
+
root,
|
|
513
|
+
rootMargin,
|
|
514
|
+
threshold,
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
!disabled && ioObserve();
|
|
518
|
+
}
|
|
519
|
+
} else {
|
|
520
|
+
// 节流后回调函数
|
|
521
|
+
// @en Callback after throttling
|
|
522
|
+
throttlingVisibleChange = checkVisibleHandler;
|
|
523
|
+
throttle &&
|
|
524
|
+
(throttlingVisibleChange = lodashThrottle(throttlingVisibleChange, throttle));
|
|
525
|
+
if (overflow) {
|
|
526
|
+
if (domRef.current) {
|
|
527
|
+
domRefParent.current = scrollParent(domRef.current);
|
|
528
|
+
const parent = domRefParent.current;
|
|
529
|
+
if (
|
|
530
|
+
parent &&
|
|
531
|
+
parent instanceof HTMLElement &&
|
|
532
|
+
typeof parent.getAttribute === 'function'
|
|
533
|
+
) {
|
|
534
|
+
const listenerCount =
|
|
535
|
+
1 + Number(parent.getAttribute(LISTEN_FLAG) as string);
|
|
536
|
+
if (!listeners[curWrapperKey]) {
|
|
537
|
+
listeners[curWrapperKey] = [];
|
|
538
|
+
}
|
|
539
|
+
if (listenerCount === 1) {
|
|
540
|
+
// listener 数量为1时监听事件(列表中插入 ShowMonitor 时做判断)
|
|
541
|
+
// @en Listen when the number of listeners is 1 (judging when ShowMonitor is inserted into the list)
|
|
542
|
+
parent.addEventListener('scroll', throttlingVisibleChange);
|
|
543
|
+
}
|
|
544
|
+
parent.setAttribute(LISTEN_FLAG, String(listenerCount));
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
} else if (
|
|
548
|
+
!listeners[curWrapperKey] ||
|
|
549
|
+
listeners[curWrapperKey].length === 0 ||
|
|
550
|
+
// 相同curWrapperKey内全部为overflow场景,需要单独注册scroll事件
|
|
551
|
+
// @en It need to register the scroll event separately when all values in the same curWrapperKey are overflow
|
|
552
|
+
(listeners[curWrapperKey] && listeners[curWrapperKey].every(it => it.overflow))
|
|
553
|
+
) {
|
|
554
|
+
if (!listeners[curWrapperKey]) {
|
|
555
|
+
listeners[curWrapperKey] = [];
|
|
556
|
+
}
|
|
557
|
+
listenScroll &&
|
|
558
|
+
scrollPort.current.addEventListener('scroll', throttlingVisibleChange);
|
|
559
|
+
listenResize &&
|
|
560
|
+
scrollPort.current.addEventListener('resize', throttlingVisibleChange);
|
|
561
|
+
}
|
|
562
|
+
listener.current = {
|
|
563
|
+
node: domRef.current!,
|
|
564
|
+
isVisible: false,
|
|
565
|
+
overflow,
|
|
566
|
+
once,
|
|
567
|
+
offset,
|
|
568
|
+
threshold,
|
|
569
|
+
onVisibleChange,
|
|
570
|
+
};
|
|
571
|
+
// 将当前 listener 存入 listeners
|
|
572
|
+
!disabled && listeners[curWrapperKey].push(listener.current);
|
|
573
|
+
nextTick(() => {
|
|
574
|
+
!disabled && checkVisible(listener.current!);
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
}, []);
|
|
578
|
+
|
|
579
|
+
useEffect(() => {
|
|
580
|
+
return () => {
|
|
581
|
+
if (isSupportNativeApi) {
|
|
582
|
+
ioUnobserve();
|
|
583
|
+
} else {
|
|
584
|
+
if (overflow) {
|
|
585
|
+
const parent = domRefParent.current;
|
|
586
|
+
if (
|
|
587
|
+
parent &&
|
|
588
|
+
parent instanceof HTMLElement &&
|
|
589
|
+
typeof parent.getAttribute === 'function'
|
|
590
|
+
) {
|
|
591
|
+
const listenerCount = Number(parent.getAttribute(LISTEN_FLAG) || 0) - 1;
|
|
592
|
+
if (listenerCount === 0) {
|
|
593
|
+
// 如果监听队列中无节点,解绑事件
|
|
594
|
+
// @en If there is no node in the listening queue, unbind the event
|
|
595
|
+
parent.removeEventListener('scroll', throttlingVisibleChange);
|
|
596
|
+
} else {
|
|
597
|
+
// 如果监听队列中有元素节点,将 dataset 减1
|
|
598
|
+
// @en If there are element nodes in the listening queue, decrement dataset by 1
|
|
599
|
+
parent.setAttribute(LISTEN_FLAG, String(listenerCount));
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
const key = wrapperKey.current;
|
|
604
|
+
const curListeners = listeners[key];
|
|
605
|
+
if (curListeners) {
|
|
606
|
+
// 不在监听队列中,移除
|
|
607
|
+
// @en Not in the listening queue, remove it
|
|
608
|
+
const index = curListeners.indexOf(listener.current!);
|
|
609
|
+
index !== -1 && curListeners.splice(index, 1);
|
|
610
|
+
// 监听队列为空,解绑 scroll、resize 事件
|
|
611
|
+
// @en When the listening queue is empty, unbind scroll and resize events
|
|
612
|
+
if (curListeners.length === 0) {
|
|
613
|
+
delete listeners[key];
|
|
614
|
+
if (scrollPort.current) {
|
|
615
|
+
scrollPort.current.removeEventListener(
|
|
616
|
+
'scroll',
|
|
617
|
+
throttlingVisibleChange,
|
|
618
|
+
);
|
|
619
|
+
scrollPort.current.removeEventListener(
|
|
620
|
+
'resize',
|
|
621
|
+
throttlingVisibleChange,
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
}, []);
|
|
629
|
+
|
|
630
|
+
return (
|
|
631
|
+
<ContextLayout>
|
|
632
|
+
{({ prefixCls }) => (
|
|
633
|
+
<div
|
|
634
|
+
className={cls(`${prefixCls}-show-monitor`, className)}
|
|
635
|
+
style={style}
|
|
636
|
+
ref={domRef}
|
|
637
|
+
onClick={onClick}
|
|
638
|
+
>
|
|
639
|
+
{children}
|
|
640
|
+
</div>
|
|
641
|
+
)}
|
|
642
|
+
</ContextLayout>
|
|
643
|
+
);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
export default ShowMonitor;
|