@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.
Files changed (191) hide show
  1. package/README.md +85 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +244 -0
  4. package/metaData/components/action-sheet/doc.md +102 -0
  5. package/metaData/components/action-sheet/example/index.tsx +199 -0
  6. package/metaData/components/action-sheet/example/methods.tsx +6 -0
  7. package/metaData/components/avatar/doc.md +57 -0
  8. package/metaData/components/avatar/example/group.tsx +69 -0
  9. package/metaData/components/avatar/example/index.tsx +196 -0
  10. package/metaData/components/badge/doc.md +28 -0
  11. package/metaData/components/badge/example/index.tsx +128 -0
  12. package/metaData/components/button/doc.md +45 -0
  13. package/metaData/components/button/example/index.tsx +264 -0
  14. package/metaData/components/carousel/doc.md +89 -0
  15. package/metaData/components/carousel/example/index.tsx +1481 -0
  16. package/metaData/components/cell/doc.md +68 -0
  17. package/metaData/components/cell/example/arrow.tsx +13 -0
  18. package/metaData/components/cell/example/cell.tsx +71 -0
  19. package/metaData/components/cell/example/group.tsx +63 -0
  20. package/metaData/components/cell/example/index.tsx +19 -0
  21. package/metaData/components/checkbox/doc.md +91 -0
  22. package/metaData/components/checkbox/example/checkbox.tsx +158 -0
  23. package/metaData/components/checkbox/example/group.tsx +103 -0
  24. package/metaData/components/checkbox/example/index.tsx +25 -0
  25. package/metaData/components/circle-progress/doc.md +40 -0
  26. package/metaData/components/circle-progress/example/index.tsx +323 -0
  27. package/metaData/components/collapse/doc.md +74 -0
  28. package/metaData/components/collapse/example/collapse.tsx +183 -0
  29. package/metaData/components/collapse/example/group.tsx +136 -0
  30. package/metaData/components/collapse/example/index.tsx +16 -0
  31. package/metaData/components/collapse/example/utils.tsx +20 -0
  32. package/metaData/components/context-provider/doc.md +44 -0
  33. package/metaData/components/context-provider/example/index.tsx +208 -0
  34. package/metaData/components/count-down/doc.md +40 -0
  35. package/metaData/components/count-down/example/hooks.tsx +118 -0
  36. package/metaData/components/count-down/example/index.tsx +63 -0
  37. package/metaData/components/date-picker/doc.md +102 -0
  38. package/metaData/components/date-picker/example/index.tsx +545 -0
  39. package/metaData/components/dialog/doc.md +200 -0
  40. package/metaData/components/dialog/example/index.tsx +328 -0
  41. package/metaData/components/dialog/example/methods.tsx +56 -0
  42. package/metaData/components/divider/doc.md +25 -0
  43. package/metaData/components/divider/example/index.tsx +87 -0
  44. package/metaData/components/dropdown/doc.md +80 -0
  45. package/metaData/components/dropdown/example/dropdown.tsx +308 -0
  46. package/metaData/components/dropdown/example/index.tsx +19 -0
  47. package/metaData/components/dropdown/example/options.tsx +103 -0
  48. package/metaData/components/dropdown-menu/doc.md +91 -0
  49. package/metaData/components/dropdown-menu/example/dropdown-menu.tsx +270 -0
  50. package/metaData/components/dropdown-menu/example/index.tsx +16 -0
  51. package/metaData/components/ellipsis/doc.md +33 -0
  52. package/metaData/components/ellipsis/example/index.tsx +93 -0
  53. package/metaData/components/ellipsis/example/js-ellipsis.tsx +229 -0
  54. package/metaData/components/ellipsis/example/native-ellipsis.tsx +48 -0
  55. package/metaData/components/form/doc.md +90 -0
  56. package/metaData/components/form/example/form-item-context.tsx +8 -0
  57. package/metaData/components/form/example/form-item.tsx +378 -0
  58. package/metaData/components/form/example/index.tsx +87 -0
  59. package/metaData/components/form/example/linked-container.tsx +66 -0
  60. package/metaData/components/grid/doc.md +39 -0
  61. package/metaData/components/grid/example/index.tsx +252 -0
  62. package/metaData/components/icon/doc.md +15 -0
  63. package/metaData/components/icon/example/index.tsx +32 -0
  64. package/metaData/components/image/doc.md +62 -0
  65. package/metaData/components/image/example/index.tsx +528 -0
  66. package/metaData/components/image-picker/doc.md +116 -0
  67. package/metaData/components/image-picker/example/add-icon.tsx +14 -0
  68. package/metaData/components/image-picker/example/index.tsx +175 -0
  69. package/metaData/components/image-preview/doc.md +119 -0
  70. package/metaData/components/image-preview/example/index.tsx +1290 -0
  71. package/metaData/components/image-preview/example/methods.tsx +72 -0
  72. package/metaData/components/index-bar/doc.md +83 -0
  73. package/metaData/components/index-bar/example/group.tsx +93 -0
  74. package/metaData/components/index-bar/example/index.tsx +271 -0
  75. package/metaData/components/index-bar/example/side-bar.tsx +82 -0
  76. package/metaData/components/input/doc.md +60 -0
  77. package/metaData/components/input/example/hooks.tsx +259 -0
  78. package/metaData/components/input/example/index.tsx +134 -0
  79. package/metaData/components/input/example/props.tsx +183 -0
  80. package/metaData/components/keyboard/doc.md +73 -0
  81. package/metaData/components/keyboard/example/index.tsx +238 -0
  82. package/metaData/components/load-more/doc.md +48 -0
  83. package/metaData/components/load-more/example/index.tsx +395 -0
  84. package/metaData/components/loading/doc.md +34 -0
  85. package/metaData/components/loading/example/index.tsx +312 -0
  86. package/metaData/components/masking/doc.md +93 -0
  87. package/metaData/components/masking/example/index.tsx +410 -0
  88. package/metaData/components/masking/example/methods.tsx +88 -0
  89. package/metaData/components/nav-bar/doc.md +40 -0
  90. package/metaData/components/nav-bar/example/back-icon.tsx +28 -0
  91. package/metaData/components/nav-bar/example/index.tsx +305 -0
  92. package/metaData/components/notice-bar/doc.md +34 -0
  93. package/metaData/components/notice-bar/example/index.tsx +294 -0
  94. package/metaData/components/notify/doc.md +81 -0
  95. package/metaData/components/notify/example/index.tsx +171 -0
  96. package/metaData/components/notify/example/methods.tsx +83 -0
  97. package/metaData/components/pagination/doc.md +41 -0
  98. package/metaData/components/pagination/example/arrow.tsx +14 -0
  99. package/metaData/components/pagination/example/index.tsx +292 -0
  100. package/metaData/components/picker/doc.md +89 -0
  101. package/metaData/components/picker/example/index.tsx +217 -0
  102. package/metaData/components/picker-view/doc.md +57 -0
  103. package/metaData/components/picker-view/example/cascader.tsx +141 -0
  104. package/metaData/components/picker-view/example/index.tsx +297 -0
  105. package/metaData/components/picker-view/example/multi-picker.tsx +66 -0
  106. package/metaData/components/picker-view/example/picker-cell.tsx +416 -0
  107. package/metaData/components/popover/doc.md +144 -0
  108. package/metaData/components/popover/example/index.tsx +23 -0
  109. package/metaData/components/popover/example/menu.tsx +145 -0
  110. package/metaData/components/popover/example/popover-inner.tsx +147 -0
  111. package/metaData/components/popover/example/popover.tsx +268 -0
  112. package/metaData/components/popup/doc.md +101 -0
  113. package/metaData/components/popup/example/index.tsx +125 -0
  114. package/metaData/components/popup/example/methods.tsx +6 -0
  115. package/metaData/components/popup-swiper/doc.md +108 -0
  116. package/metaData/components/popup-swiper/example/index.tsx +332 -0
  117. package/metaData/components/popup-swiper/example/methods.tsx +6 -0
  118. package/metaData/components/portal/doc.md +14 -0
  119. package/metaData/components/portal/example/index.tsx +35 -0
  120. package/metaData/components/progress/doc.md +42 -0
  121. package/metaData/components/progress/example/index.tsx +256 -0
  122. package/metaData/components/pull-refresh/doc.md +37 -0
  123. package/metaData/components/pull-refresh/example/android-pull-refresh.tsx +260 -0
  124. package/metaData/components/pull-refresh/example/index.tsx +44 -0
  125. package/metaData/components/pull-refresh/example/ios-pull-refresh.tsx +216 -0
  126. package/metaData/components/radio/doc.md +89 -0
  127. package/metaData/components/radio/example/group.tsx +92 -0
  128. package/metaData/components/radio/example/index.tsx +25 -0
  129. package/metaData/components/radio/example/radio.tsx +22 -0
  130. package/metaData/components/rate/doc.md +33 -0
  131. package/metaData/components/rate/example/index.tsx +281 -0
  132. package/metaData/components/search-bar/doc.md +97 -0
  133. package/metaData/components/search-bar/example/association.tsx +89 -0
  134. package/metaData/components/search-bar/example/cancel-button.tsx +34 -0
  135. package/metaData/components/search-bar/example/highlight.tsx +66 -0
  136. package/metaData/components/search-bar/example/index.tsx +251 -0
  137. package/metaData/components/show-monitor/doc.md +34 -0
  138. package/metaData/components/show-monitor/example/index.tsx +646 -0
  139. package/metaData/components/skeleton/doc.md +101 -0
  140. package/metaData/components/skeleton/example/elements.tsx +290 -0
  141. package/metaData/components/skeleton/example/index.tsx +96 -0
  142. package/metaData/components/skeleton/example/skeleton-context.tsx +7 -0
  143. package/metaData/components/slider/doc.md +42 -0
  144. package/metaData/components/slider/example/index.tsx +326 -0
  145. package/metaData/components/slider/example/marks.tsx +50 -0
  146. package/metaData/components/slider/example/popover.tsx +38 -0
  147. package/metaData/components/slider/example/thumb.tsx +48 -0
  148. package/metaData/components/slider/example/useSliderIcon.tsx +38 -0
  149. package/metaData/components/slider/example/useSliderInit.tsx +170 -0
  150. package/metaData/components/stepper/doc.md +46 -0
  151. package/metaData/components/stepper/example/index.tsx +166 -0
  152. package/metaData/components/stepper/example/useButtonClick.tsx +74 -0
  153. package/metaData/components/stepper/example/useInputEvent.tsx +65 -0
  154. package/metaData/components/stepper/example/useValue.tsx +43 -0
  155. package/metaData/components/steps/doc.md +57 -0
  156. package/metaData/components/steps/example/index.tsx +123 -0
  157. package/metaData/components/steps/example/step.tsx +126 -0
  158. package/metaData/components/sticky/doc.md +45 -0
  159. package/metaData/components/sticky/example/index.tsx +430 -0
  160. package/metaData/components/swipe-action/doc.md +44 -0
  161. package/metaData/components/swipe-action/example/index.tsx +338 -0
  162. package/metaData/components/swipe-action/example/item.tsx +40 -0
  163. package/metaData/components/swipe-load/doc.md +43 -0
  164. package/metaData/components/swipe-load/example/index.tsx +339 -0
  165. package/metaData/components/swipe-load/example/type.tsx +153 -0
  166. package/metaData/components/switch/doc.md +38 -0
  167. package/metaData/components/switch/example/index.tsx +237 -0
  168. package/metaData/components/tab-bar/doc.md +57 -0
  169. package/metaData/components/tab-bar/example/index.tsx +18 -0
  170. package/metaData/components/tab-bar/example/item.tsx +74 -0
  171. package/metaData/components/tab-bar/example/tab-bar.tsx +105 -0
  172. package/metaData/components/tabs/doc.md +187 -0
  173. package/metaData/components/tabs/example/index.tsx +514 -0
  174. package/metaData/components/tabs/example/tab-cell-underline.tsx +320 -0
  175. package/metaData/components/tabs/example/tab-cell.tsx +433 -0
  176. package/metaData/components/tabs/example/tab-pane.tsx +349 -0
  177. package/metaData/components/tag/doc.md +80 -0
  178. package/metaData/components/tag/example/index.tsx +23 -0
  179. package/metaData/components/tag/example/list.tsx +97 -0
  180. package/metaData/components/tag/example/tag.tsx +85 -0
  181. package/metaData/components/textarea/doc.md +67 -0
  182. package/metaData/components/textarea/example/index.tsx +258 -0
  183. package/metaData/components/toast/doc.md +90 -0
  184. package/metaData/components/toast/example/index.tsx +335 -0
  185. package/metaData/components/toast/example/methods.tsx +87 -0
  186. package/metaData/components/transition/doc.md +19 -0
  187. package/metaData/components/transition/example/index.tsx +113 -0
  188. package/metaData/components/uploader/doc.md +82 -0
  189. package/metaData/components/uploader/example/index.tsx +208 -0
  190. package/metaData/components-index.json +242 -0
  191. package/package.json +56 -0
@@ -0,0 +1,416 @@
1
+ import React, {
2
+ useState,
3
+ useLayoutEffect,
4
+ useMemo,
5
+ useRef,
6
+ useCallback,
7
+ useEffect,
8
+ forwardRef,
9
+ Ref,
10
+ useImperativeHandle,
11
+ } from 'react';
12
+ import { cls } from '@arco-design/mobile-utils';
13
+ import { ValueType, PickerData, PickerCellMovingStatus } from '../type';
14
+ import { getStyleWithVendor, useMountedState, useRefState } from '../../_helpers';
15
+
16
+ export interface PickerCellProps {
17
+ prefixCls: string;
18
+ style?: React.CSSProperties;
19
+ data: PickerData[];
20
+ clickable: boolean;
21
+ itemHeight: number;
22
+ wrapperHeight: number;
23
+ selectedValue?: ValueType;
24
+ onValueChange?: (value: ValueType, data: PickerData) => void;
25
+ disabled: boolean;
26
+ hideEmptyCols?: boolean;
27
+ /**
28
+ * 一列展示可选项的个数,默认是5,最小为3
29
+ * @en The number of options displayed in a column, the default is 5, the minimum is 3
30
+ */
31
+ rows?: number;
32
+ touchToStop?: boolean | number;
33
+ }
34
+ export interface PickerCellRef {
35
+ movingStatus: PickerCellMovingStatus;
36
+ scrollToCurrentIndex: () => void;
37
+ getCurrentCellValue: () => ValueType;
38
+ getCurrentCellData: () => PickerData;
39
+ }
40
+
41
+ const PickerCell = forwardRef((props: PickerCellProps, ref: Ref<PickerCellRef>) => {
42
+ const {
43
+ prefixCls,
44
+ style,
45
+ data,
46
+ clickable,
47
+ itemHeight,
48
+ wrapperHeight,
49
+ selectedValue,
50
+ onValueChange,
51
+ disabled,
52
+ hideEmptyCols,
53
+ rows = 5,
54
+ touchToStop = false,
55
+ } = props;
56
+ const [transitionDuration, setTransitionDuration] = useMountedState('');
57
+ const [bezier, setBezier] = useState('');
58
+ const [currentIndex, setCurrentIndex] = useMountedState(0);
59
+ const [currentValue, setCurrentValue] = useMountedState(selectedValue);
60
+ // 是否开始touch
61
+ // @en whether to start touch
62
+ const [transformY, transformYRef, setTransformY] = useRefState(0);
63
+ const lastTransformYRef = useRef(0);
64
+ const touchStartTimeRef = useRef(0);
65
+ const latestCallbackTimer = useRef(0);
66
+ const touchStartXRef = useRef(0);
67
+ const touchStartYRef = useRef(0);
68
+ const touchingRef = useRef(false);
69
+ const touchingXRef = useRef<boolean | null>(null);
70
+ const wrapRef = useRef<HTMLDivElement | null>(null);
71
+ const movingStatusRef = useRef<PickerCellMovingStatus>('normal');
72
+ const rowCount = Math.max(rows % 2 === 0 ? rows + 1 : rows, 3);
73
+ const isTouchMoveRef = useRef(false);
74
+ const isTouchStopped = useRef(false);
75
+ const unmountCallbackRef = useRef<(() => void) | undefined>(undefined);
76
+ const timeRef = useRef<number | null>(null);
77
+
78
+ const colStyle = useMemo(
79
+ () =>
80
+ getStyleWithVendor({
81
+ transform: `translate3d(0px, ${transformY || 0}px, 0px)`,
82
+ ...(transitionDuration ? { transitionDuration } : {}),
83
+ transitionTimingFunction: bezier,
84
+ paddingBottom: `${((rowCount - 1) / 2) * itemHeight}px`,
85
+ paddingTop: `${((rowCount - 1) / 2) * itemHeight}px`,
86
+ }),
87
+ [transitionDuration, transformY, bezier, itemHeight, rowCount],
88
+ );
89
+
90
+ function _scrollingComplete(nowItemIndex: number) {
91
+ // index有改变时再抛出
92
+ // @en Throws again when index changes
93
+ if (currentIndex !== nowItemIndex) {
94
+ setCurrentIndex(Math.max(nowItemIndex, 0));
95
+ const newData = data[nowItemIndex];
96
+ const newValue = newData?.value;
97
+
98
+ if (newValue !== currentValue) {
99
+ setCurrentValue(newValue);
100
+ if (onValueChange) {
101
+ onValueChange(newValue, newData);
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ function _scrollTo(transY: number, transDuration = 0, callback = () => {}) {
108
+ setTransitionDuration(transDuration ? `${transDuration}ms` : '');
109
+ setTransformY(transY);
110
+ // 处理连续滑动的情况:如果上一次callback还未执行,先cancel掉上一次回调
111
+ // @en handle the case of continuous sliding: If the last callback has not been executed, cancel the last callback first
112
+ if (latestCallbackTimer.current) {
113
+ clearTimeout(latestCallbackTimer.current);
114
+ }
115
+
116
+ const setNormalStatus = () => {
117
+ // 如果timer顺利执行,则在unmount时不再重复执行
118
+ // @en If the timer is successfully executed, it will not be repeated when unmounting
119
+ unmountCallbackRef.current = undefined;
120
+ movingStatusRef.current = 'normal';
121
+ setTransitionDuration('');
122
+ callback();
123
+ };
124
+
125
+ unmountCallbackRef.current = setNormalStatus;
126
+ latestCallbackTimer.current = window.setTimeout(setNormalStatus, transDuration);
127
+ }
128
+
129
+ function _scrollToIndex(itemIndex: number, transDuration = 0, callback = () => {}) {
130
+ _scrollTo(-1 * itemIndex * itemHeight, transDuration, callback);
131
+ }
132
+
133
+ function _scrollToIndexWithChange(itemIndex: number, transDuration = 0) {
134
+ _scrollToIndex(itemIndex, transDuration, () => {
135
+ _scrollingComplete(itemIndex);
136
+ });
137
+ }
138
+
139
+ const _handleColumnTouchStart = useCallback(
140
+ (e: TouchEvent) => {
141
+ if (disabled) {
142
+ return;
143
+ }
144
+ isTouchMoveRef.current = false;
145
+ movingStatusRef.current = 'moving';
146
+ const x = e.touches[0].screenX;
147
+ const y = e.touches[0].screenY;
148
+ touchStartTimeRef.current = Date.now();
149
+ touchingRef.current = true;
150
+ touchingXRef.current = null;
151
+ touchStartXRef.current = x;
152
+ touchStartYRef.current = y;
153
+ lastTransformYRef.current = transformYRef.current;
154
+ },
155
+ [disabled],
156
+ );
157
+
158
+ const _handleColumnTouchMove = useCallback(
159
+ (e: TouchEvent) => {
160
+ if (disabled || !touchingRef.current) {
161
+ return;
162
+ }
163
+ isTouchMoveRef.current = true;
164
+ const lastTransformY = lastTransformYRef.current;
165
+ const touchMoveX = e.touches[0].screenX;
166
+ const touchMoveY = e.touches[0].screenY;
167
+ const distanceX = touchMoveX - touchStartXRef.current;
168
+ const distance = touchMoveY - touchStartYRef.current;
169
+ if (touchingXRef.current === null) {
170
+ touchingXRef.current = Math.abs(distanceX) > Math.abs(distance);
171
+ }
172
+ if (touchingXRef.current) {
173
+ return;
174
+ }
175
+ e.cancelable && e.preventDefault();
176
+ const newPos = lastTransformY + distance;
177
+ const maxPos = -1 * (data.length - 1) * itemHeight;
178
+ // 当滚动到上边界或下边界时增加阻尼效果
179
+ // @en Add damping effect when scrolling to the upper or lower border
180
+ setTransformY(
181
+ (lastTransformY >= 0 && distance > 0) || (lastTransformY <= maxPos && distance < 0)
182
+ ? lastTransformY + distance / 4
183
+ : newPos,
184
+ );
185
+ },
186
+ [data.length, disabled, itemHeight],
187
+ );
188
+
189
+ function _handleScrollEnd() {
190
+ const maxIndex = data.length - 1;
191
+ const nowIndex = Math.max(
192
+ 0,
193
+ Math.min(maxIndex, Math.round((-1 * transformY) / itemHeight)),
194
+ );
195
+ // 滚动(包括加动量的滚动)完成之后定位到最近的一个index上
196
+ // @en After scrolling (including scrolling with momentum) is completed, it is positioned on the nearest index
197
+ _scrollToIndexWithChange(nowIndex, 200);
198
+ }
199
+
200
+ // Reference: https://juejin.im/post/6844904185121488910
201
+ function momentum(current, start, duration, minY, maxY) {
202
+ const durationMap = {
203
+ noBounce: 400,
204
+ weekBounce: 100,
205
+ strongBounce: 75,
206
+ };
207
+ const bezierMap = {
208
+ noBounce: 'cubic-bezier(.17, .89, .45, 1)',
209
+ weekBounce: 'cubic-bezier(.25, .46, .45, .94)',
210
+ strongBounce: 'cubic-bezier(.25, .46, .45, .94)',
211
+ };
212
+ let type = 'noBounce';
213
+ // 惯性滑动加速度
214
+ // @en inertial sliding acceleration
215
+ const deceleration = 0.003;
216
+ // 回弹阻力
217
+ // @en rebound resistance
218
+ const bounceRate = 5;
219
+ // 强弱回弹的分割值
220
+ // @en Split value of strong and weak rebound
221
+ const bounceThreshold = 300;
222
+ // 回弹的最大限度
223
+ // @en maximum rebound
224
+ const maxOverflowY = wrapperHeight / 6;
225
+ let overflowY;
226
+
227
+ const distance = current - start;
228
+ const speed = (2 * Math.abs(distance)) / duration;
229
+ let destination = current + (speed / deceleration) * (distance < 0 ? -1 : 1);
230
+ if (destination < minY) {
231
+ overflowY = minY - destination;
232
+ type = overflowY > bounceThreshold ? 'strongBounce' : 'weekBounce';
233
+ destination = Math.max(minY - maxOverflowY, minY - overflowY / bounceRate);
234
+ } else if (destination > maxY) {
235
+ overflowY = destination - maxY;
236
+ type = overflowY > bounceThreshold ? 'strongBounce' : 'weekBounce';
237
+ destination = Math.min(maxY + maxOverflowY, maxY + overflowY / bounceRate);
238
+ }
239
+
240
+ return {
241
+ destination,
242
+ duration: durationMap[type],
243
+ bezier: bezierMap[type],
244
+ };
245
+ }
246
+
247
+ function _handleColumnTouchEnd() {
248
+ _clearTimer();
249
+ movingStatusRef.current = 'normal';
250
+ const lastTransformY = lastTransformYRef.current;
251
+ if (!isTouchMoveRef.current || transformY === lastTransformY) {
252
+ return;
253
+ }
254
+ touchingRef.current = false;
255
+ const endTime = Date.now();
256
+ const scrollerHeight = (data.length + rowCount - 1) * itemHeight;
257
+ const duration = endTime - touchStartTimeRef.current;
258
+ const absDistY = Math.abs(transformY - lastTransformY);
259
+ // 计算动量,保证滚动顺畅,条件:手势时间小于300ms && 移动距离绝对值大于30时
260
+ // @en Calculate the momentum to ensure smooth scrolling, condition: the gesture time is less than 300ms && the absolute value of the moving distance is greater than 30
261
+ if (duration < 300 && absDistY > 90) {
262
+ const momentumY = momentum(
263
+ transformY,
264
+ lastTransformY,
265
+ duration,
266
+ wrapperHeight - scrollerHeight,
267
+ 0,
268
+ );
269
+ const newItemIndex = Math.max(
270
+ 0,
271
+ Math.min(data.length - 1, Math.round((-1 * momentumY.destination) / itemHeight)),
272
+ );
273
+
274
+ setBezier(momentumY.bezier);
275
+
276
+ movingStatusRef.current = 'scrolling';
277
+ _scrollToIndex(newItemIndex, momentumY.duration, () => {
278
+ _scrollingComplete(newItemIndex);
279
+ });
280
+ } else {
281
+ _handleScrollEnd();
282
+ }
283
+ }
284
+
285
+ function scrollToCurrentIndex() {
286
+ if (!wrapRef.current) {
287
+ return;
288
+ }
289
+ const curStyle = window.getComputedStyle(wrapRef.current);
290
+ const transformMatrix = curStyle.transform || curStyle.webkitTransform || '';
291
+ const transY =
292
+ Number(
293
+ transformMatrix
294
+ .replace(/matrix\((.*?)\)/, '$1')
295
+ .split(/,\s*/)
296
+ .slice(-1)[0],
297
+ ) || 0;
298
+ const maxIndex = data.length - 1;
299
+ const nowIndex = Math.max(0, Math.min(maxIndex, Math.round((-1 * transY) / itemHeight)));
300
+ // 滚动(包括加动量的滚动)完成之后定位到最近的一个index上
301
+ // @en After scrolling (including scrolling with momentum) is completed, it is positioned on the nearest index
302
+ _scrollToIndexWithChange(nowIndex, 0);
303
+ }
304
+
305
+ function getCurrentCellValue() {
306
+ return data[currentIndex]?.value;
307
+ }
308
+
309
+ function getCurrentCellData() {
310
+ return data[currentIndex];
311
+ }
312
+
313
+ function _clearTimer() {
314
+ timeRef.current && clearTimeout(timeRef.current);
315
+ timeRef.current = null;
316
+ }
317
+
318
+ function _handleItemTouchStart() {
319
+ if (touchToStop === false) {
320
+ return;
321
+ }
322
+ isTouchStopped.current = false;
323
+ timeRef.current = window.setTimeout(
324
+ () => {
325
+ if (!isTouchMoveRef.current) {
326
+ isTouchStopped.current = true;
327
+ scrollToCurrentIndex();
328
+ }
329
+ },
330
+ touchToStop === true ? 100 : touchToStop,
331
+ );
332
+ }
333
+
334
+ function _handleItemClick(itemIndex: number) {
335
+ if (!clickable || disabled || (touchToStop !== false && isTouchStopped.current)) {
336
+ return;
337
+ }
338
+ _scrollToIndexWithChange(itemIndex, 200);
339
+ }
340
+
341
+ useEffect(() => {
342
+ return () => {
343
+ // 卸载组件时,如果timer中还有未执行的onchange操作,则立刻执行该操作并移除timer
344
+ // @en When unloading the component, if there is an unexecuted onchange operation in the timer, execute it immediately and remove the timer
345
+ const timerId = latestCallbackTimer.current;
346
+ unmountCallbackRef.current?.();
347
+ timerId && clearTimeout(timerId);
348
+ };
349
+ }, []);
350
+
351
+ useEffect(() => {
352
+ if (wrapRef.current) {
353
+ wrapRef.current.addEventListener('touchstart', _handleColumnTouchStart);
354
+ wrapRef.current.addEventListener('touchmove', _handleColumnTouchMove);
355
+ }
356
+ return () => {
357
+ if (wrapRef.current) {
358
+ wrapRef.current.removeEventListener('touchstart', _handleColumnTouchStart);
359
+ wrapRef.current.removeEventListener('touchmove', _handleColumnTouchMove);
360
+ }
361
+ };
362
+ }, [_handleColumnTouchStart, _handleColumnTouchMove]);
363
+
364
+ useLayoutEffect(() => {
365
+ if ('selectedValue' in props) {
366
+ const curIndex = data.findIndex((item: PickerData) => item.value === selectedValue);
367
+ setCurrentIndex(Math.max(curIndex, 0));
368
+
369
+ if (curIndex >= 0) {
370
+ _scrollToIndexWithChange(curIndex);
371
+ }
372
+ }
373
+ }, [selectedValue, itemHeight, data]);
374
+
375
+ useImperativeHandle(ref, () => ({
376
+ movingStatus: movingStatusRef.current,
377
+ scrollToCurrentIndex,
378
+ getCurrentCellValue,
379
+ getCurrentCellData,
380
+ }));
381
+
382
+ return !hideEmptyCols || (data && data.length) ? (
383
+ <div className={`${prefixCls}-column`}>
384
+ <div
385
+ className={`${prefixCls}-column-item-wrap`}
386
+ style={colStyle}
387
+ ref={wrapRef}
388
+ onTouchStart={_handleItemTouchStart}
389
+ onTouchEnd={_handleColumnTouchEnd}
390
+ onTouchCancel={_handleColumnTouchEnd}
391
+ aria-disabled={disabled}
392
+ >
393
+ {data.map((item, index) => {
394
+ const dis = Math.abs(index - currentIndex);
395
+ return (
396
+ <div
397
+ aria-label={index === currentIndex ? `${item.value}` : ''}
398
+ key={`${index}_${item.value}`}
399
+ className={cls(`${prefixCls}-column-item`, {
400
+ selected: index === currentIndex,
401
+ [`selected-neighbor-${dis}`]:
402
+ dis && dis <= Math.floor(rowCount / 2),
403
+ })}
404
+ style={style}
405
+ onClick={() => _handleItemClick(index)}
406
+ >
407
+ {item.label}
408
+ </div>
409
+ );
410
+ })}
411
+ </div>
412
+ </div>
413
+ ) : null;
414
+ });
415
+
416
+ export default PickerCell;
@@ -0,0 +1,144 @@
1
+ ### 信息展示
2
+
3
+ # 气泡卡片 Popover
4
+
5
+ 气泡卡片,支持六个方向,小箭头在各个方向均基于挂载的子元素居中放置,支持受控和非受控模式。
6
+
7
+ ======
8
+
9
+ > 属性/Props
10
+
11
+ |参数|描述|类型|默认值|
12
+ |----------|-------------|------|------|
13
+ |children|气泡载体,组件会监听其变化以重新计算布局,建议用 useMemo 包裹|ReactNode|-|
14
+ |innerPopoverClassName|自定义气泡类名|string|''|
15
+ |visible|是否显示气泡,受控模式|boolean|-|
16
+ |content|气泡内容,组件会监听其变化以重新计算布局,建议用 useMemo 包裹|ReactNode|null|
17
+ |direction|气泡展示的位置|"topLeft" \| "topCenter" \| "topRight" \| "bottomLeft" \| "bottomCenter" \| "bottomRight"|'topRight'|
18
+ |duration|自动关闭场景下的停留时长,单位毫秒,为0则表示不自动关闭|number|0|
19
+ |transitionTimeout|动画时长,单位毫秒|number \| \{ appear?: number; enter?: number; exit?: number; \}|300|
20
+ |minWidth|气泡最小宽度|string|'10px'|
21
+ |maxWidth|气泡最大宽度|string|'90vw'|
22
+ |transitionName|气泡过渡动画类名,修改类名需自行实现动画|string|'fade'|
23
+ |verticalOffset|气泡垂直方向上相对于子元素的偏移量,单位为px|number|10|
24
+ |horizontalOffset|气泡水平方向上相对于子元素的偏移量,单位为px|number|8|
25
+ |useAutoDirection|气泡边界自适应,开启时会在气泡左右边界留出安全距离,气泡与视窗上下边界小于安全距离时改变气泡的垂直方向,使其在视窗中漏出|boolean \| \{ horizontal: boolean; vertical: boolean; \}|true|
26
+ |edgeOffset|气泡距离边界的安全距离,单位为px|number \| EdgeOffset|14|
27
+ |verticalScrollThrottle|滚动容器的滚动事件节流粒度,单位ms|number|100|
28
+ |arrowWidth|气泡尖角大小,单位为px|number|9|
29
+ |preventBodyClick|气泡出现后,是否屏蔽非内部元素的点击事件,点击非内部元素后只做隐藏气泡操作|boolean|false|
30
+ |clickOtherToClose|点击非子元素和气泡元素是否关闭气泡|boolean|true|
31
+ |clickSelfToClose|点击气泡和子元素是否关闭气泡|boolean|true|
32
+ |touchOtherToClose|触碰非子元素和气泡元素是否关闭气泡|boolean|false|
33
+ |touchSelfToClose|触碰气泡和子元素是否关闭气泡|boolean|false|
34
+ |touchToClose|触碰页面是否关闭气泡,是touchOtherToClose和touchSelfToClose的默认值|boolean|false|
35
+ |theme|气泡的主题,black主题是黑底白字,white主题是白底黑字|"black" \| "white"|'black'|
36
+ |needShadow|气泡内容是否需要阴影|boolean|false|
37
+ |bordered|气泡是否有border|boolean|白色主题默认有边框,黑色主题没有|
38
+ |defaultVisible|气泡是否可见的默认值,非受控模式|boolean|false|
39
+ |showCloseIcon|是否展示关闭按钮|boolean|false|
40
+ |textSuffix|文字气泡后置元素,如操作按钮等|ReactNode|null|
41
+ |mode|气泡挂载位置,follow模式挂载在当前子元素下,global模式挂载在body上|"follow" \| "global"|'follow'|
42
+ |showMask|是否展示蒙层|boolean|false|
43
+ |maskTransitionTimeout|蒙层动画时长|number \| \{ appear?: number; enter?: number; exit?: number; \}|{ enter: 450, exit: 240 }|
44
+ |renderArrow|自定义箭头渲染|(options: \{ arrowWidth?: number; arrowLeft: number; direction: Direction; \}) =\> ReactNode|-|
45
+ |onChange|状态发生改变的回调事件|(visible: boolean) =\> void|() => void|
46
+ |onClickCloseIcon|点击关闭icon的回调|() =\> void|() => void|
47
+ |onClickTextSuffix|点击文字气泡后置元素回调|() =\> void|() => void|
48
+ |onClickMask|点击蒙层回调|() =\> void|() => void|
49
+ |getVerticalScrollContainer|获取页面垂直方向的滚动容器|() =\> HTMLElement|() => document|
50
+ |className|自定义类名|string|""|
51
+ |style|自定义样式|CSSProperties|{}|
52
+
53
+ > 引用/Refs
54
+
55
+ |参数|描述|类型|
56
+ |----------|-------------|------|
57
+ |child|气泡包裹子元素dom|HTMLDivElement|
58
+ |innerPopover|气泡组件 ref|PopoverInnerRef|
59
+ |innerPopoverDom|气泡元素dom|HTMLDivElement|
60
+ |updatePosition|手动更新气泡的位置|() =\> void|
61
+ |dom|组件外层dom元素|HTMLDivElement|
62
+
63
+ > Popover.Menu
64
+
65
+ 气泡菜单
66
+
67
+ |参数|描述|类型|默认值|
68
+ |----------|-------------|------|------|
69
+ |menu|菜单项列表|(string \| PopoverMenuItem)\[\]|必填|
70
+ |menuLayout|菜单排列顺序,默认为竖向菜单,支持横向菜单和竖向菜单,横向菜单一行最多可放4个菜单|"vertical" \| "horizontal"|'vertical'|
71
+ |useClickStatus|菜单项是否有点击态|boolean|false|
72
+ |clickStatusDuration|菜单项点击态持续时长,单位毫秒|number|300|
73
+ |onSelect|选择可用菜单项的回调|(value: string, item: string \| PopoverMenuItem) =\> void|() => {}|
74
+ |onClickMenuItem|点击所有菜单项回调|(value: string, item: string \| PopoverMenuItem) =\> void|必填|
75
+ |children|气泡载体,组件会监听其变化以重新计算布局,建议用 useMemo 包裹|ReactNode|-|
76
+ |innerPopoverClassName|自定义气泡类名|string|''|
77
+ |visible|是否显示气泡,受控模式|boolean|-|
78
+ |content|气泡内容,组件会监听其变化以重新计算布局,建议用 useMemo 包裹|ReactNode|null|
79
+ |direction|气泡展示的位置|"topLeft" \| "topCenter" \| "topRight" \| "bottomLeft" \| "bottomCenter" \| "bottomRight"|'topRight'|
80
+ |duration|自动关闭场景下的停留时长,单位毫秒,为0则表示不自动关闭|number|0|
81
+ |transitionTimeout|动画时长,单位毫秒|number \| \{ appear?: number; enter?: number; exit?: number; \}|300|
82
+ |minWidth|气泡最小宽度|string|'10px'|
83
+ |maxWidth|气泡最大宽度|string|'90vw'|
84
+ |transitionName|气泡过渡动画类名,修改类名需自行实现动画|string|'fade'|
85
+ |verticalOffset|气泡垂直方向上相对于子元素的偏移量,单位为px|number|10|
86
+ |horizontalOffset|气泡水平方向上相对于子元素的偏移量,单位为px|number|8|
87
+ |useAutoDirection|气泡边界自适应,开启时会在气泡左右边界留出安全距离,气泡与视窗上下边界小于安全距离时改变气泡的垂直方向,使其在视窗中漏出|boolean \| \{ horizontal: boolean; vertical: boolean; \}|true|
88
+ |edgeOffset|气泡距离边界的安全距离,单位为px|number \| EdgeOffset|14|
89
+ |verticalScrollThrottle|滚动容器的滚动事件节流粒度,单位ms|number|100|
90
+ |arrowWidth|气泡尖角大小,单位为px|number|9|
91
+ |preventBodyClick|气泡出现后,是否屏蔽非内部元素的点击事件,点击非内部元素后只做隐藏气泡操作|boolean|false|
92
+ |clickOtherToClose|点击非子元素和气泡元素是否关闭气泡|boolean|true|
93
+ |clickSelfToClose|点击气泡和子元素是否关闭气泡|boolean|true|
94
+ |touchOtherToClose|触碰非子元素和气泡元素是否关闭气泡|boolean|false|
95
+ |touchSelfToClose|触碰气泡和子元素是否关闭气泡|boolean|false|
96
+ |touchToClose|触碰页面是否关闭气泡,是touchOtherToClose和touchSelfToClose的默认值|boolean|false|
97
+ |theme|气泡的主题,black主题是黑底白字,white主题是白底黑字|"black" \| "white"|'black'|
98
+ |needShadow|气泡内容是否需要阴影|boolean|false|
99
+ |bordered|气泡是否有border|boolean|白色主题默认有边框,黑色主题没有|
100
+ |defaultVisible|气泡是否可见的默认值,非受控模式|boolean|false|
101
+ |showCloseIcon|是否展示关闭按钮|boolean|false|
102
+ |textSuffix|文字气泡后置元素,如操作按钮等|ReactNode|null|
103
+ |mode|气泡挂载位置,follow模式挂载在当前子元素下,global模式挂载在body上|"follow" \| "global"|'follow'|
104
+ |showMask|是否展示蒙层|boolean|false|
105
+ |maskTransitionTimeout|蒙层动画时长|number \| \{ appear?: number; enter?: number; exit?: number; \}|{ enter: 450, exit: 240 }|
106
+ |renderArrow|自定义箭头渲染|(options: \{ arrowWidth?: number; arrowLeft: number; direction: Direction; \}) =\> ReactNode|-|
107
+ |onChange|状态发生改变的回调事件|(visible: boolean) =\> void|() => void|
108
+ |onClickCloseIcon|点击关闭icon的回调|() =\> void|() => void|
109
+ |onClickTextSuffix|点击文字气泡后置元素回调|() =\> void|() => void|
110
+ |onClickMask|点击蒙层回调|() =\> void|() => void|
111
+ |getVerticalScrollContainer|获取页面垂直方向的滚动容器|() =\> HTMLElement|() => document|
112
+ |className|自定义类名|string|""|
113
+ |style|自定义样式|CSSProperties|{}|
114
+
115
+ > Direction
116
+
117
+ ```
118
+ "topLeft" | "topCenter" | "topRight" | "bottomLeft" | "bottomCenter" | "bottomRight"
119
+ ```
120
+
121
+ > EdgeOffset
122
+
123
+ |参数|描述|类型|
124
+ |----------|-------------|------|
125
+ |top|上|number|
126
+ |right|右|number|
127
+ |bottom|下|number|
128
+ |left|左|number|
129
+
130
+ > PopoverInnerRef
131
+
132
+ |参数|描述|类型|
133
+ |----------|-------------|------|
134
+ |dom|组件容器dom|HTMLDivElement|
135
+ |content|-|HTMLDivElement|
136
+
137
+ > PopoverMenuItem
138
+
139
+ |参数|描述|类型|默认值|
140
+ |----------|-------------|------|------|
141
+ |text|菜单项文本|ReactNode|必填|
142
+ |value|菜单项值|string|text|
143
+ |icon|菜单项的icon组件|ReactNode|null|
144
+ |disabled|是否禁用菜单项|boolean|false|
@@ -0,0 +1,23 @@
1
+ import { componentWrapper } from '@arco-design/mobile-utils';
2
+ import { componentGenerator } from './menu';
3
+ import { Popover } from './popover';
4
+
5
+ export * from './type';
6
+
7
+ /**
8
+ * 气泡菜单
9
+ * @en Bubble menu
10
+ */
11
+ const Menu = componentGenerator(Popover);
12
+
13
+ /**
14
+ * 气泡卡片,支持六个方向,小箭头在各个方向均基于挂载的子元素居中放置,支持受控和非受控模式。
15
+ * @en Bubble card, supports six directions, small arrows are centered in each direction based on the mounted sub-elements, and supports controlled and uncontrolled modes.
16
+ * @name 气泡卡片
17
+ * @name_en Popover
18
+ * @type 信息展示
19
+ * @type_en Data Display
20
+ */
21
+ export default componentWrapper(Popover, {
22
+ Menu,
23
+ });