@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,238 @@
|
|
|
1
|
+
import { cls, defaultLocale } from '@arco-design/mobile-utils';
|
|
2
|
+
import React, {
|
|
3
|
+
useRef,
|
|
4
|
+
forwardRef,
|
|
5
|
+
Ref,
|
|
6
|
+
useImperativeHandle,
|
|
7
|
+
useMemo,
|
|
8
|
+
useCallback,
|
|
9
|
+
useEffect,
|
|
10
|
+
useContext,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import { GlobalContext } from '../context-provider';
|
|
13
|
+
import { IconKeyboard, IconKeyboardDelete } from '../icon';
|
|
14
|
+
import Popup, { PopupRef } from '../popup';
|
|
15
|
+
import { ColumnData, ColumnObjData, KeyboardProps, KeyboardRef } from './type';
|
|
16
|
+
|
|
17
|
+
export * from './type';
|
|
18
|
+
|
|
19
|
+
// 键盘乱序
|
|
20
|
+
// @en let keyboard random
|
|
21
|
+
const makeArrayRandom = (targetArray: Array<number>) => {
|
|
22
|
+
const randomFn = () => {
|
|
23
|
+
return Math.random() - 0.5;
|
|
24
|
+
};
|
|
25
|
+
return targetArray.sort(randomFn);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const splitArray = (target: Array<number>) => {
|
|
29
|
+
return target.reduce<number[][]>((acc, cur) => {
|
|
30
|
+
if (!acc[acc.length - 1] || acc[acc.length - 1].length >= 3) {
|
|
31
|
+
acc.push([]);
|
|
32
|
+
}
|
|
33
|
+
acc[acc.length - 1].push(cur);
|
|
34
|
+
return acc;
|
|
35
|
+
}, []);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// 纯数字类字符
|
|
39
|
+
// @en Pure numeric characters
|
|
40
|
+
const contentArray: Array<number> = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 数字键盘组件
|
|
44
|
+
* @en Keyboard component
|
|
45
|
+
* @type 数据录入
|
|
46
|
+
* @type_en Data Entry
|
|
47
|
+
* @name 数字键盘
|
|
48
|
+
* @name_en Keyboard
|
|
49
|
+
*/
|
|
50
|
+
const Keyboard = forwardRef((props: KeyboardProps, ref: Ref<KeyboardRef>) => {
|
|
51
|
+
const {
|
|
52
|
+
normalKeyClass,
|
|
53
|
+
normalKeyStyle,
|
|
54
|
+
className = '',
|
|
55
|
+
style,
|
|
56
|
+
type = 'number',
|
|
57
|
+
randomOrder = false,
|
|
58
|
+
title,
|
|
59
|
+
rightColumns,
|
|
60
|
+
confirmClosable,
|
|
61
|
+
confirmButton,
|
|
62
|
+
deleteButton,
|
|
63
|
+
keyboardButton,
|
|
64
|
+
close,
|
|
65
|
+
onConfirm,
|
|
66
|
+
onDelete,
|
|
67
|
+
onChange,
|
|
68
|
+
...resetProps
|
|
69
|
+
} = props;
|
|
70
|
+
const popupRef = useRef<PopupRef>(null);
|
|
71
|
+
const keyboardRef = useRef<HTMLDivElement | null>(null);
|
|
72
|
+
const { prefixCls, locale = defaultLocale } = useContext(GlobalContext);
|
|
73
|
+
const prefix = `${prefixCls}-keyboard`;
|
|
74
|
+
// 3x4键盘的按键内容
|
|
75
|
+
// @en 3x4 Keyboard button content
|
|
76
|
+
const displayData = useMemo(() => {
|
|
77
|
+
const numberArr = randomOrder ? makeArrayRandom(contentArray) : contentArray;
|
|
78
|
+
const lastNum = numberArr.slice(-1)[0];
|
|
79
|
+
const finalData: ColumnData[][] = splitArray(numberArr.slice(0, -1));
|
|
80
|
+
switch (type) {
|
|
81
|
+
case 'confirm':
|
|
82
|
+
finalData.push([lastNum, '.']);
|
|
83
|
+
break;
|
|
84
|
+
case 'tool':
|
|
85
|
+
finalData.push(['.', lastNum, { type: 'delete' }]);
|
|
86
|
+
break;
|
|
87
|
+
case 'number':
|
|
88
|
+
default:
|
|
89
|
+
finalData.push([{ type: 'keyboard' }, lastNum, { type: 'delete' }]);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
return finalData;
|
|
93
|
+
}, [type]);
|
|
94
|
+
|
|
95
|
+
// 右边一列
|
|
96
|
+
// @en Right column
|
|
97
|
+
const rightColumn = useMemo<ColumnData[]>(() => {
|
|
98
|
+
switch (type) {
|
|
99
|
+
case 'number':
|
|
100
|
+
return [];
|
|
101
|
+
case 'confirm': {
|
|
102
|
+
return [{ type: 'delete' }];
|
|
103
|
+
}
|
|
104
|
+
case 'tool':
|
|
105
|
+
return ['+', '-', '×', '÷'];
|
|
106
|
+
default:
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
}, [type]);
|
|
110
|
+
|
|
111
|
+
// 确认按钮需要在type为confirm时展示
|
|
112
|
+
// @en Display then confirm button when type is confirm
|
|
113
|
+
const getConfirmButton = useCallback(() => {
|
|
114
|
+
if (type !== 'confirm') {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return (
|
|
118
|
+
<div
|
|
119
|
+
onClick={() => {
|
|
120
|
+
confirmClosable && close();
|
|
121
|
+
onConfirm?.();
|
|
122
|
+
}}
|
|
123
|
+
className={cls(`${prefix}-key`, `${prefix}-key-confirm`)}
|
|
124
|
+
>
|
|
125
|
+
{confirmButton || locale.Keyboard.confirm}
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
}, [type]);
|
|
129
|
+
|
|
130
|
+
// 点击空白处关闭键盘
|
|
131
|
+
// @en Close keyboard when clicked the blank space
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
const handlerDocumentClick = (e: MouseEvent) => {
|
|
134
|
+
if (!keyboardRef.current) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (
|
|
138
|
+
!keyboardRef.current.contains(e.target as Node) &&
|
|
139
|
+
e.target !== keyboardRef.current
|
|
140
|
+
) {
|
|
141
|
+
close();
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
document.addEventListener('click', handlerDocumentClick);
|
|
145
|
+
return () => {
|
|
146
|
+
document.removeEventListener('click', handlerDocumentClick);
|
|
147
|
+
};
|
|
148
|
+
}, []);
|
|
149
|
+
|
|
150
|
+
useImperativeHandle(ref, () => ({
|
|
151
|
+
keyboard: keyboardRef.current,
|
|
152
|
+
...popupRef.current!,
|
|
153
|
+
}));
|
|
154
|
+
|
|
155
|
+
const handleButtonClick = (ele: ColumnObjData) => {
|
|
156
|
+
switch (ele.type) {
|
|
157
|
+
case 'delete':
|
|
158
|
+
onDelete?.();
|
|
159
|
+
break;
|
|
160
|
+
case 'keyboard':
|
|
161
|
+
close();
|
|
162
|
+
break;
|
|
163
|
+
default:
|
|
164
|
+
onChange?.(ele.value);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const renderButtonContent = (ele: ColumnObjData) => {
|
|
169
|
+
switch (ele.type) {
|
|
170
|
+
case 'delete':
|
|
171
|
+
return deleteButton || <IconKeyboardDelete />;
|
|
172
|
+
case 'keyboard':
|
|
173
|
+
return keyboardButton || <IconKeyboard />;
|
|
174
|
+
default:
|
|
175
|
+
return ele.content;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const renderButton = (data: ColumnData, key: string, extraClass?: Record<string, any>) => {
|
|
180
|
+
const ele: ColumnObjData =
|
|
181
|
+
typeof data === 'string' || typeof data === 'number'
|
|
182
|
+
? {
|
|
183
|
+
type: 'content',
|
|
184
|
+
content: data,
|
|
185
|
+
value: data,
|
|
186
|
+
}
|
|
187
|
+
: data;
|
|
188
|
+
return (
|
|
189
|
+
<button
|
|
190
|
+
onClick={() => handleButtonClick(ele)}
|
|
191
|
+
className={cls(`${prefix}-key`, normalKeyClass, extraClass)}
|
|
192
|
+
key={key}
|
|
193
|
+
style={normalKeyStyle}
|
|
194
|
+
>
|
|
195
|
+
{renderButtonContent(ele)}
|
|
196
|
+
</button>
|
|
197
|
+
);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const renderKeyboardRightColumns = () => {
|
|
201
|
+
if (rightColumns) {
|
|
202
|
+
return rightColumns;
|
|
203
|
+
}
|
|
204
|
+
return rightColumn.length > 0 ? (
|
|
205
|
+
<div className={`${prefix}-col`}>
|
|
206
|
+
{rightColumn.map((item, index) => renderButton(item, String(index)))}
|
|
207
|
+
{getConfirmButton()}
|
|
208
|
+
</div>
|
|
209
|
+
) : null;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<Popup ref={popupRef} maskClass={`${prefix}-popup`} close={close} {...resetProps}>
|
|
214
|
+
<div className={cls(prefix, `${className}`)} style={style} ref={keyboardRef}>
|
|
215
|
+
{title}
|
|
216
|
+
<div className={`${prefix}-wrapper`}>
|
|
217
|
+
<div className={`${prefix}-key-wrapper`}>
|
|
218
|
+
{displayData.map((item, index) => (
|
|
219
|
+
<div className={`${prefix}-row`} key={index}>
|
|
220
|
+
{item.map((e, idx) =>
|
|
221
|
+
renderButton(e, String(idx), {
|
|
222
|
+
[`${prefix}-key-special`]:
|
|
223
|
+
type === 'confirm' &&
|
|
224
|
+
index === displayData.length - 1 &&
|
|
225
|
+
idx === 0,
|
|
226
|
+
}),
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
))}
|
|
230
|
+
</div>
|
|
231
|
+
{renderKeyboardRightColumns()}
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
</Popup>
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
export default Keyboard;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
### 反馈
|
|
2
|
+
|
|
3
|
+
# 加载更多 LoadMore
|
|
4
|
+
|
|
5
|
+
上拉加载组件,支持`scroll`和`click`两种触发加载方式,支持滚动监听。支持受控与不受控两种形式。<br>如果引入组件后发现仅触发了初始的`getData`,请确认是否在`getData`方法内没有调用`callback`移除 loading 状态,且未设置`blockWhenLoading`属性为 false。
|
|
6
|
+
|
|
7
|
+
======
|
|
8
|
+
|
|
9
|
+
> 属性/Props
|
|
10
|
+
|
|
11
|
+
|参数|描述|类型|默认值|
|
|
12
|
+
|----------|-------------|------|------|
|
|
13
|
+
|style|自定义样式|CSSProperties|-|
|
|
14
|
+
|className|自定义类名|string|-|
|
|
15
|
+
|disabled|是否禁用加载能力|boolean|-|
|
|
16
|
+
|beforeReadyArea|组件加载但尚未启用状态下的内容|ReactNode|null|
|
|
17
|
+
|loadingArea|加载中状态下的内容|ReactNode|"正在努力加载中..."|
|
|
18
|
+
|noMoreArea|无更多数据状态下的内容|ReactNode|"没有更多数据了"|
|
|
19
|
+
|prepareArea|准备加载状态下的内容|ReactNode|"上拉/点击加载更多"|
|
|
20
|
+
|retryArea|加载重试状态下的内容|ReactNode|"加载失败,点击重试"|
|
|
21
|
+
|defaultStatus|组件加载初始状态,传入 "before\-ready" 则先加载组件但不请求数据|"before\-ready" \| "prepare"|"prepare"|
|
|
22
|
+
|status|当前状态,传入则受控|"before\-ready" \| "prepare" \| "loading" \| "nomore" \| "retry"|-|
|
|
23
|
+
|getScrollContainer|待计算滚动容器|() =\> HTMLElement \| Window|() => window|
|
|
24
|
+
|getOffsetNode|当多个 loadmore 在同一页面时,通过传入节点的 offsetHeight \+ offsetTop 代替 scrollHeight|() =\> HTMLElement \| Window|-|
|
|
25
|
+
|trigger|触发loading的时机,当为click时,点击后将触发getData|"scroll" \| "click"|"scroll"|
|
|
26
|
+
|threshold|滚动到离列表底部多远的位置触发getData,触发状态时机为'scroll'时有效|number|200|
|
|
27
|
+
|getData|请求数据方法,可在异步任务结束后根据任务结果调用callback修改loadmore内部状态|(callback: (status: LoadMoreStatus) =\> void) =\> void|-|
|
|
28
|
+
|throttle|节流粒度|number|0|
|
|
29
|
+
|blockWhenLoading|是否在loading状态下不触发getData|boolean|true|
|
|
30
|
+
|getDataAtFirst|刚加载好组件时是否自动先请求一次,trigger=scroll时有效|boolean|true|
|
|
31
|
+
|getDataWhenNoScrollAtFirst|当 getDataAtFirst 值为 false 且数据不满一屏时是否触发一次请求,trigger=scroll时有效|boolean|false|
|
|
32
|
+
|onStatusChange|状态改变时回调|(status: LoadMoreStatus, scene?: string) =\> void|-|
|
|
33
|
+
|onClick|组件被点击时回调|(e: MouseEvent\<HTMLDivElement, MouseEvent\>) =\> void|-|
|
|
34
|
+
|onEndReached|滚动到(底部 \- threshold)距离时触发|() =\> void|-|
|
|
35
|
+
|
|
36
|
+
> 引用/Refs
|
|
37
|
+
|
|
38
|
+
|参数|描述|类型|
|
|
39
|
+
|----------|-------------|------|
|
|
40
|
+
|dom|最外层元素 DOM|HTMLDivElement|
|
|
41
|
+
|changeStatus|手动更改组件状态|(status: LoadMoreStatus, scene?: string) =\> void|
|
|
42
|
+
|getDataWithEndReachCheck|判断是否滚动到底部并手动触发数据获取|() =\> void|
|
|
43
|
+
|
|
44
|
+
> LoadMoreStatus
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
"before-ready" | "prepare" | "loading" | "nomore" | "retry"
|
|
48
|
+
```
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useRef,
|
|
3
|
+
forwardRef,
|
|
4
|
+
Ref,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
ReactNode,
|
|
7
|
+
useEffect,
|
|
8
|
+
useCallback,
|
|
9
|
+
useState,
|
|
10
|
+
} from 'react';
|
|
11
|
+
import lodashThrottle from 'lodash.throttle';
|
|
12
|
+
import {
|
|
13
|
+
getScrollContainerAttribute,
|
|
14
|
+
getValidScrollContainer,
|
|
15
|
+
defaultLocale,
|
|
16
|
+
ILocale,
|
|
17
|
+
cls,
|
|
18
|
+
} from '@arco-design/mobile-utils';
|
|
19
|
+
import { ContextLayout } from '../context-provider';
|
|
20
|
+
import { useLatestRef, useUpdateEffect } from '../_helpers';
|
|
21
|
+
|
|
22
|
+
export type LoadMoreStatus = 'before-ready' | 'prepare' | 'loading' | 'nomore' | 'retry';
|
|
23
|
+
|
|
24
|
+
export interface LoadMoreProps {
|
|
25
|
+
/**
|
|
26
|
+
* 自定义样式
|
|
27
|
+
* @en Custom stylesheet
|
|
28
|
+
*/
|
|
29
|
+
style?: React.CSSProperties;
|
|
30
|
+
/**
|
|
31
|
+
* 自定义类名
|
|
32
|
+
* @en Custom classname
|
|
33
|
+
*/
|
|
34
|
+
className?: string;
|
|
35
|
+
/**
|
|
36
|
+
* 是否禁用加载能力
|
|
37
|
+
* @en Whether to disable the loading capability
|
|
38
|
+
*/
|
|
39
|
+
disabled?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* 组件加载但尚未启用状态下的内容
|
|
42
|
+
* @en Content when the component is loaded but not yet enabled
|
|
43
|
+
* @default null
|
|
44
|
+
*/
|
|
45
|
+
beforeReadyArea?: ReactNode;
|
|
46
|
+
/**
|
|
47
|
+
* 加载中状态下的内容
|
|
48
|
+
* @en Content in loading state
|
|
49
|
+
* @default "正在努力加载中..."
|
|
50
|
+
* @default_en "Trying to load..."
|
|
51
|
+
*/
|
|
52
|
+
loadingArea?: ReactNode;
|
|
53
|
+
/**
|
|
54
|
+
* 无更多数据状态下的内容
|
|
55
|
+
* @en Content with no more data
|
|
56
|
+
* @default "没有更多数据了"
|
|
57
|
+
* @default_en "No more data"
|
|
58
|
+
*/
|
|
59
|
+
noMoreArea?: ReactNode;
|
|
60
|
+
/**
|
|
61
|
+
* 准备加载状态下的内容
|
|
62
|
+
* @en Content in ready-to-load state
|
|
63
|
+
* @default "上拉/点击加载更多"
|
|
64
|
+
* @default_en "Pull up to load more" or "Click to load more"
|
|
65
|
+
*/
|
|
66
|
+
prepareArea?: ReactNode;
|
|
67
|
+
/**
|
|
68
|
+
* 加载重试状态下的内容
|
|
69
|
+
* @en Load content in retry state
|
|
70
|
+
* @default "加载失败,点击重试"
|
|
71
|
+
* @default_en "failed to load, click to retry"
|
|
72
|
+
*/
|
|
73
|
+
retryArea?: ReactNode;
|
|
74
|
+
/**
|
|
75
|
+
* 组件加载初始状态,传入 "before-ready" 则先加载组件但不请求数据
|
|
76
|
+
* @en The component is loaded in the initial state. Inputing in "before-ready" will load the component first without requesting data
|
|
77
|
+
* @default "prepare"
|
|
78
|
+
*/
|
|
79
|
+
defaultStatus?: 'before-ready' | 'prepare';
|
|
80
|
+
/**
|
|
81
|
+
* 当前状态,传入则受控
|
|
82
|
+
* @en Current state, the component is controlledc when it is input
|
|
83
|
+
*/
|
|
84
|
+
status?: LoadMoreStatus;
|
|
85
|
+
/**
|
|
86
|
+
* 待计算滚动容器
|
|
87
|
+
* @en Scrolling container to be calculated
|
|
88
|
+
* @default () => window
|
|
89
|
+
*/
|
|
90
|
+
getScrollContainer?: () => HTMLElement | Window | null;
|
|
91
|
+
/**
|
|
92
|
+
* 当多个 loadmore 在同一页面时,通过传入节点的 offsetHeight + offsetTop 代替 scrollHeight
|
|
93
|
+
* @en When multiple loadmores are on the same page, pass in the offsetHeight + offsetTop of the node instead of scrollHeight
|
|
94
|
+
*/
|
|
95
|
+
getOffsetNode?: () => HTMLElement | Window | null;
|
|
96
|
+
/**
|
|
97
|
+
* 触发loading的时机,当为click时,点击后将触发getData
|
|
98
|
+
* @en The timing of triggering loading, when it is click, getData will be triggered after clicking
|
|
99
|
+
* @default "scroll"
|
|
100
|
+
*/
|
|
101
|
+
trigger?: 'scroll' | 'click';
|
|
102
|
+
/**
|
|
103
|
+
* 滚动到离列表底部多远的位置触发getData,触发状态时机为'scroll'时有效
|
|
104
|
+
* @en Scroll to how far from the bottom of the list to trigger getData, valid when the trigger state timing is 'scroll'
|
|
105
|
+
* @default 200
|
|
106
|
+
*/
|
|
107
|
+
threshold?: number;
|
|
108
|
+
/**
|
|
109
|
+
* 请求数据方法,可在异步任务结束后根据任务结果调用callback修改loadmore内部状态
|
|
110
|
+
* @en The request data method, after the asynchronous task ends, the callback can be called according to the task result to modify the internal state of loadmore
|
|
111
|
+
*/
|
|
112
|
+
getData?: (callback: (status: LoadMoreStatus) => void) => void;
|
|
113
|
+
/**
|
|
114
|
+
* 节流粒度
|
|
115
|
+
* @en Throttle granularity
|
|
116
|
+
* @default 0
|
|
117
|
+
*/
|
|
118
|
+
throttle?: number;
|
|
119
|
+
/**
|
|
120
|
+
* 是否在loading状态下不触发getData
|
|
121
|
+
* @en Whether to not trigger getData in the loading state
|
|
122
|
+
* @default true
|
|
123
|
+
*/
|
|
124
|
+
blockWhenLoading?: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* 刚加载好组件时是否自动先请求一次,trigger=scroll时有效
|
|
127
|
+
* @en Whether to automatically request once when the component is just loaded, valid when trigger=scroll
|
|
128
|
+
* @default true
|
|
129
|
+
*/
|
|
130
|
+
getDataAtFirst?: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* 当 getDataAtFirst 值为 false 且数据不满一屏时是否触发一次请求,trigger=scroll时有效
|
|
133
|
+
* @en Whether to trigger a request when getDataAtFirst equals false and the data is not full of one screen, valid when trigger=scroll
|
|
134
|
+
* @default false
|
|
135
|
+
*/
|
|
136
|
+
getDataWhenNoScrollAtFirst?: boolean;
|
|
137
|
+
/**
|
|
138
|
+
* 状态改变时回调
|
|
139
|
+
* @en Callback when state changes
|
|
140
|
+
*/
|
|
141
|
+
onStatusChange?: (status: LoadMoreStatus, scene?: string) => void;
|
|
142
|
+
/**
|
|
143
|
+
* 组件被点击时回调
|
|
144
|
+
* @en Callback when the component is clicked
|
|
145
|
+
*/
|
|
146
|
+
onClick?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
|
147
|
+
/**
|
|
148
|
+
* 滚动到(底部 - threshold)距离时触发
|
|
149
|
+
* @en Callback when scrolling to (bottom - threshold) distance
|
|
150
|
+
*/
|
|
151
|
+
onEndReached?: () => void;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface LoadMoreRef {
|
|
155
|
+
/**
|
|
156
|
+
* 最外层元素 DOM
|
|
157
|
+
* @en The outermost element DOM
|
|
158
|
+
*/
|
|
159
|
+
dom: HTMLDivElement | null;
|
|
160
|
+
/**
|
|
161
|
+
* 手动更改组件状态
|
|
162
|
+
* @en Change component state manually
|
|
163
|
+
*/
|
|
164
|
+
changeStatus: (status: LoadMoreStatus, scene?: string) => void;
|
|
165
|
+
/**
|
|
166
|
+
* 判断是否滚动到底部并手动触发数据获取
|
|
167
|
+
* @en Determine whether to scroll to the bottom and manually trigger data acquisition
|
|
168
|
+
*/
|
|
169
|
+
getDataWithEndReachCheck: () => void;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 上拉加载组件,支持`scroll`和`click`两种触发加载方式,支持滚动监听。支持受控与不受控两种形式。<br>如果引入组件后发现仅触发了初始的`getData`,请确认是否在`getData`方法内没有调用`callback`移除 loading 状态,且未设置`blockWhenLoading`属性为 false。
|
|
174
|
+
* @en Pull-up loading component, supports `scroll` and `click` two trigger loading methods, supports scroll monitoring. Both controlled and uncontrolled forms are supported. <br>If only the initial `getData` is triggered after the component is introduced, please make sure that the `callback` is not called in the `getData` method to remove the loading state, and the `blockWhenLoading` property is not set to false.
|
|
175
|
+
* @type 反馈
|
|
176
|
+
* @type_en Feedback
|
|
177
|
+
* @name 加载更多
|
|
178
|
+
* @name_en LoadMore
|
|
179
|
+
*/
|
|
180
|
+
const LoadMore = forwardRef((props: LoadMoreProps, ref: Ref<LoadMoreRef>) => {
|
|
181
|
+
const {
|
|
182
|
+
className = '',
|
|
183
|
+
style,
|
|
184
|
+
disabled,
|
|
185
|
+
beforeReadyArea,
|
|
186
|
+
loadingArea,
|
|
187
|
+
noMoreArea,
|
|
188
|
+
prepareArea,
|
|
189
|
+
retryArea,
|
|
190
|
+
defaultStatus = 'prepare',
|
|
191
|
+
status,
|
|
192
|
+
getScrollContainer,
|
|
193
|
+
getOffsetNode,
|
|
194
|
+
trigger = 'scroll',
|
|
195
|
+
threshold = 200,
|
|
196
|
+
throttle = 0,
|
|
197
|
+
getDataAtFirst = true,
|
|
198
|
+
getData,
|
|
199
|
+
blockWhenLoading = true,
|
|
200
|
+
onStatusChange,
|
|
201
|
+
onClick,
|
|
202
|
+
onEndReached,
|
|
203
|
+
getDataWhenNoScrollAtFirst = false,
|
|
204
|
+
} = props;
|
|
205
|
+
const domRef = useRef<HTMLDivElement | null>(null);
|
|
206
|
+
const requestAtFirst = trigger === 'scroll' ? getDataAtFirst : false;
|
|
207
|
+
const [innerStatus, setInnerStatus] = useState<LoadMoreStatus>(defaultStatus);
|
|
208
|
+
const lastScrollEndRef = useRef(false);
|
|
209
|
+
const nowStatus = status || innerStatus;
|
|
210
|
+
const statusRef = useRef<LoadMoreStatus>(nowStatus);
|
|
211
|
+
const disabledRef = useLatestRef(disabled);
|
|
212
|
+
|
|
213
|
+
const changeStatus = useCallback(
|
|
214
|
+
(st: LoadMoreStatus, scene?: string) => {
|
|
215
|
+
setInnerStatus(st);
|
|
216
|
+
onStatusChange && onStatusChange(st, scene);
|
|
217
|
+
},
|
|
218
|
+
[onStatusChange],
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const triggerGetData = useCallback(
|
|
222
|
+
(scene: string) => {
|
|
223
|
+
if (disabledRef.current) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (blockWhenLoading && statusRef.current === 'loading') {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
changeStatus('loading', scene);
|
|
230
|
+
getData?.(st => {
|
|
231
|
+
changeStatus(st, 'manual');
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
[blockWhenLoading, changeStatus, getData],
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
if (!blockWhenLoading || nowStatus !== 'loading') {
|
|
239
|
+
lastScrollEndRef.current = false;
|
|
240
|
+
}
|
|
241
|
+
statusRef.current = nowStatus;
|
|
242
|
+
}, [nowStatus]);
|
|
243
|
+
|
|
244
|
+
useUpdateEffect(() => {
|
|
245
|
+
// 当状态被设置为 prepare ,且页面总长度不足一屏时,再触发一次 getData
|
|
246
|
+
// @en When the state is set to prepare and the total page length is less than one screen, trigger getData again
|
|
247
|
+
if (trigger === 'scroll' && nowStatus === 'prepare' && checkNeedTrigger(0, threshold)) {
|
|
248
|
+
triggerGetData('pageEnd');
|
|
249
|
+
}
|
|
250
|
+
}, [nowStatus]);
|
|
251
|
+
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
if (requestAtFirst) {
|
|
254
|
+
if (statusRef.current === 'prepare' && !disabled) {
|
|
255
|
+
triggerGetData('requestAtFirst');
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
if (
|
|
259
|
+
trigger === 'scroll' &&
|
|
260
|
+
nowStatus === 'prepare' &&
|
|
261
|
+
checkNeedTrigger(0, threshold) &&
|
|
262
|
+
!disabled &&
|
|
263
|
+
getDataWhenNoScrollAtFirst
|
|
264
|
+
) {
|
|
265
|
+
triggerGetData('pageEnd');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}, [trigger, disabled]);
|
|
269
|
+
|
|
270
|
+
const handleContainerScroll = useCallback(() => {
|
|
271
|
+
const scrollTop = getScrollContainerAttribute('scrollTop', getScrollContainer);
|
|
272
|
+
if (checkNeedTrigger(scrollTop, threshold + 1)) {
|
|
273
|
+
if (!lastScrollEndRef.current) {
|
|
274
|
+
lastScrollEndRef.current = true;
|
|
275
|
+
onEndReached && onEndReached();
|
|
276
|
+
if (!['nomore', 'retry', 'before-ready'].includes(statusRef.current)) {
|
|
277
|
+
triggerGetData('scrollEnd');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
lastScrollEndRef.current = false;
|
|
282
|
+
}
|
|
283
|
+
}, [getScrollContainer, threshold, triggerGetData, onEndReached]);
|
|
284
|
+
|
|
285
|
+
useEffect(() => {
|
|
286
|
+
let binded: HTMLElement | Window | null = null;
|
|
287
|
+
const scrollFunc = throttle
|
|
288
|
+
? lodashThrottle(handleContainerScroll, throttle)
|
|
289
|
+
: handleContainerScroll;
|
|
290
|
+
if (trigger === 'scroll' && !disabled) {
|
|
291
|
+
const container = getValidScrollContainer(getScrollContainer);
|
|
292
|
+
if (container) {
|
|
293
|
+
container.addEventListener('scroll', scrollFunc);
|
|
294
|
+
binded = container;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return () => {
|
|
298
|
+
if (binded) {
|
|
299
|
+
binded.removeEventListener('scroll', scrollFunc);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}, [trigger, disabled, getScrollContainer, handleContainerScroll, throttle]);
|
|
303
|
+
|
|
304
|
+
const getDataWithEndReachCheck = () => {
|
|
305
|
+
if (checkNeedTrigger(0, threshold)) {
|
|
306
|
+
triggerGetData('pageEnd');
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
useImperativeHandle(
|
|
311
|
+
ref,
|
|
312
|
+
() => ({
|
|
313
|
+
dom: domRef.current,
|
|
314
|
+
changeStatus,
|
|
315
|
+
getDataWithEndReachCheck,
|
|
316
|
+
}),
|
|
317
|
+
[changeStatus],
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
function checkNeedTrigger(top: number, ths: number) {
|
|
321
|
+
const scrollHeight = getOffsetNode
|
|
322
|
+
? getScrollContainerAttribute('offsetHeight', getOffsetNode) +
|
|
323
|
+
getScrollContainerAttribute('offsetTop', getOffsetNode)
|
|
324
|
+
: getScrollContainerAttribute('scrollHeight', getScrollContainer);
|
|
325
|
+
const clientHeight = getScrollContainerAttribute('clientHeight', getScrollContainer);
|
|
326
|
+
return scrollHeight - top - clientHeight <= ths;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function handleClick(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
|
330
|
+
if (
|
|
331
|
+
(trigger === 'click' && statusRef.current === 'prepare') ||
|
|
332
|
+
statusRef.current === 'retry'
|
|
333
|
+
) {
|
|
334
|
+
triggerGetData('click');
|
|
335
|
+
}
|
|
336
|
+
onClick && onClick(e);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function renderArea(locale: ILocale) {
|
|
340
|
+
switch (nowStatus) {
|
|
341
|
+
case 'before-ready':
|
|
342
|
+
return beforeReadyArea;
|
|
343
|
+
case 'prepare':
|
|
344
|
+
return prepareArea === void 0 ? (
|
|
345
|
+
<div className="load-more-text prepare">
|
|
346
|
+
{trigger === 'scroll'
|
|
347
|
+
? locale.LoadMore.prepareScrollText
|
|
348
|
+
: locale.LoadMore.prepareClickText}
|
|
349
|
+
{locale.LoadMore.loadMoreText}
|
|
350
|
+
</div>
|
|
351
|
+
) : (
|
|
352
|
+
prepareArea
|
|
353
|
+
);
|
|
354
|
+
case 'loading':
|
|
355
|
+
return loadingArea === void 0 ? (
|
|
356
|
+
<div className="load-more-text loading">{locale.LoadMore.loadingText}</div>
|
|
357
|
+
) : (
|
|
358
|
+
loadingArea
|
|
359
|
+
);
|
|
360
|
+
case 'nomore':
|
|
361
|
+
return noMoreArea === void 0 ? (
|
|
362
|
+
<div className="load-more-text nomore">{locale.LoadMore.noDataText}</div>
|
|
363
|
+
) : (
|
|
364
|
+
noMoreArea
|
|
365
|
+
);
|
|
366
|
+
case 'retry':
|
|
367
|
+
return retryArea === void 0 ? (
|
|
368
|
+
<div className="load-more-text retry">{locale.LoadMore.failLoadText}</div>
|
|
369
|
+
) : (
|
|
370
|
+
retryArea
|
|
371
|
+
);
|
|
372
|
+
default:
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return (
|
|
378
|
+
<ContextLayout>
|
|
379
|
+
{({ prefixCls, locale = defaultLocale }) => (
|
|
380
|
+
<div
|
|
381
|
+
className={cls(`${prefixCls}-load-more status-${nowStatus}`, className, {
|
|
382
|
+
[`${prefixCls}-load-more-disabled`]: disabled,
|
|
383
|
+
})}
|
|
384
|
+
ref={domRef}
|
|
385
|
+
style={style}
|
|
386
|
+
onClick={handleClick}
|
|
387
|
+
>
|
|
388
|
+
{renderArea(locale)}
|
|
389
|
+
</div>
|
|
390
|
+
)}
|
|
391
|
+
</ContextLayout>
|
|
392
|
+
);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
export default LoadMore;
|