You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

790 lines
23 KiB

import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
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, setValue, setValues } from './utils/valueUtil';
export var FormStore = function FormStore(forceRootUpdate) {
var _this = this;
_classCallCheck(this, FormStore);
this.formHooked = false;
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,
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
};
}; // ======================== Internal Hooks ========================
this.getInternalHooks = function (key) {
if (key === HOOK_MARK) {
_this.formHooked = true;
return {
dispatch: _this.dispatch,
registerField: _this.registerField,
useSubscribe: _this.useSubscribe,
setInitialValues: _this.setInitialValues,
setCallbacks: _this.setCallbacks,
setValidateMessages: _this.setValidateMessages,
getFields: _this.getFields,
setPreserve: _this.setPreserve
};
}
warning(false, '`getInternalHooks` is internal usage. Should not call directly.');
return null;
};
this.useSubscribe = function (subscribable) {
_this.subscribable = subscribable;
};
/**
* First time `setInitialValues` should update store with initial value
*/
this.setInitialValues = function (initialValues, init) {
_this.initialValues = initialValues || {};
if (init) {
_this.store = setValues({}, initialValues, _this.store);
}
};
this.getInitialValue = function (namePath) {
return 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;
}; // ========================== Dev Warning =========================
this.timeoutId = null;
this.warningUnhooked = function () {
if (process.env.NODE_ENV !== 'production' && !_this.timeoutId && typeof window !== 'undefined') {
_this.timeoutId = window.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?');
}
});
}
}; // ============================ Fields ============================
/**
* Get registered field entities.
* @param pure Only return field which has a `name`. Default: false
*/
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 namePath = 'INVALIDATE_NAME_PATH' in entity ? entity.INVALIDATE_NAME_PATH : entity.getNamePath();
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()
};
}
return {
name: getNamePath(nameList[index]),
errors: []
};
});
};
this.getFieldError = function (name) {
_this.warningUnhooked();
var namePath = getNamePath(name);
var fieldError = _this.getFieldsError([namePath])[0];
return fieldError.errors;
};
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 testTouched = function testTouched(field) {
// Not provide `nameList` will check all the fields
if (!namePathList) {
return field.isFieldTouched();
}
var fieldNamePath = field.getNamePath();
if (containsNamePath(namePathList, fieldNamePath)) {
return field.isFieldTouched();
}
return isAllFieldsTouched;
};
return isAllFieldsTouched ? _this.getFieldEntities(true).every(testTouched) : _this.getFieldEntities(true).some(testTouched);
};
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]);
};
/**
* Reset Field with field `initialValue` prop.
* Can pass `entities` or `namePathList` or just nothing.
*/
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, ["name", "errors"]);
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({}, meta, {
name: namePath,
value: _this.getFieldValue(namePath)
});
Object.defineProperty(fieldData, 'originRCField', {
value: true
});
return fieldData;
});
return fields;
}; // =========================== Observer ===========================
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 (preserve) {
_this.fieldEntities = _this.fieldEntities.filter(function (item) {
return item !== entity;
}); // Clean up store value if preserve
var mergedPreserve = preserve !== undefined ? preserve : _this.preserve;
if (mergedPreserve === false) {
var namePath = entity.getNamePath();
if (_this.getFieldValue(namePath) !== undefined) {
_this.store = setValue(_this.store, namePath, undefined);
}
}
};
};
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({}, info, {
store: _this.getFieldsValue(true)
});
_this.getFieldEntities().forEach(function (_ref) {
var onStoreChange = _ref.onStoreChange;
onStoreChange(prevStore, namePathList, mergedInfo);
});
} else {
_this.forceRootUpdate();
}
};
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'
}); // Notify dependencies children with parent update
var childrenFields = _this.getDependencyChildrenFields(namePath);
_this.validateFields(childrenFields);
_this.notifyObservers(prevStore, childrenFields, {
type: 'dependenciesUpdate',
relatedFields: [namePath].concat(_toConsumableArray(childrenFields))
}); // trigger callback function
var onValuesChange = _this.callbacks.onValuesChange;
if (onValuesChange) {
var changedValues = cloneByNamePathList(_this.store, [namePath]);
onValuesChange(changedValues, _this.store);
}
_this.triggerOnFieldsChange([namePath].concat(_toConsumableArray(childrenFields)));
}; // Let all child Field get update.
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 (_ref2) {
var name = _ref2.name,
errors = _ref2.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 (_ref3) {
var fieldName = _ref3.name;
return containsNamePath(namePathList, fieldName);
});
onFieldsChange(changedFields, fields);
}
}; // =========================== Validate ===========================
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());
} // 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({}, defaultValidateMessages, {}, _this.validateMessages)
}, options)); // Wrap promise with field
promiseList.push(promise.then(function () {
return {
name: fieldNamePath,
errors: []
};
}).catch(function (errors) {
return Promise.reject({
name: fieldNamePath,
errors: errors
});
}));
}
});
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 (_ref4) {
var name = _ref4.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;
}; // ============================ Submit ============================
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;