@tendaui/components 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 (245) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +176 -0
  3. package/alert/Alert.tsx +147 -0
  4. package/alert/defaultProps.ts +3 -0
  5. package/alert/index.ts +9 -0
  6. package/alert/style/css.js +1 -0
  7. package/alert/style/index.js +1 -0
  8. package/alert/type.ts +44 -0
  9. package/badge/Badge.tsx +85 -0
  10. package/badge/defaultProps.ts +10 -0
  11. package/badge/index.ts +9 -0
  12. package/badge/style/css.js +1 -0
  13. package/badge/style/index.js +1 -0
  14. package/badge/type.ts +51 -0
  15. package/button/Button.tsx +95 -0
  16. package/button/defaultProps.ts +13 -0
  17. package/button/index.ts +7 -0
  18. package/button/style/css.js +1 -0
  19. package/button/style/index.js +1 -0
  20. package/button/type.ts +82 -0
  21. package/checkbox/Checkbox.tsx +19 -0
  22. package/checkbox/CheckboxGroup.tsx +207 -0
  23. package/checkbox/defaultProps.ts +14 -0
  24. package/checkbox/index.ts +10 -0
  25. package/checkbox/style/css.js +1 -0
  26. package/checkbox/style/index.js +1 -0
  27. package/checkbox/type.ts +117 -0
  28. package/common/Check.tsx +131 -0
  29. package/common/FakeArrow.tsx +36 -0
  30. package/common/PluginContainer.tsx +21 -0
  31. package/common/Portal.tsx +67 -0
  32. package/common.ts +76 -0
  33. package/config-provider/ConfigContext.tsx +21 -0
  34. package/config-provider/ConfigProvider.tsx +53 -0
  35. package/config-provider/index.ts +9 -0
  36. package/config-provider/type.ts +1062 -0
  37. package/dialog/Dialog.tsx +254 -0
  38. package/dialog/DialogCard.tsx +152 -0
  39. package/dialog/defaultProps.ts +25 -0
  40. package/dialog/hooks/useDialogDrag.ts +50 -0
  41. package/dialog/hooks/useDialogEsc.ts +31 -0
  42. package/dialog/hooks/useDialogPosition.ts +36 -0
  43. package/dialog/hooks/useLockStyle.ts +54 -0
  44. package/dialog/index.ts +13 -0
  45. package/dialog/plugin.tsx +78 -0
  46. package/dialog/style/css.js +1 -0
  47. package/dialog/style/index.js +1 -0
  48. package/dialog/type.ts +241 -0
  49. package/dialog/utils.ts +4 -0
  50. package/form/Form.tsx +136 -0
  51. package/form/FormContext.tsx +64 -0
  52. package/form/FormItem.tsx +554 -0
  53. package/form/FormList.tsx +303 -0
  54. package/form/const.ts +6 -0
  55. package/form/defaultProps.ts +26 -0
  56. package/form/formModel.ts +117 -0
  57. package/form/hooks/interface.ts +20 -0
  58. package/form/hooks/useForm.ts +122 -0
  59. package/form/hooks/useFormItemInitialData.ts +95 -0
  60. package/form/hooks/useFormItemStyle.tsx +122 -0
  61. package/form/hooks/useInstance.tsx +275 -0
  62. package/form/hooks/useWatch.ts +42 -0
  63. package/form/index.ts +11 -0
  64. package/form/style/css.js +1 -0
  65. package/form/style/index.js +1 -0
  66. package/form/type.ts +519 -0
  67. package/form/utils/index.ts +69 -0
  68. package/hooks/useAttach.ts +24 -0
  69. package/hooks/useCommonClassName.ts +45 -0
  70. package/hooks/useConfig.ts +3 -0
  71. package/hooks/useControlled.ts +39 -0
  72. package/hooks/useDefaultProps.ts +16 -0
  73. package/hooks/useDomCallback.ts +13 -0
  74. package/hooks/useDomRefCallback.ts +12 -0
  75. package/hooks/useDragSorter.tsx +151 -0
  76. package/hooks/useEventCallback.ts +47 -0
  77. package/hooks/useGlobalConfig.ts +14 -0
  78. package/hooks/useGlobalIcon.ts +14 -0
  79. package/hooks/useLastest.ts +13 -0
  80. package/hooks/useLayoutEffect.ts +7 -0
  81. package/hooks/useMouseEvent.ts +142 -0
  82. package/hooks/useMutationObserver.ts +56 -0
  83. package/hooks/usePopper.ts +189 -0
  84. package/hooks/useRipple.ts +0 -0
  85. package/hooks/useSetState.ts +25 -0
  86. package/hooks/useVirtualScroll.ts +246 -0
  87. package/hooks/useWindowSize.ts +31 -0
  88. package/index.ts +70 -0
  89. package/input/Input.tsx +383 -0
  90. package/input/InputGroup.tsx +29 -0
  91. package/input/defaultProps.ts +22 -0
  92. package/input/index.ts +11 -0
  93. package/input/style/css.js +1 -0
  94. package/input/style/index.js +1 -0
  95. package/input/type.ts +219 -0
  96. package/loading/Gradient.tsx +36 -0
  97. package/loading/Loading.tsx +169 -0
  98. package/loading/circleAdapter.ts +44 -0
  99. package/loading/defaultProps.ts +12 -0
  100. package/loading/index.ts +13 -0
  101. package/loading/style/css.js +1 -0
  102. package/loading/style/index.js +1 -0
  103. package/loading/type.ts +71 -0
  104. package/loading/utils/setStyle.ts +13 -0
  105. package/myform/index.ts +0 -0
  106. package/notification/Notify.ts +24 -0
  107. package/notification/NotifyContainer.tsx +90 -0
  108. package/notification/NotifyContext.tsx +173 -0
  109. package/notification/NotifyItem.tsx +121 -0
  110. package/notification/index.ts +3 -0
  111. package/notification/style/css.js +1 -0
  112. package/notification/style/index.js +1 -0
  113. package/notification/type.ts +23 -0
  114. package/package.json +52 -0
  115. package/popup/Popup.tsx +264 -0
  116. package/popup/defaultProps.ts +13 -0
  117. package/popup/hooks/useTrigger.ts +276 -0
  118. package/popup/index.ts +6 -0
  119. package/popup/style/css.js +1 -0
  120. package/popup/style/index.js +1 -0
  121. package/popup/type.ts +130 -0
  122. package/portal/Portal.tsx +63 -0
  123. package/portal/index.ts +1 -0
  124. package/select/Option.tsx +162 -0
  125. package/select/OptionGroup.tsx +30 -0
  126. package/select/PopupContent.tsx +271 -0
  127. package/select/Select.tsx +586 -0
  128. package/select/defaultProps.ts +27 -0
  129. package/select/hooks/useOptions.ts +120 -0
  130. package/select/hooks/usePanelVirtualScroll.ts +111 -0
  131. package/select/index.ts +9 -0
  132. package/select/style/css.js +1 -0
  133. package/select/style/index.js +2 -0
  134. package/select/type.ts +382 -0
  135. package/select/utils/helper.ts +256 -0
  136. package/select-input/SelectInput.tsx +98 -0
  137. package/select-input/defaultProps.ts +15 -0
  138. package/select-input/hook/useMultiple.tsx +100 -0
  139. package/select-input/hook/useOverlayInnerStyle.ts +84 -0
  140. package/select-input/hook/useSingle.tsx +112 -0
  141. package/select-input/index.ts +6 -0
  142. package/select-input/interface.ts +18 -0
  143. package/select-input/style/css.js +1 -0
  144. package/select-input/style/index.js +1 -0
  145. package/select-input/type.ts +280 -0
  146. package/space/defaultProps.ts +0 -0
  147. package/space/index.ts +0 -0
  148. package/space/type.ts +0 -0
  149. package/style/index.js +2 -0
  150. package/styles/_global.scss +39 -0
  151. package/styles/_vars.scss +386 -0
  152. package/styles/components/alert/_index.scss +175 -0
  153. package/styles/components/alert/_vars.scss +39 -0
  154. package/styles/components/badge/_index.scss +70 -0
  155. package/styles/components/badge/_vars.scss +25 -0
  156. package/styles/components/button/_index.scss +511 -0
  157. package/styles/components/button/_mixins.scss +39 -0
  158. package/styles/components/button/_vars.scss +122 -0
  159. package/styles/components/checkbox/_index.scss +158 -0
  160. package/styles/components/checkbox/_mixin.scss +0 -0
  161. package/styles/components/checkbox/_var.scss +60 -0
  162. package/styles/components/dialog/_animate.scss +135 -0
  163. package/styles/components/dialog/_index.scss +311 -0
  164. package/styles/components/dialog/_mixins.scss +0 -0
  165. package/styles/components/dialog/_vars.scss +59 -0
  166. package/styles/components/form/_index.scss +174 -0
  167. package/styles/components/form/_mixins.scss +76 -0
  168. package/styles/components/form/_vars.scss +100 -0
  169. package/styles/components/input/_index.scss +349 -0
  170. package/styles/components/input/_map.scss +0 -0
  171. package/styles/components/input/_mixins.scss +116 -0
  172. package/styles/components/input/_vars.scss +134 -0
  173. package/styles/components/loading/_index.scss +112 -0
  174. package/styles/components/loading/_vars.scss +39 -0
  175. package/styles/components/notification/_index.scss +160 -0
  176. package/styles/components/notification/_mixins.scss +12 -0
  177. package/styles/components/notification/_vars.scss +59 -0
  178. package/styles/components/popup/_index.scss +82 -0
  179. package/styles/components/popup/_mixin.scss +149 -0
  180. package/styles/components/popup/_var.scss +31 -0
  181. package/styles/components/select/_index.scss +290 -0
  182. package/styles/components/select/_var.scss +65 -0
  183. package/styles/components/select-input/_index.scss +5 -0
  184. package/styles/components/select-input/_var.scss +3 -0
  185. package/styles/components/switch/_index.scss +279 -0
  186. package/styles/components/switch/_mixins.scss +0 -0
  187. package/styles/components/switch/_vars.scss +61 -0
  188. package/styles/components/tag/_index.scss +316 -0
  189. package/styles/components/tag/_var.scss +85 -0
  190. package/styles/components/tag-input/_index.scss +163 -0
  191. package/styles/components/tag-input/_vars.scss +16 -0
  192. package/styles/globals.css +250 -0
  193. package/styles/mixins/_focus.scss +7 -0
  194. package/styles/mixins/_layout.scss +32 -0
  195. package/styles/mixins/_reset.scss +10 -0
  196. package/styles/mixins/_scrollbar.scss +31 -0
  197. package/styles/mixins/_text.scss +48 -0
  198. package/styles/rillple.css +16 -0
  199. package/styles/scrollbar.css +42 -0
  200. package/styles/themes/_dark.scss +191 -0
  201. package/styles/themes/_font.scss +79 -0
  202. package/styles/themes/_index.scss +5 -0
  203. package/styles/themes/_light.scss +190 -0
  204. package/styles/themes/_radius.scss +9 -0
  205. package/styles/themes/_size.scss +68 -0
  206. package/styles/themes.css +66 -0
  207. package/styles/utilities/_animation.scss +57 -0
  208. package/styles/utilities/_tips.scss +9 -0
  209. package/switch/Switch.tsx +120 -0
  210. package/switch/defaultProps.ts +3 -0
  211. package/switch/index.ts +7 -0
  212. package/switch/style/css.js +1 -0
  213. package/switch/style/index.js +1 -0
  214. package/switch/type.ts +46 -0
  215. package/tag/Tag.tsx +149 -0
  216. package/tag/defaultProps.ts +19 -0
  217. package/tag/index.ts +8 -0
  218. package/tag/style/css.js +1 -0
  219. package/tag/style/index.js +1 -0
  220. package/tag/type.ts +170 -0
  221. package/tag-input/TagInput.tsx +215 -0
  222. package/tag-input/defaultProps.ts +15 -0
  223. package/tag-input/hooks/useHover.ts +28 -0
  224. package/tag-input/hooks/useTagList.tsx +131 -0
  225. package/tag-input/hooks/useTagScroll.ts +105 -0
  226. package/tag-input/index.ts +9 -0
  227. package/tag-input/style/css.js +1 -0
  228. package/tag-input/style/index.js +1 -0
  229. package/tag-input/type.ts +224 -0
  230. package/tag-input/useTagList.tsx +131 -0
  231. package/utils/composeRefs.ts +14 -0
  232. package/utils/dom.ts +29 -0
  233. package/utils/forwardRefWithStatics.ts +12 -0
  234. package/utils/getScrollbarWidth.ts +11 -0
  235. package/utils/helper.ts +161 -0
  236. package/utils/isFragment.ts +22 -0
  237. package/utils/listener.ts +37 -0
  238. package/utils/noop.ts +3 -0
  239. package/utils/parentTNode.ts +38 -0
  240. package/utils/parseTNode.ts +38 -0
  241. package/utils/react-render.ts +108 -0
  242. package/utils/ref.ts +6 -0
  243. package/utils/refs.ts +81 -0
  244. package/utils/style.ts +60 -0
  245. package/utils/transition.ts +28 -0
@@ -0,0 +1,554 @@
1
+ import React, { forwardRef, ReactNode, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
2
+ import {
3
+ IconCheckCircleStroked as TdCheckCircleFilledIcon,
4
+ IconClose as TdCloseCircleFilledIcon,
5
+ IconClose as TdErrorCircleFilledIcon
6
+ } from "@tendaui/icons";
7
+ import { flattenDeep, get, isEqual, isFunction, isObject, isString, merge, set, unset } from "lodash-es";
8
+ import { StyledProps } from "../common";
9
+ import useConfig from "../hooks/useConfig";
10
+ import useDefaultProps from "../hooks/useDefaultProps";
11
+ import useGlobalIcon from "../hooks/useGlobalIcon";
12
+ // import { useLocaleReceiver } from '../locale/LocalReceiver';
13
+ import { ValidateStatus } from "./const";
14
+ import { formItemDefaultProps } from "./defaultProps";
15
+ import { useFormContext, useFormListContext } from "./FormContext";
16
+ import { parseMessage, validate as validateModal } from "./formModel";
17
+ import { HOOK_MARK } from "./hooks/useForm";
18
+ import useFormItemInitialData, { ctrlKeyMap } from "./hooks/useFormItemInitialData";
19
+ import useFormItemStyle from "./hooks/useFormItemStyle";
20
+ import type {
21
+ FormInstanceFunctions,
22
+ FormItemValidateMessage,
23
+ FormRule,
24
+ NamePath,
25
+ TdFormItemProps,
26
+ ValueType
27
+ } from "./type";
28
+ import { calcFieldValue } from "./utils";
29
+
30
+ export interface FormItemProps extends TdFormItemProps, StyledProps {
31
+ children?: React.ReactNode | React.ReactNode[] | ((form: FormInstanceFunctions) => React.ReactElement);
32
+ }
33
+
34
+ export interface FormItemInstance {
35
+ name?: NamePath;
36
+ isUpdated?: boolean;
37
+ value?: ValueType;
38
+ getValue?: (...args: unknown[]) => unknown;
39
+ setValue?: (...args: unknown[]) => unknown;
40
+ setField?: (...args: unknown[]) => unknown;
41
+ validate?: (...args: unknown[]) => unknown;
42
+ resetField?: (...args: unknown[]) => unknown;
43
+ setValidateMessage?: (...args: unknown[]) => unknown;
44
+ getValidateMessage?: (...args: unknown[]) => unknown;
45
+ resetValidate?: (...args: unknown[]) => unknown;
46
+ validateOnly?: (...args: unknown[]) => unknown;
47
+ isFormList?: boolean;
48
+ }
49
+
50
+ const FormItem = forwardRef<FormItemInstance, FormItemProps>((originalProps, ref) => {
51
+ // const [locale, t] = useLocaleReceiver('form');
52
+ const { classPrefix, form: globalFormConfig } = useConfig();
53
+ useGlobalIcon({
54
+ CheckCircleFilledIcon: TdCheckCircleFilledIcon,
55
+ CloseCircleFilledIcon: TdCloseCircleFilledIcon,
56
+ ErrorCircleFilledIcon: TdErrorCircleFilledIcon
57
+ });
58
+ const {
59
+ form,
60
+ colon,
61
+ layout,
62
+ requiredMark: requiredMarkFromContext,
63
+ requiredMarkPosition,
64
+ labelAlign: labelAlignFromContext,
65
+ labelWidth: labelWidthFromContext,
66
+ showErrorMessage: showErrorMessageFromContext,
67
+ disabled: disabledFromContext,
68
+ resetType: resetTypeFromContext,
69
+ rules: rulesFromContext,
70
+ statusIcon: statusIconFromContext,
71
+ errorMessage,
72
+ formMapRef,
73
+ onFormItemValueChange
74
+ } = useFormContext();
75
+
76
+ const { name: formListName, rules: formListRules, formListMapRef, form: formOfFormList } = useFormListContext();
77
+ const props = useDefaultProps<FormItemProps>(originalProps, formItemDefaultProps);
78
+
79
+ const {
80
+ children,
81
+ style,
82
+ label,
83
+ name,
84
+ status,
85
+ tips,
86
+ help,
87
+ valueFormat,
88
+ initialData,
89
+ className,
90
+ shouldUpdate,
91
+ successBorder,
92
+ statusIcon = statusIconFromContext,
93
+ rules: innerRules = getInnerRules(name, rulesFromContext, formListName, formListRules),
94
+ labelWidth = labelWidthFromContext,
95
+ labelAlign = labelAlignFromContext,
96
+ requiredMark = requiredMarkFromContext
97
+ } = props;
98
+
99
+ const { getDefaultInitialData } = useFormItemInitialData(name);
100
+
101
+ const [, forceUpdate] = useState({}); // custom render state
102
+ const [freeShowErrorMessage, setFreeShowErrorMessage] = useState(undefined);
103
+ const [errorList, setErrorList] = useState([]);
104
+ const [successList, setSuccessList] = useState([]);
105
+ const [verifyStatus, setVerifyStatus] = useState("validating");
106
+ const [resetValidating, setResetValidating] = useState(false);
107
+ const [needResetField, setNeedResetField] = useState(false);
108
+ const [formValue, setFormValue] = useState(() => {
109
+ const fieldName = flattenDeep([formListName, name]);
110
+ const storeValue = get(form?.store, fieldName);
111
+ // if (!storeValue && formListName) return; // TODO 针对新增空的动态表单情况,避免回填默认值
112
+ return (
113
+ storeValue ??
114
+ getDefaultInitialData({
115
+ children,
116
+ initialData
117
+ })
118
+ );
119
+ });
120
+
121
+ const formItemRef = useRef<FormItemInstance>(null); // 当前 formItem 实例
122
+ const innerFormItemsRef = useRef([]);
123
+ const shouldEmitChangeRef = useRef(false); // onChange 冒泡开关
124
+ const isUpdatedRef = useRef(false); // 校验开关
125
+ const shouldValidate = useRef(false); // 校验开关
126
+ const valueRef = useRef(formValue); // 当前最新值
127
+ const errorListMapRef = useRef(new Map());
128
+
129
+ const isSameForm = useMemo(() => isEqual(form, formOfFormList), [form, formOfFormList]); // 用于处理 Form 嵌套的情况
130
+ const snakeName = []
131
+ .concat(isSameForm ? formListName : undefined, name)
132
+ .filter((item) => item !== undefined)
133
+ .toString(); // 转化 name
134
+
135
+ const errorMessages = useMemo(() => errorMessage ?? globalFormConfig.errorMessage, [errorMessage, globalFormConfig]);
136
+
137
+ const showErrorMessage = useMemo(() => {
138
+ if (typeof freeShowErrorMessage === "boolean") return freeShowErrorMessage;
139
+ if (typeof props.showErrorMessage === "boolean") return props.showErrorMessage;
140
+ return showErrorMessageFromContext;
141
+ }, [freeShowErrorMessage, props.showErrorMessage, showErrorMessageFromContext]);
142
+
143
+ const { formItemClass, formItemLabelClass, contentClass, labelStyle, contentStyle, helpNode, extraNode } =
144
+ useFormItemStyle({
145
+ className,
146
+ help,
147
+ tips,
148
+ snakeName,
149
+ status,
150
+ successBorder,
151
+ errorList,
152
+ successList,
153
+ layout,
154
+ verifyStatus,
155
+ label,
156
+ labelWidth,
157
+ labelAlign,
158
+ requiredMark,
159
+ requiredMarkPosition,
160
+ showErrorMessage,
161
+ innerRules
162
+ });
163
+
164
+ // 更新 form 表单字段
165
+ const updateFormValue = (newVal: ValueType, validate = true, shouldEmitChange = false) => {
166
+ const { setPrevStore } = form?.getInternalHooks?.(HOOK_MARK) || {};
167
+ setPrevStore?.(form?.getFieldsValue?.(true));
168
+ shouldEmitChangeRef.current = shouldEmitChange;
169
+ isUpdatedRef.current = true;
170
+ shouldValidate.current = validate;
171
+ valueRef.current = newVal;
172
+
173
+ let fieldName = [].concat(name);
174
+ let fieldValue = formValue;
175
+ if (formListName) {
176
+ fieldName = [].concat(formListName, name);
177
+ fieldValue = get(form?.store, fieldName);
178
+ }
179
+
180
+ fieldName = fieldName.filter((item) => item !== undefined);
181
+
182
+ if (!fieldName) return;
183
+ if (isEqual(fieldValue, newVal)) return;
184
+ set(form?.store, fieldName, newVal);
185
+ setFormValue(newVal);
186
+ };
187
+
188
+ // 初始化 rules,最终以 formItem 上优先级最高
189
+ function getInnerRules(name, formRules, formListName, formListRules): FormRule[] {
190
+ if (Array.isArray(name)) {
191
+ return get(formRules?.[formListName], name) || get(formListRules, name) || get(formRules, name.join(".")) || [];
192
+ }
193
+ return formRules?.[name] || formListRules || [];
194
+ }
195
+
196
+ const renderSuffixIcon = () => {
197
+ if (statusIcon === false) return null;
198
+
199
+ const resultIcon = (iconSlot: ReactNode) => <span className={`${classPrefix}-form__status`}>{iconSlot}</span>;
200
+
201
+ const getDefaultIcon = () => {
202
+ const iconMap = {
203
+ success: <TdCheckCircleFilledIcon size="large" />,
204
+ error: <TdCloseCircleFilledIcon size="large" />,
205
+ warning: <TdErrorCircleFilledIcon size="large" />
206
+ };
207
+ if (verifyStatus === ValidateStatus.SUCCESS) {
208
+ return resultIcon(iconMap[verifyStatus]);
209
+ }
210
+ if (errorList && errorList[0]) {
211
+ const type = errorList[0].type || "error";
212
+ return resultIcon(iconMap[type]);
213
+ }
214
+ return null;
215
+ };
216
+
217
+ if (React.isValidElement(statusIcon)) {
218
+ // @ts-expect-error React.cloneElement type inference issue
219
+ return resultIcon(React.cloneElement(statusIcon, { style: { color: "unset" }, ...statusIcon.props }));
220
+ }
221
+ if (statusIcon === true) {
222
+ return getDefaultIcon();
223
+ }
224
+
225
+ return null;
226
+ };
227
+
228
+ async function analysisValidateResult(trigger) {
229
+ const result = {
230
+ successList: [],
231
+ errorList: [],
232
+ rules: [],
233
+ resultList: [],
234
+ allowSetValue: false
235
+ };
236
+ result.rules = trigger === "all" ? innerRules : innerRules.filter((item) => (item.trigger || "change") === trigger);
237
+ if (!result.rules?.length) {
238
+ setResetValidating(false);
239
+ return result;
240
+ }
241
+ result.allowSetValue = true;
242
+ result.resultList = await validateModal(formValue, result.rules);
243
+ result.errorList = result.resultList
244
+ .filter((item) => item.result !== true)
245
+ .map((item) => {
246
+ Object.keys(item).forEach((key) => {
247
+ if (!item.message && errorMessages[key]) {
248
+ item.message = parseMessage(errorMessages[key], {
249
+ validate: item[key],
250
+ name: isString(label) ? label : String(name)
251
+ });
252
+ }
253
+ });
254
+ return item;
255
+ });
256
+ // 仅有自定义校验方法才会存在 successList
257
+ result.successList = result.resultList.filter(
258
+ (item) => item.result === true && item.message && item.type === "success"
259
+ );
260
+
261
+ return result;
262
+ }
263
+
264
+ async function validate(trigger = "all", showErrorMessage?: boolean) {
265
+ if (innerFormItemsRef.current.length) {
266
+ return innerFormItemsRef.current.map((innerFormItem) => innerFormItem?.validate(trigger, showErrorMessage));
267
+ }
268
+
269
+ setResetValidating(true);
270
+ // undefined | boolean
271
+ setFreeShowErrorMessage(showErrorMessage);
272
+
273
+ const {
274
+ successList: innerSuccessList,
275
+ errorList: innerErrorList,
276
+ rules: validateRules,
277
+ resultList,
278
+ allowSetValue
279
+ } = await analysisValidateResult(trigger);
280
+
281
+ // 缓存不同 trigger 下的错误信息 all 包含了所有场景需过滤
282
+ if (innerErrorList.length && trigger !== "all") {
283
+ errorListMapRef.current.set(trigger, innerErrorList);
284
+ } else {
285
+ errorListMapRef.current.delete(trigger);
286
+ }
287
+
288
+ // all 校验无错误信息时清空所有错误缓存
289
+ if (!innerErrorList.length && trigger === "all") {
290
+ errorListMapRef.current.clear();
291
+ }
292
+
293
+ const cacheErrorList = [...errorListMapRef.current.values()].flat();
294
+
295
+ if (allowSetValue) {
296
+ setSuccessList(innerSuccessList);
297
+ setErrorList(cacheErrorList.length ? cacheErrorList : innerErrorList);
298
+ }
299
+ // 根据校验结果设置校验状态
300
+ if (validateRules.length) {
301
+ let status = ValidateStatus.SUCCESS;
302
+ if (innerErrorList.length || cacheErrorList.length) {
303
+ status = innerErrorList?.[0]?.type || cacheErrorList?.[0]?.type || ValidateStatus.ERROR;
304
+ }
305
+ setVerifyStatus(status);
306
+ } else {
307
+ setVerifyStatus(ValidateStatus.VALIDATING);
308
+ }
309
+ // 重置处理
310
+ if (needResetField) {
311
+ resetHandler();
312
+ }
313
+
314
+ setResetValidating(false);
315
+ return {
316
+ [snakeName]: innerErrorList.length === 0 ? true : resultList
317
+ };
318
+ }
319
+
320
+ async function validateOnly(trigger = "all") {
321
+ const { errorList: innerErrorList, resultList } = await analysisValidateResult(trigger);
322
+
323
+ return {
324
+ [snakeName]: innerErrorList.length === 0 ? true : resultList
325
+ };
326
+ }
327
+
328
+ // blur 下触发校验
329
+ function handleItemBlur() {
330
+ const filterRules = innerRules.filter((item) => item.trigger === "blur");
331
+
332
+ if (filterRules.length) {
333
+ validate("blur");
334
+ }
335
+ }
336
+
337
+ function getResetValue(resetType: string): ValueType {
338
+ if (resetType === "initial") {
339
+ return getDefaultInitialData({
340
+ children,
341
+ initialData
342
+ });
343
+ }
344
+
345
+ let emptyValue: ValueType;
346
+ if (Array.isArray(formValue)) {
347
+ emptyValue = [];
348
+ } else if (isObject(formValue)) {
349
+ emptyValue = {};
350
+ } else if (isString(formValue)) {
351
+ emptyValue = "";
352
+ }
353
+
354
+ return emptyValue;
355
+ }
356
+
357
+ function resetField(type: string) {
358
+ if (typeof name === "undefined") return;
359
+
360
+ const resetType = type || resetTypeFromContext;
361
+ const resetValue = getResetValue(resetType);
362
+ // reset 不校验
363
+ updateFormValue(resetValue, false);
364
+
365
+ if (resetValidating) {
366
+ setNeedResetField(true);
367
+ } else {
368
+ resetHandler();
369
+ }
370
+ }
371
+
372
+ function resetHandler() {
373
+ setNeedResetField(false);
374
+ setErrorList([]);
375
+ setSuccessList([]);
376
+ setVerifyStatus(ValidateStatus.VALIDATING);
377
+ }
378
+
379
+ function setField(field: { value?: string; status?: ValidateStatus; validateMessage?: FormItemValidateMessage }) {
380
+ const { value, status, validateMessage } = field;
381
+ if (typeof status !== "undefined") {
382
+ setErrorList(validateMessage ? [validateMessage] : []);
383
+ setSuccessList(validateMessage ? [validateMessage] : []);
384
+ setNeedResetField(false);
385
+ setVerifyStatus(status);
386
+ }
387
+ if (typeof value !== "undefined") {
388
+ // 手动设置 status 则不需要校验 交给用户判断
389
+ updateFormValue(value, typeof status === "undefined" ? true : false, true);
390
+ }
391
+ }
392
+
393
+ function setValidateMessage(validateMessage: FormItemValidateMessage[]) {
394
+ if (!validateMessage || !Array.isArray(validateMessage)) return;
395
+ if (validateMessage.length === 0) {
396
+ setErrorList([]);
397
+ setVerifyStatus(ValidateStatus.SUCCESS);
398
+ return;
399
+ }
400
+ setErrorList(validateMessage);
401
+ const status = validateMessage?.[0]?.type || ValidateStatus.ERROR;
402
+ setVerifyStatus(status);
403
+ }
404
+
405
+ function getValidateMessage() {
406
+ return errorList;
407
+ }
408
+
409
+ useEffect(() => {
410
+ // 注册自定义更新回调
411
+ if (!shouldUpdate || !form) return;
412
+
413
+ const { getPrevStore, registerWatch } = form?.getInternalHooks?.(HOOK_MARK) || {};
414
+
415
+ const cancelRegister = registerWatch?.(() => {
416
+ const currStore = form?.getFieldsValue?.(true) || {};
417
+ let updateFlag = shouldUpdate as boolean;
418
+ if (isFunction(shouldUpdate)) updateFlag = shouldUpdate(getPrevStore?.(), currStore);
419
+
420
+ if (updateFlag) forceUpdate({});
421
+ });
422
+
423
+ return cancelRegister;
424
+ }, [shouldUpdate, form]);
425
+
426
+ useEffect(() => {
427
+ // 记录填写 name 属性 formItem
428
+ if (typeof name === "undefined") return;
429
+
430
+ // formList 下特殊处理
431
+ if (formListName && isSameForm) {
432
+ formListMapRef.current.set(name, formItemRef);
433
+ return () => {
434
+ // eslint-disable-next-line react-hooks/exhaustive-deps
435
+ formListMapRef.current.delete(name);
436
+ unset(form?.store, name);
437
+ };
438
+ }
439
+ if (!formMapRef) return;
440
+ formMapRef.current.set(name, formItemRef);
441
+ return () => {
442
+ // eslint-disable-next-line react-hooks/exhaustive-deps
443
+ formMapRef.current.delete(name);
444
+ unset(form?.store, name);
445
+ };
446
+ // eslint-disable-next-line react-hooks/exhaustive-deps
447
+ }, [snakeName, formListName]);
448
+
449
+ useEffect(() => {
450
+ // value 变化通知 watch 事件
451
+ form?.getInternalHooks?.(HOOK_MARK)?.notifyWatch?.(name);
452
+
453
+ // 控制是否需要校验
454
+ if (!shouldValidate.current) return;
455
+
456
+ // value change event
457
+ if (typeof name !== "undefined" && shouldEmitChangeRef.current) {
458
+ if (formListName && isSameForm) {
459
+ // 整理 formItem 的值
460
+ const formListValue = merge([], calcFieldValue(name, formValue));
461
+ // 整理 formList 的值
462
+ const fieldValue = calcFieldValue(formListName, formListValue);
463
+ onFormItemValueChange?.({ ...fieldValue });
464
+ } else {
465
+ const fieldValue = calcFieldValue(name, formValue);
466
+ onFormItemValueChange?.({ ...fieldValue });
467
+ }
468
+ }
469
+
470
+ const filterRules = innerRules.filter((item) => (item.trigger || "change") === "change");
471
+
472
+ if (filterRules.length) {
473
+ validate("change");
474
+ }
475
+ // eslint-disable-next-line react-hooks/exhaustive-deps
476
+ }, [formValue, snakeName]);
477
+
478
+ // 暴露 ref 实例方法
479
+ const instance: FormItemInstance = {
480
+ name,
481
+ value: formValue,
482
+ isUpdated: isUpdatedRef.current,
483
+ getValue: () => valueRef.current,
484
+ setValue: (newVal: ValueType) => updateFormValue(newVal, true, true),
485
+ setField,
486
+ validate,
487
+ validateOnly,
488
+ resetField,
489
+ setValidateMessage,
490
+ getValidateMessage,
491
+ resetValidate: resetHandler
492
+ };
493
+ useImperativeHandle(ref, (): FormItemInstance => instance);
494
+ useImperativeHandle(formItemRef, (): FormItemInstance => instance);
495
+
496
+ // 传入 form 实例支持自定义渲染
497
+ if (isFunction(children)) return children(form);
498
+
499
+ return (
500
+ <div className={formItemClass} style={style}>
501
+ {label && (
502
+ <div className={formItemLabelClass} style={labelStyle}>
503
+ <label htmlFor={props?.for}>{label}</label>
504
+ {colon || ":"}
505
+ </div>
506
+ )}
507
+ <div className={contentClass()} style={contentStyle}>
508
+ <div className={`${classPrefix}-form__controls-content`}>
509
+ {React.Children.map(children, (child, index) => {
510
+ if (!child) return null;
511
+
512
+ let ctrlKey = "value";
513
+ if (React.isValidElement(child)) {
514
+ if (child.type === FormItem) {
515
+ return React.cloneElement(child, {
516
+ ref: (el) => {
517
+ if (!el) return;
518
+ innerFormItemsRef.current[index] = el;
519
+ }
520
+ });
521
+ }
522
+ if (typeof child.type === "object") {
523
+ ctrlKey = ctrlKeyMap.get(child.type) || "value";
524
+ }
525
+ const childProps = child.props as TdFormItemProps & React.HTMLAttributes<HTMLDivElement>;
526
+ return React.cloneElement(child, {
527
+ disabled: disabledFromContext,
528
+ ...childProps,
529
+ [ctrlKey]: formValue,
530
+ onChange: (value: ValueType, ...args: unknown[]) => {
531
+ const newValue = valueFormat ? valueFormat(value) : value;
532
+ updateFormValue(newValue, true, true);
533
+ childProps?.onChange?.call?.(null, value, ...args);
534
+ },
535
+ onBlur: (value: ValueType, ...args: unknown[]) => {
536
+ handleItemBlur();
537
+ childProps?.onBlur?.call?.(null, value, ...args);
538
+ }
539
+ });
540
+ }
541
+ return child;
542
+ })}
543
+ {renderSuffixIcon()}
544
+ </div>
545
+ {helpNode}
546
+ {extraNode}
547
+ </div>
548
+ </div>
549
+ );
550
+ });
551
+
552
+ FormItem.displayName = "FormItem";
553
+
554
+ export default FormItem;