index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. "use client";
  2. import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
  3. import * as React from 'react';
  4. import classNames from 'classnames';
  5. import { Field, FieldContext, ListContext } from 'rc-field-form';
  6. import useState from "rc-util/es/hooks/useState";
  7. import { supportRef } from "rc-util/es/ref";
  8. import { cloneElement } from '../../_util/reactNode';
  9. import { devUseWarning } from '../../_util/warning';
  10. import { ConfigContext } from '../../config-provider';
  11. import useCSSVarCls from '../../config-provider/hooks/useCSSVarCls';
  12. import { FormContext, NoStyleItemContext } from '../context';
  13. import useChildren from '../hooks/useChildren';
  14. import useFormItemStatus from '../hooks/useFormItemStatus';
  15. import useFrameState from '../hooks/useFrameState';
  16. import useItemRef from '../hooks/useItemRef';
  17. import useStyle from '../style';
  18. import { getFieldId, toArray } from '../util';
  19. import ItemHolder from './ItemHolder';
  20. import StatusProvider from './StatusProvider';
  21. const NAME_SPLIT = '__SPLIT__';
  22. const _ValidateStatuses = ['success', 'warning', 'error', 'validating', ''];
  23. // https://github.com/ant-design/ant-design/issues/46417
  24. // `getValueProps` may modify the value props name,
  25. // we should check if the control is similar.
  26. function isSimilarControl(a, b) {
  27. const keysA = Object.keys(a);
  28. const keysB = Object.keys(b);
  29. return keysA.length === keysB.length && keysA.every(key => {
  30. const propValueA = a[key];
  31. const propValueB = b[key];
  32. return propValueA === propValueB || typeof propValueA === 'function' || typeof propValueB === 'function';
  33. });
  34. }
  35. const MemoInput = /*#__PURE__*/React.memo(({
  36. children
  37. }) => children, (prev, next) => isSimilarControl(prev.control, next.control) && prev.update === next.update && prev.childProps.length === next.childProps.length && prev.childProps.every((value, index) => value === next.childProps[index]));
  38. function genEmptyMeta() {
  39. return {
  40. errors: [],
  41. warnings: [],
  42. touched: false,
  43. validating: false,
  44. name: [],
  45. validated: false
  46. };
  47. }
  48. function InternalFormItem(props) {
  49. const {
  50. name,
  51. noStyle,
  52. className,
  53. dependencies,
  54. prefixCls: customizePrefixCls,
  55. shouldUpdate,
  56. rules,
  57. children,
  58. required,
  59. label,
  60. messageVariables,
  61. trigger = 'onChange',
  62. validateTrigger,
  63. hidden,
  64. help,
  65. layout
  66. } = props;
  67. const {
  68. getPrefixCls
  69. } = React.useContext(ConfigContext);
  70. const {
  71. name: formName
  72. } = React.useContext(FormContext);
  73. const mergedChildren = useChildren(children);
  74. const isRenderProps = typeof mergedChildren === 'function';
  75. const notifyParentMetaChange = React.useContext(NoStyleItemContext);
  76. const {
  77. validateTrigger: contextValidateTrigger
  78. } = React.useContext(FieldContext);
  79. const mergedValidateTrigger = validateTrigger !== undefined ? validateTrigger : contextValidateTrigger;
  80. const hasName = !(name === undefined || name === null);
  81. const prefixCls = getPrefixCls('form', customizePrefixCls);
  82. // Style
  83. const rootCls = useCSSVarCls(prefixCls);
  84. const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
  85. // ========================= Warn =========================
  86. const warning = devUseWarning('Form.Item');
  87. if (process.env.NODE_ENV !== 'production') {
  88. process.env.NODE_ENV !== "production" ? warning(name !== null, 'usage', '`null` is passed as `name` property') : void 0;
  89. }
  90. // ========================= MISC =========================
  91. // Get `noStyle` required info
  92. const listContext = React.useContext(ListContext);
  93. const fieldKeyPathRef = React.useRef(null);
  94. // ======================== Errors ========================
  95. // >>>>> Collect sub field errors
  96. const [subFieldErrors, setSubFieldErrors] = useFrameState({});
  97. // >>>>> Current field errors
  98. const [meta, setMeta] = useState(() => genEmptyMeta());
  99. const onMetaChange = nextMeta => {
  100. // This keyInfo is not correct when field is removed
  101. // Since origin keyManager no longer keep the origin key anymore
  102. // Which means we need cache origin one and reuse when removed
  103. const keyInfo = listContext === null || listContext === void 0 ? void 0 : listContext.getKey(nextMeta.name);
  104. // Destroy will reset all the meta
  105. setMeta(nextMeta.destroy ? genEmptyMeta() : nextMeta, true);
  106. // Bump to parent since noStyle
  107. if (noStyle && help !== false && notifyParentMetaChange) {
  108. let namePath = nextMeta.name;
  109. if (!nextMeta.destroy) {
  110. if (keyInfo !== undefined) {
  111. const [fieldKey, restPath] = keyInfo;
  112. namePath = [fieldKey].concat(_toConsumableArray(restPath));
  113. fieldKeyPathRef.current = namePath;
  114. }
  115. } else {
  116. // Use origin cache data
  117. namePath = fieldKeyPathRef.current || namePath;
  118. }
  119. notifyParentMetaChange(nextMeta, namePath);
  120. }
  121. };
  122. // >>>>> Collect noStyle Field error to the top FormItem
  123. const onSubItemMetaChange = (subMeta, uniqueKeys) => {
  124. // Only `noStyle` sub item will trigger
  125. setSubFieldErrors(prevSubFieldErrors => {
  126. const clone = Object.assign({}, prevSubFieldErrors);
  127. // name: ['user', 1] + key: [4] = ['user', 4]
  128. const mergedNamePath = [].concat(_toConsumableArray(subMeta.name.slice(0, -1)), _toConsumableArray(uniqueKeys));
  129. const mergedNameKey = mergedNamePath.join(NAME_SPLIT);
  130. if (subMeta.destroy) {
  131. // Remove
  132. delete clone[mergedNameKey];
  133. } else {
  134. // Update
  135. clone[mergedNameKey] = subMeta;
  136. }
  137. return clone;
  138. });
  139. };
  140. // >>>>> Get merged errors
  141. const [mergedErrors, mergedWarnings] = React.useMemo(() => {
  142. const errorList = _toConsumableArray(meta.errors);
  143. const warningList = _toConsumableArray(meta.warnings);
  144. Object.values(subFieldErrors).forEach(subFieldError => {
  145. errorList.push.apply(errorList, _toConsumableArray(subFieldError.errors || []));
  146. warningList.push.apply(warningList, _toConsumableArray(subFieldError.warnings || []));
  147. });
  148. return [errorList, warningList];
  149. }, [subFieldErrors, meta.errors, meta.warnings]);
  150. // ===================== Children Ref =====================
  151. const getItemRef = useItemRef();
  152. // ======================== Render ========================
  153. function renderLayout(baseChildren, fieldId, isRequired) {
  154. if (noStyle && !hidden) {
  155. return /*#__PURE__*/React.createElement(StatusProvider, {
  156. prefixCls: prefixCls,
  157. hasFeedback: props.hasFeedback,
  158. validateStatus: props.validateStatus,
  159. meta: meta,
  160. errors: mergedErrors,
  161. warnings: mergedWarnings,
  162. noStyle: true,
  163. name: name
  164. }, baseChildren);
  165. }
  166. return /*#__PURE__*/React.createElement(ItemHolder, Object.assign({
  167. key: "row"
  168. }, props, {
  169. className: classNames(className, cssVarCls, rootCls, hashId),
  170. prefixCls: prefixCls,
  171. fieldId: fieldId,
  172. isRequired: isRequired,
  173. errors: mergedErrors,
  174. warnings: mergedWarnings,
  175. meta: meta,
  176. onSubItemMetaChange: onSubItemMetaChange,
  177. layout: layout,
  178. name: name
  179. }), baseChildren);
  180. }
  181. if (!hasName && !isRenderProps && !dependencies) {
  182. return wrapCSSVar(renderLayout(mergedChildren));
  183. }
  184. let variables = {};
  185. if (typeof label === 'string') {
  186. variables.label = label;
  187. } else if (name) {
  188. variables.label = String(name);
  189. }
  190. if (messageVariables) {
  191. variables = Object.assign(Object.assign({}, variables), messageVariables);
  192. }
  193. // >>>>> With Field
  194. return wrapCSSVar(/*#__PURE__*/React.createElement(Field, Object.assign({}, props, {
  195. messageVariables: variables,
  196. trigger: trigger,
  197. validateTrigger: mergedValidateTrigger,
  198. onMetaChange: onMetaChange
  199. }), (control, renderMeta, context) => {
  200. const mergedName = toArray(name).length && renderMeta ? renderMeta.name : [];
  201. const fieldId = getFieldId(mergedName, formName);
  202. const isRequired = required !== undefined ? required : !!(rules === null || rules === void 0 ? void 0 : rules.some(rule => {
  203. if (rule && typeof rule === 'object' && rule.required && !rule.warningOnly) {
  204. return true;
  205. }
  206. if (typeof rule === 'function') {
  207. const ruleEntity = rule(context);
  208. return (ruleEntity === null || ruleEntity === void 0 ? void 0 : ruleEntity.required) && !(ruleEntity === null || ruleEntity === void 0 ? void 0 : ruleEntity.warningOnly);
  209. }
  210. return false;
  211. }));
  212. // ======================= Children =======================
  213. const mergedControl = Object.assign({}, control);
  214. let childNode = null;
  215. process.env.NODE_ENV !== "production" ? warning(!(shouldUpdate && dependencies), 'usage', "`shouldUpdate` and `dependencies` shouldn't be used together. See https://u.ant.design/form-deps.") : void 0;
  216. if (Array.isArray(mergedChildren) && hasName) {
  217. process.env.NODE_ENV !== "production" ? warning(false, 'usage', 'A `Form.Item` with a `name` prop must have a single child element. For information on how to render more complex form items, see https://u.ant.design/complex-form-item.') : void 0;
  218. childNode = mergedChildren;
  219. } else if (isRenderProps && (!(shouldUpdate || dependencies) || hasName)) {
  220. process.env.NODE_ENV !== "production" ? warning(!!(shouldUpdate || dependencies), 'usage', 'A `Form.Item` with a render function must have either `shouldUpdate` or `dependencies`.') : void 0;
  221. process.env.NODE_ENV !== "production" ? warning(!hasName, 'usage', 'A `Form.Item` with a render function cannot be a field, and thus cannot have a `name` prop.') : void 0;
  222. } else if (dependencies && !isRenderProps && !hasName) {
  223. process.env.NODE_ENV !== "production" ? warning(false, 'usage', 'Must set `name` or use a render function when `dependencies` is set.') : void 0;
  224. } else if (/*#__PURE__*/React.isValidElement(mergedChildren)) {
  225. process.env.NODE_ENV !== "production" ? warning(mergedChildren.props.defaultValue === undefined, 'usage', '`defaultValue` will not work on controlled Field. You should use `initialValues` of Form instead.') : void 0;
  226. const childProps = Object.assign(Object.assign({}, mergedChildren.props), mergedControl);
  227. if (!childProps.id) {
  228. childProps.id = fieldId;
  229. }
  230. if (help || mergedErrors.length > 0 || mergedWarnings.length > 0 || props.extra) {
  231. const describedbyArr = [];
  232. if (help || mergedErrors.length > 0) {
  233. describedbyArr.push(`${fieldId}_help`);
  234. }
  235. if (props.extra) {
  236. describedbyArr.push(`${fieldId}_extra`);
  237. }
  238. childProps['aria-describedby'] = describedbyArr.join(' ');
  239. }
  240. if (mergedErrors.length > 0) {
  241. childProps['aria-invalid'] = 'true';
  242. }
  243. if (isRequired) {
  244. childProps['aria-required'] = 'true';
  245. }
  246. if (supportRef(mergedChildren)) {
  247. childProps.ref = getItemRef(mergedName, mergedChildren);
  248. }
  249. // We should keep user origin event handler
  250. const triggers = new Set([].concat(_toConsumableArray(toArray(trigger)), _toConsumableArray(toArray(mergedValidateTrigger))));
  251. triggers.forEach(eventName => {
  252. childProps[eventName] = (...args) => {
  253. var _a2, _c2;
  254. var _a, _b, _c;
  255. (_a = mergedControl[eventName]) === null || _a === void 0 ? void 0 : (_a2 = _a).call.apply(_a2, [mergedControl].concat(args));
  256. (_c = (_b = mergedChildren.props)[eventName]) === null || _c === void 0 ? void 0 : (_c2 = _c).call.apply(_c2, [_b].concat(args));
  257. };
  258. });
  259. // List of props that need to be watched for changes -> if changes are detected in MemoInput -> rerender
  260. const watchingChildProps = [childProps['aria-required'], childProps['aria-invalid'], childProps['aria-describedby']];
  261. childNode = /*#__PURE__*/React.createElement(MemoInput, {
  262. control: mergedControl,
  263. update: mergedChildren,
  264. childProps: watchingChildProps
  265. }, cloneElement(mergedChildren, childProps));
  266. } else if (isRenderProps && (shouldUpdate || dependencies) && !hasName) {
  267. childNode = mergedChildren(context);
  268. } else {
  269. process.env.NODE_ENV !== "production" ? warning(!mergedName.length || !!noStyle, 'usage', '`name` is only used for validate React element. If you are using Form.Item as layout display, please remove `name` instead.') : void 0;
  270. childNode = mergedChildren;
  271. }
  272. return renderLayout(childNode, fieldId, isRequired);
  273. }));
  274. }
  275. const FormItem = InternalFormItem;
  276. FormItem.useStatus = useFormItemStatus;
  277. export default FormItem;