import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2"; import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties"; import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray"; import _createClass from "@babel/runtime/helpers/esm/createClass"; import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck"; var _excluded = ["name", "errors"]; import * as React from 'react'; import warning from "rc-util/es/warning"; import { HOOK_MARK } from './FieldContext'; import { allPromiseFinish } from './utils/asyncUtil'; import NameMap from './utils/NameMap'; import { defaultValidateMessages } from './utils/messages'; import { cloneByNamePathList, containsNamePath, getNamePath, getValue, matchNamePath, setValue, setValues } from './utils/valueUtil'; import cloneDeep from './utils/cloneDeep'; export var FormStore = /*#__PURE__*/_createClass(function FormStore(forceRootUpdate) { var _this = this; _classCallCheck(this, FormStore); this.formHooked = false; this.forceRootUpdate = void 0; this.subscribable = true; this.store = {}; this.fieldEntities = []; this.initialValues = {}; this.callbacks = {}; this.validateMessages = null; this.preserve = null; this.lastValidatePromise = null; this.getForm = function () { return { getFieldValue: _this.getFieldValue, getFieldsValue: _this.getFieldsValue, getFieldError: _this.getFieldError, getFieldWarning: _this.getFieldWarning, getFieldsError: _this.getFieldsError, isFieldsTouched: _this.isFieldsTouched, isFieldTouched: _this.isFieldTouched, isFieldValidating: _this.isFieldValidating, isFieldsValidating: _this.isFieldsValidating, resetFields: _this.resetFields, setFields: _this.setFields, setFieldsValue: _this.setFieldsValue, validateFields: _this.validateFields, submit: _this.submit, getInternalHooks: _this.getInternalHooks }; }; this.getInternalHooks = function (key) { if (key === HOOK_MARK) { _this.formHooked = true; return { dispatch: _this.dispatch, initEntityValue: _this.initEntityValue, registerField: _this.registerField, useSubscribe: _this.useSubscribe, setInitialValues: _this.setInitialValues, setCallbacks: _this.setCallbacks, setValidateMessages: _this.setValidateMessages, getFields: _this.getFields, setPreserve: _this.setPreserve, getInitialValue: _this.getInitialValue }; } warning(false, '`getInternalHooks` is internal usage. Should not call directly.'); return null; }; this.useSubscribe = function (subscribable) { _this.subscribable = subscribable; }; this.setInitialValues = function (initialValues, init) { _this.initialValues = initialValues || {}; if (init) { _this.store = setValues({}, initialValues, _this.store); } }; this.getInitialValue = function (namePath) { return cloneDeep(getValue(_this.initialValues, namePath)); }; this.setCallbacks = function (callbacks) { _this.callbacks = callbacks; }; this.setValidateMessages = function (validateMessages) { _this.validateMessages = validateMessages; }; this.setPreserve = function (preserve) { _this.preserve = preserve; }; this.timeoutId = null; this.warningUnhooked = function () { if (process.env.NODE_ENV !== 'production' && !_this.timeoutId && typeof window !== 'undefined') { _this.timeoutId = setTimeout(function () { _this.timeoutId = null; if (!_this.formHooked) { warning(false, 'Instance created by `useForm` is not connected to any Form element. Forget to pass `form` prop?'); } }); } }; this.getFieldEntities = function () { var pure = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; if (!pure) { return _this.fieldEntities; } return _this.fieldEntities.filter(function (field) { return field.getNamePath().length; }); }; this.getFieldsMap = function () { var pure = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; var cache = new NameMap(); _this.getFieldEntities(pure).forEach(function (field) { var namePath = field.getNamePath(); cache.set(namePath, field); }); return cache; }; this.getFieldEntitiesForNamePathList = function (nameList) { if (!nameList) { return _this.getFieldEntities(true); } var cache = _this.getFieldsMap(true); return nameList.map(function (name) { var namePath = getNamePath(name); return cache.get(namePath) || { INVALIDATE_NAME_PATH: getNamePath(name) }; }); }; this.getFieldsValue = function (nameList, filterFunc) { _this.warningUnhooked(); if (nameList === true && !filterFunc) { return _this.store; } var fieldEntities = _this.getFieldEntitiesForNamePathList(Array.isArray(nameList) ? nameList : null); var filteredNameList = []; fieldEntities.forEach(function (entity) { var _entity$isListField; var namePath = 'INVALIDATE_NAME_PATH' in entity ? entity.INVALIDATE_NAME_PATH : entity.getNamePath(); // Ignore when it's a list item and not specific the namePath, // since parent field is already take in count if (!nameList && ((_entity$isListField = entity.isListField) === null || _entity$isListField === void 0 ? void 0 : _entity$isListField.call(entity))) { return; } if (!filterFunc) { filteredNameList.push(namePath); } else { var meta = 'getMeta' in entity ? entity.getMeta() : null; if (filterFunc(meta)) { filteredNameList.push(namePath); } } }); return cloneByNamePathList(_this.store, filteredNameList.map(getNamePath)); }; this.getFieldValue = function (name) { _this.warningUnhooked(); var namePath = getNamePath(name); return getValue(_this.store, namePath); }; this.getFieldsError = function (nameList) { _this.warningUnhooked(); var fieldEntities = _this.getFieldEntitiesForNamePathList(nameList); return fieldEntities.map(function (entity, index) { if (entity && !('INVALIDATE_NAME_PATH' in entity)) { return { name: entity.getNamePath(), errors: entity.getErrors(), warnings: entity.getWarnings() }; } return { name: getNamePath(nameList[index]), errors: [], warnings: [] }; }); }; this.getFieldError = function (name) { _this.warningUnhooked(); var namePath = getNamePath(name); var fieldError = _this.getFieldsError([namePath])[0]; return fieldError.errors; }; this.getFieldWarning = function (name) { _this.warningUnhooked(); var namePath = getNamePath(name); var fieldError = _this.getFieldsError([namePath])[0]; return fieldError.warnings; }; this.isFieldsTouched = function () { _this.warningUnhooked(); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } var arg0 = args[0], arg1 = args[1]; var namePathList; var isAllFieldsTouched = false; if (args.length === 0) { namePathList = null; } else if (args.length === 1) { if (Array.isArray(arg0)) { namePathList = arg0.map(getNamePath); isAllFieldsTouched = false; } else { namePathList = null; isAllFieldsTouched = arg0; } } else { namePathList = arg0.map(getNamePath); isAllFieldsTouched = arg1; } var fieldEntities = _this.getFieldEntities(true); var isFieldTouched = function isFieldTouched(field) { return field.isFieldTouched(); }; // ===== Will get fully compare when not config namePathList ===== if (!namePathList) { return isAllFieldsTouched ? fieldEntities.every(isFieldTouched) : fieldEntities.some(isFieldTouched); } // Generate a nest tree for validate var map = new NameMap(); namePathList.forEach(function (shortNamePath) { map.set(shortNamePath, []); }); fieldEntities.forEach(function (field) { var fieldNamePath = field.getNamePath(); // Find matched entity and put into list namePathList.forEach(function (shortNamePath) { if (shortNamePath.every(function (nameUnit, i) { return fieldNamePath[i] === nameUnit; })) { map.update(shortNamePath, function (list) { return [].concat(_toConsumableArray(list), [field]); }); } }); }); // Check if NameMap value is touched var isNamePathListTouched = function isNamePathListTouched(entities) { return entities.some(isFieldTouched); }; var namePathListEntities = map.map(function (_ref) { var value = _ref.value; return value; }); return isAllFieldsTouched ? namePathListEntities.every(isNamePathListTouched) : namePathListEntities.some(isNamePathListTouched); }; this.isFieldTouched = function (name) { _this.warningUnhooked(); return _this.isFieldsTouched([name]); }; this.isFieldsValidating = function (nameList) { _this.warningUnhooked(); var fieldEntities = _this.getFieldEntities(); if (!nameList) { return fieldEntities.some(function (testField) { return testField.isFieldValidating(); }); } var namePathList = nameList.map(getNamePath); return fieldEntities.some(function (testField) { var fieldNamePath = testField.getNamePath(); return containsNamePath(namePathList, fieldNamePath) && testField.isFieldValidating(); }); }; this.isFieldValidating = function (name) { _this.warningUnhooked(); return _this.isFieldsValidating([name]); }; this.resetWithFieldInitialValue = function () { var info = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; // Create cache var cache = new NameMap(); var fieldEntities = _this.getFieldEntities(true); fieldEntities.forEach(function (field) { var initialValue = field.props.initialValue; var namePath = field.getNamePath(); // Record only if has `initialValue` if (initialValue !== undefined) { var records = cache.get(namePath) || new Set(); records.add({ entity: field, value: initialValue }); cache.set(namePath, records); } }); // Reset var resetWithFields = function resetWithFields(entities) { entities.forEach(function (field) { var initialValue = field.props.initialValue; if (initialValue !== undefined) { var namePath = field.getNamePath(); var formInitialValue = _this.getInitialValue(namePath); if (formInitialValue !== undefined) { // Warning if conflict with form initialValues and do not modify value warning(false, "Form already set 'initialValues' with path '".concat(namePath.join('.'), "'. Field can not overwrite it.")); } else { var records = cache.get(namePath); if (records && records.size > 1) { // Warning if multiple field set `initialValue`and do not modify value warning(false, "Multiple Field with path '".concat(namePath.join('.'), "' set 'initialValue'. Can not decide which one to pick.")); } else if (records) { var originValue = _this.getFieldValue(namePath); // Set `initialValue` if (!info.skipExist || originValue === undefined) { _this.store = setValue(_this.store, namePath, _toConsumableArray(records)[0].value); } } } } }); }; var requiredFieldEntities; if (info.entities) { requiredFieldEntities = info.entities; } else if (info.namePathList) { requiredFieldEntities = []; info.namePathList.forEach(function (namePath) { var records = cache.get(namePath); if (records) { var _requiredFieldEntitie; (_requiredFieldEntitie = requiredFieldEntities).push.apply(_requiredFieldEntitie, _toConsumableArray(_toConsumableArray(records).map(function (r) { return r.entity; }))); } }); } else { requiredFieldEntities = fieldEntities; } resetWithFields(requiredFieldEntities); }; this.resetFields = function (nameList) { _this.warningUnhooked(); var prevStore = _this.store; if (!nameList) { _this.store = setValues({}, _this.initialValues); _this.resetWithFieldInitialValue(); _this.notifyObservers(prevStore, null, { type: 'reset' }); return; } // Reset by `nameList` var namePathList = nameList.map(getNamePath); namePathList.forEach(function (namePath) { var initialValue = _this.getInitialValue(namePath); _this.store = setValue(_this.store, namePath, initialValue); }); _this.resetWithFieldInitialValue({ namePathList: namePathList }); _this.notifyObservers(prevStore, namePathList, { type: 'reset' }); }; this.setFields = function (fields) { _this.warningUnhooked(); var prevStore = _this.store; fields.forEach(function (fieldData) { var name = fieldData.name, errors = fieldData.errors, data = _objectWithoutProperties(fieldData, _excluded); var namePath = getNamePath(name); // Value if ('value' in data) { _this.store = setValue(_this.store, namePath, data.value); } _this.notifyObservers(prevStore, [namePath], { type: 'setField', data: fieldData }); }); }; this.getFields = function () { var entities = _this.getFieldEntities(true); var fields = entities.map(function (field) { var namePath = field.getNamePath(); var meta = field.getMeta(); var fieldData = _objectSpread(_objectSpread({}, meta), {}, { name: namePath, value: _this.getFieldValue(namePath) }); Object.defineProperty(fieldData, 'originRCField', { value: true }); return fieldData; }); return fields; }; this.initEntityValue = function (entity) { var initialValue = entity.props.initialValue; if (initialValue !== undefined) { var namePath = entity.getNamePath(); var prevValue = getValue(_this.store, namePath); if (prevValue === undefined) { _this.store = setValue(_this.store, namePath, initialValue); } } }; this.registerField = function (entity) { _this.fieldEntities.push(entity); // Set initial values if (entity.props.initialValue !== undefined) { var prevStore = _this.store; _this.resetWithFieldInitialValue({ entities: [entity], skipExist: true }); _this.notifyObservers(prevStore, [entity.getNamePath()], { type: 'valueUpdate', source: 'internal' }); } // un-register field callback return function (isListField, preserve) { var subNamePath = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : []; _this.fieldEntities = _this.fieldEntities.filter(function (item) { return item !== entity; }); // Clean up store value if not preserve var mergedPreserve = preserve !== undefined ? preserve : _this.preserve; if (mergedPreserve === false && (!isListField || subNamePath.length > 1)) { var namePath = entity.getNamePath(); var defaultValue = isListField ? undefined : _this.getInitialValue(namePath); if (namePath.length && _this.getFieldValue(namePath) !== defaultValue && _this.fieldEntities.every(function (field) { return (// Only reset when no namePath exist !matchNamePath(field.getNamePath(), namePath) ); })) { var _prevStore = _this.store; _this.store = setValue(_prevStore, namePath, defaultValue, true); // Notify that field is unmount _this.notifyObservers(_prevStore, [namePath], { type: 'remove' }); // Dependencies update _this.triggerDependenciesUpdate(_prevStore, namePath); } } }; }; this.dispatch = function (action) { switch (action.type) { case 'updateValue': { var namePath = action.namePath, value = action.value; _this.updateValue(namePath, value); break; } case 'validateField': { var _namePath = action.namePath, triggerName = action.triggerName; _this.validateFields([_namePath], { triggerName: triggerName }); break; } default: // Currently we don't have other action. Do nothing. } }; this.notifyObservers = function (prevStore, namePathList, info) { if (_this.subscribable) { var mergedInfo = _objectSpread(_objectSpread({}, info), {}, { store: _this.getFieldsValue(true) }); _this.getFieldEntities().forEach(function (_ref2) { var onStoreChange = _ref2.onStoreChange; onStoreChange(prevStore, namePathList, mergedInfo); }); } else { _this.forceRootUpdate(); } }; this.triggerDependenciesUpdate = function (prevStore, namePath) { var childrenFields = _this.getDependencyChildrenFields(namePath); if (childrenFields.length) { _this.validateFields(childrenFields); } _this.notifyObservers(prevStore, childrenFields, { type: 'dependenciesUpdate', relatedFields: [namePath].concat(_toConsumableArray(childrenFields)) }); return childrenFields; }; this.updateValue = function (name, value) { var namePath = getNamePath(name); var prevStore = _this.store; _this.store = setValue(_this.store, namePath, value); _this.notifyObservers(prevStore, [namePath], { type: 'valueUpdate', source: 'internal' }); // Dependencies update var childrenFields = _this.triggerDependenciesUpdate(prevStore, namePath); // trigger callback function var onValuesChange = _this.callbacks.onValuesChange; if (onValuesChange) { var changedValues = cloneByNamePathList(_this.store, [namePath]); onValuesChange(changedValues, _this.getFieldsValue()); } _this.triggerOnFieldsChange([namePath].concat(_toConsumableArray(childrenFields))); }; this.setFieldsValue = function (store) { _this.warningUnhooked(); var prevStore = _this.store; if (store) { _this.store = setValues(_this.store, store); } _this.notifyObservers(prevStore, null, { type: 'valueUpdate', source: 'external' }); }; this.getDependencyChildrenFields = function (rootNamePath) { var children = new Set(); var childrenFields = []; var dependencies2fields = new NameMap(); /** * Generate maps * Can use cache to save perf if user report performance issue with this */ _this.getFieldEntities().forEach(function (field) { var dependencies = field.props.dependencies; (dependencies || []).forEach(function (dependency) { var dependencyNamePath = getNamePath(dependency); dependencies2fields.update(dependencyNamePath, function () { var fields = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Set(); fields.add(field); return fields; }); }); }); var fillChildren = function fillChildren(namePath) { var fields = dependencies2fields.get(namePath) || new Set(); fields.forEach(function (field) { if (!children.has(field)) { children.add(field); var fieldNamePath = field.getNamePath(); if (field.isFieldDirty() && fieldNamePath.length) { childrenFields.push(fieldNamePath); fillChildren(fieldNamePath); } } }); }; fillChildren(rootNamePath); return childrenFields; }; this.triggerOnFieldsChange = function (namePathList, filedErrors) { var onFieldsChange = _this.callbacks.onFieldsChange; if (onFieldsChange) { var fields = _this.getFields(); /** * Fill errors since `fields` may be replaced by controlled fields */ if (filedErrors) { var cache = new NameMap(); filedErrors.forEach(function (_ref3) { var name = _ref3.name, errors = _ref3.errors; cache.set(name, errors); }); fields.forEach(function (field) { // eslint-disable-next-line no-param-reassign field.errors = cache.get(field.name) || field.errors; }); } var changedFields = fields.filter(function (_ref4) { var fieldName = _ref4.name; return containsNamePath(namePathList, fieldName); }); onFieldsChange(changedFields, fields); } }; this.validateFields = function (nameList, options) { _this.warningUnhooked(); var provideNameList = !!nameList; var namePathList = provideNameList ? nameList.map(getNamePath) : []; // Collect result in promise list var promiseList = []; _this.getFieldEntities(true).forEach(function (field) { // Add field if not provide `nameList` if (!provideNameList) { namePathList.push(field.getNamePath()); } /** * Recursive validate if configured. * TODO: perf improvement @zombieJ */ if ((options === null || options === void 0 ? void 0 : options.recursive) && provideNameList) { var namePath = field.getNamePath(); if ( // nameList[i] === undefined 说明是以 nameList 开头的 // ['name'] -> ['name','list'] namePath.every(function (nameUnit, i) { return nameList[i] === nameUnit || nameList[i] === undefined; })) { namePathList.push(namePath); } } // Skip if without rule if (!field.props.rules || !field.props.rules.length) { return; } var fieldNamePath = field.getNamePath(); // Add field validate rule in to promise list if (!provideNameList || containsNamePath(namePathList, fieldNamePath)) { var promise = field.validateRules(_objectSpread({ validateMessages: _objectSpread(_objectSpread({}, defaultValidateMessages), _this.validateMessages) }, options)); // Wrap promise with field promiseList.push(promise.then(function () { return { name: fieldNamePath, errors: [], warnings: [] }; }).catch(function (ruleErrors) { var mergedErrors = []; var mergedWarnings = []; ruleErrors.forEach(function (_ref5) { var warningOnly = _ref5.rule.warningOnly, errors = _ref5.errors; if (warningOnly) { mergedWarnings.push.apply(mergedWarnings, _toConsumableArray(errors)); } else { mergedErrors.push.apply(mergedErrors, _toConsumableArray(errors)); } }); if (mergedErrors.length) { return Promise.reject({ name: fieldNamePath, errors: mergedErrors, warnings: mergedWarnings }); } return { name: fieldNamePath, errors: mergedErrors, warnings: mergedWarnings }; })); } }); var summaryPromise = allPromiseFinish(promiseList); _this.lastValidatePromise = summaryPromise; // Notify fields with rule that validate has finished and need update summaryPromise.catch(function (results) { return results; }).then(function (results) { var resultNamePathList = results.map(function (_ref6) { var name = _ref6.name; return name; }); _this.notifyObservers(_this.store, resultNamePathList, { type: 'validateFinish' }); _this.triggerOnFieldsChange(resultNamePathList, results); }); var returnPromise = summaryPromise.then(function () { if (_this.lastValidatePromise === summaryPromise) { return Promise.resolve(_this.getFieldsValue(namePathList)); } return Promise.reject([]); }).catch(function (results) { var errorList = results.filter(function (result) { return result && result.errors.length; }); return Promise.reject({ values: _this.getFieldsValue(namePathList), errorFields: errorList, outOfDate: _this.lastValidatePromise !== summaryPromise }); }); // Do not throw in console returnPromise.catch(function (e) { return e; }); return returnPromise; }; this.submit = function () { _this.warningUnhooked(); _this.validateFields().then(function (values) { var onFinish = _this.callbacks.onFinish; if (onFinish) { try { onFinish(values); } catch (err) { // Should print error if user `onFinish` callback failed console.error(err); } } }).catch(function (e) { var onFinishFailed = _this.callbacks.onFinishFailed; if (onFinishFailed) { onFinishFailed(e); } }); }; this.forceRootUpdate = forceRootUpdate; }); function useForm(form) { var formRef = React.useRef(); var _React$useState = React.useState({}), _React$useState2 = _slicedToArray(_React$useState, 2), forceUpdate = _React$useState2[1]; if (!formRef.current) { if (form) { formRef.current = form; } else { // Create a new FormStore if not provided var forceReRender = function forceReRender() { forceUpdate({}); }; var formStore = new FormStore(forceReRender); formRef.current = formStore.getForm(); } } return [formRef.current]; } export default useForm;