1341 lines
40 KiB
JavaScript
1341 lines
40 KiB
JavaScript
import _extends from '@babel/runtime/helpers/esm/extends';
|
|
import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/esm/objectWithoutPropertiesLoose';
|
|
|
|
//
|
|
var toPath = function toPath(key) {
|
|
if (key === null || key === undefined || !key.length) {
|
|
return [];
|
|
}
|
|
|
|
if (typeof key !== 'string') {
|
|
throw new Error('toPath() expects a string');
|
|
}
|
|
|
|
return key.split(/[.[\]]+/).filter(Boolean);
|
|
};
|
|
|
|
//
|
|
|
|
var getIn = function getIn(state, complexKey) {
|
|
// Intentionally using iteration rather than recursion
|
|
var path = toPath(complexKey);
|
|
var current = state;
|
|
|
|
for (var i = 0; i < path.length; i++) {
|
|
var key = path[i];
|
|
|
|
if (current === undefined || current === null || typeof current !== 'object' || Array.isArray(current) && isNaN(key)) {
|
|
return undefined;
|
|
}
|
|
|
|
current = current[key];
|
|
}
|
|
|
|
return current;
|
|
};
|
|
|
|
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
|
|
|
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
|
|
var setInRecursor = function setInRecursor(current, index, path, value, destroyArrays) {
|
|
if (index >= path.length) {
|
|
// end of recursion
|
|
return value;
|
|
}
|
|
|
|
var key = path[index]; // determine type of key
|
|
|
|
if (isNaN(key)) {
|
|
var _extends2;
|
|
|
|
// object set
|
|
if (current === undefined || current === null) {
|
|
var _ref;
|
|
|
|
// recurse
|
|
var _result2 = setInRecursor(undefined, index + 1, path, value, destroyArrays); // delete or create an object
|
|
|
|
|
|
return _result2 === undefined ? undefined : (_ref = {}, _ref[key] = _result2, _ref);
|
|
}
|
|
|
|
if (Array.isArray(current)) {
|
|
throw new Error('Cannot set a non-numeric property on an array');
|
|
} // current exists, so make a copy of all its values, and add/update the new one
|
|
|
|
|
|
var _result = setInRecursor(current[key], index + 1, path, value, destroyArrays);
|
|
|
|
if (_result === undefined) {
|
|
var numKeys = Object.keys(current).length;
|
|
|
|
if (current[key] === undefined && numKeys === 0) {
|
|
// object was already empty
|
|
return undefined;
|
|
}
|
|
|
|
if (current[key] !== undefined && numKeys <= 1) {
|
|
// only key we had was the one we are deleting
|
|
if (!isNaN(path[index - 1]) && !destroyArrays) {
|
|
// we are in an array, so return an empty object
|
|
return {};
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
var _removed = current[key],
|
|
_final = _objectWithoutPropertiesLoose(current, [key].map(_toPropertyKey));
|
|
|
|
return _final;
|
|
} // set result in key
|
|
|
|
|
|
return _extends({}, current, (_extends2 = {}, _extends2[key] = _result, _extends2));
|
|
} // array set
|
|
|
|
|
|
var numericKey = Number(key);
|
|
|
|
if (current === undefined || current === null) {
|
|
// recurse
|
|
var _result3 = setInRecursor(undefined, index + 1, path, value, destroyArrays); // if nothing returned, delete it
|
|
|
|
|
|
if (_result3 === undefined) {
|
|
return undefined;
|
|
} // create an array
|
|
|
|
|
|
var _array = [];
|
|
_array[numericKey] = _result3;
|
|
return _array;
|
|
}
|
|
|
|
if (!Array.isArray(current)) {
|
|
throw new Error('Cannot set a numeric property on an object');
|
|
} // recurse
|
|
|
|
|
|
var existingValue = current[numericKey];
|
|
var result = setInRecursor(existingValue, index + 1, path, value, destroyArrays); // current exists, so make a copy of all its values, and add/update the new one
|
|
|
|
var array = [].concat(current);
|
|
|
|
if (destroyArrays && result === undefined) {
|
|
array.splice(numericKey, 1);
|
|
|
|
if (array.length === 0) {
|
|
return undefined;
|
|
}
|
|
} else {
|
|
array[numericKey] = result;
|
|
}
|
|
|
|
return array;
|
|
};
|
|
|
|
var setIn = function setIn(state, key, value, destroyArrays) {
|
|
if (destroyArrays === void 0) {
|
|
destroyArrays = false;
|
|
}
|
|
|
|
if (state === undefined || state === null) {
|
|
throw new Error("Cannot call setIn() with " + String(state) + " state");
|
|
}
|
|
|
|
if (key === undefined || key === null) {
|
|
throw new Error("Cannot call setIn() with " + String(key) + " key");
|
|
} // Recursive function needs to accept and return State, but public API should
|
|
// only deal with Objects
|
|
|
|
|
|
return setInRecursor(state, 0, toPath(key), value, destroyArrays);
|
|
};
|
|
|
|
var FORM_ERROR = 'FINAL_FORM/form-error';
|
|
var ARRAY_ERROR = 'FINAL_FORM/array-error';
|
|
|
|
//
|
|
/**
|
|
* Converts internal field state to published field state
|
|
*/
|
|
|
|
function publishFieldState(formState, field) {
|
|
var errors = formState.errors,
|
|
initialValues = formState.initialValues,
|
|
lastSubmittedValues = formState.lastSubmittedValues,
|
|
submitErrors = formState.submitErrors,
|
|
submitFailed = formState.submitFailed,
|
|
submitSucceeded = formState.submitSucceeded,
|
|
submitting = formState.submitting,
|
|
values = formState.values;
|
|
var active = field.active,
|
|
blur = field.blur,
|
|
change = field.change,
|
|
data = field.data,
|
|
focus = field.focus,
|
|
modified = field.modified,
|
|
name = field.name,
|
|
touched = field.touched,
|
|
validating = field.validating,
|
|
visited = field.visited;
|
|
var value = getIn(values, name);
|
|
var error = getIn(errors, name);
|
|
|
|
if (error && error[ARRAY_ERROR]) {
|
|
error = error[ARRAY_ERROR];
|
|
}
|
|
|
|
var submitError = submitErrors && getIn(submitErrors, name);
|
|
var initial = initialValues && getIn(initialValues, name);
|
|
var pristine = field.isEqual(initial, value);
|
|
var dirtySinceLastSubmit = !!(lastSubmittedValues && !field.isEqual(getIn(lastSubmittedValues, name), value));
|
|
var valid = !error && !submitError;
|
|
return {
|
|
active: active,
|
|
blur: blur,
|
|
change: change,
|
|
data: data,
|
|
dirty: !pristine,
|
|
dirtySinceLastSubmit: dirtySinceLastSubmit,
|
|
error: error,
|
|
focus: focus,
|
|
initial: initial,
|
|
invalid: !valid,
|
|
length: Array.isArray(value) ? value.length : undefined,
|
|
modified: modified,
|
|
name: name,
|
|
pristine: pristine,
|
|
submitError: submitError,
|
|
submitFailed: submitFailed,
|
|
submitSucceeded: submitSucceeded,
|
|
submitting: submitting,
|
|
touched: touched,
|
|
valid: valid,
|
|
value: value,
|
|
visited: visited,
|
|
validating: validating
|
|
};
|
|
}
|
|
|
|
//
|
|
var fieldSubscriptionItems = ['active', 'data', 'dirty', 'dirtySinceLastSubmit', 'error', 'initial', 'invalid', 'length', 'modified', 'pristine', 'submitError', 'submitFailed', 'submitSucceeded', 'submitting', 'touched', 'valid', 'value', 'visited', 'validating'];
|
|
|
|
//
|
|
var shallowEqual = function shallowEqual(a, b) {
|
|
if (a === b) {
|
|
return true;
|
|
}
|
|
|
|
if (typeof a !== 'object' || !a || typeof b !== 'object' || !b) {
|
|
return false;
|
|
}
|
|
|
|
var keysA = Object.keys(a);
|
|
var keysB = Object.keys(b);
|
|
|
|
if (keysA.length !== keysB.length) {
|
|
return false;
|
|
}
|
|
|
|
var bHasOwnProperty = Object.prototype.hasOwnProperty.bind(b);
|
|
|
|
for (var idx = 0; idx < keysA.length; idx++) {
|
|
var key = keysA[idx];
|
|
|
|
if (!bHasOwnProperty(key) || a[key] !== b[key]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
//
|
|
function subscriptionFilter (dest, src, previous, subscription, keys, shallowEqualKeys) {
|
|
var different = false;
|
|
keys.forEach(function (key) {
|
|
if (subscription[key]) {
|
|
dest[key] = src[key];
|
|
|
|
if (!previous || (~shallowEqualKeys.indexOf(key) ? !shallowEqual(src[key], previous[key]) : src[key] !== previous[key])) {
|
|
different = true;
|
|
}
|
|
}
|
|
});
|
|
return different;
|
|
}
|
|
|
|
//
|
|
var shallowEqualKeys = ['data'];
|
|
/**
|
|
* Filters items in a FieldState based on a FieldSubscription
|
|
*/
|
|
|
|
var filterFieldState = function filterFieldState(state, previousState, subscription, force) {
|
|
var result = {
|
|
blur: state.blur,
|
|
change: state.change,
|
|
focus: state.focus,
|
|
name: state.name
|
|
};
|
|
var different = subscriptionFilter(result, state, previousState, subscription, fieldSubscriptionItems, shallowEqualKeys) || !previousState;
|
|
return different || force ? result : undefined;
|
|
};
|
|
|
|
//
|
|
var formSubscriptionItems = ['active', 'dirty', 'dirtyFields', 'dirtyFieldsSinceLastSubmit', 'dirtySinceLastSubmit', 'error', 'errors', 'hasSubmitErrors', 'hasValidationErrors', 'initialValues', 'invalid', 'modified', 'pristine', 'submitting', 'submitError', 'submitErrors', 'submitFailed', 'submitSucceeded', 'touched', 'valid', 'validating', 'values', 'visited'];
|
|
|
|
//
|
|
var shallowEqualKeys$1 = ['touched', 'visited'];
|
|
/**
|
|
* Filters items in a FormState based on a FormSubscription
|
|
*/
|
|
|
|
function filterFormState(state, previousState, subscription, force) {
|
|
var result = {};
|
|
var different = subscriptionFilter(result, state, previousState, subscription, formSubscriptionItems, shallowEqualKeys$1) || !previousState;
|
|
return different || force ? result : undefined;
|
|
}
|
|
|
|
//
|
|
|
|
var memoize = function memoize(fn) {
|
|
var lastArgs;
|
|
var lastResult;
|
|
return function () {
|
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
args[_key] = arguments[_key];
|
|
}
|
|
|
|
if (!lastArgs || args.length !== lastArgs.length || args.some(function (arg, index) {
|
|
return !shallowEqual(lastArgs[index], arg);
|
|
})) {
|
|
lastArgs = args;
|
|
lastResult = fn.apply(void 0, args);
|
|
}
|
|
|
|
return lastResult;
|
|
};
|
|
};
|
|
|
|
var isPromise = (function (obj) {
|
|
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
|
|
});
|
|
|
|
var version = "4.18.6";
|
|
|
|
var configOptions = ['debug', 'initialValues', 'keepDirtyOnReinitialize', 'mutators', 'onSubmit', 'validate', 'validateOnBlur'];
|
|
|
|
var tripleEquals = function tripleEquals(a, b) {
|
|
return a === b;
|
|
};
|
|
|
|
var hasAnyError = function hasAnyError(errors) {
|
|
return Object.keys(errors).some(function (key) {
|
|
var value = errors[key];
|
|
|
|
if (value && typeof value === 'object' && !(value instanceof Error)) {
|
|
return hasAnyError(value);
|
|
}
|
|
|
|
return typeof value !== 'undefined';
|
|
});
|
|
};
|
|
|
|
function convertToExternalFormState(_ref) {
|
|
var active = _ref.active,
|
|
dirtySinceLastSubmit = _ref.dirtySinceLastSubmit,
|
|
error = _ref.error,
|
|
errors = _ref.errors,
|
|
initialValues = _ref.initialValues,
|
|
pristine = _ref.pristine,
|
|
submitting = _ref.submitting,
|
|
submitFailed = _ref.submitFailed,
|
|
submitSucceeded = _ref.submitSucceeded,
|
|
submitError = _ref.submitError,
|
|
submitErrors = _ref.submitErrors,
|
|
valid = _ref.valid,
|
|
validating = _ref.validating,
|
|
values = _ref.values;
|
|
return {
|
|
active: active,
|
|
dirty: !pristine,
|
|
dirtySinceLastSubmit: dirtySinceLastSubmit,
|
|
error: error,
|
|
errors: errors,
|
|
hasSubmitErrors: !!(submitError || submitErrors && hasAnyError(submitErrors)),
|
|
hasValidationErrors: !!(error || hasAnyError(errors)),
|
|
invalid: !valid,
|
|
initialValues: initialValues,
|
|
pristine: pristine,
|
|
submitting: submitting,
|
|
submitFailed: submitFailed,
|
|
submitSucceeded: submitSucceeded,
|
|
submitError: submitError,
|
|
submitErrors: submitErrors,
|
|
valid: valid,
|
|
validating: validating > 0,
|
|
values: values
|
|
};
|
|
}
|
|
|
|
function notifySubscriber(subscriber, subscription, state, lastState, filter, force) {
|
|
var notification = filter(state, lastState, subscription, force);
|
|
|
|
if (notification) {
|
|
subscriber(notification);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function notify(_ref2, state, lastState, filter, force) {
|
|
var entries = _ref2.entries;
|
|
Object.keys(entries).forEach(function (key) {
|
|
var entry = entries[Number(key)]; // istanbul ignore next
|
|
|
|
if (entry) {
|
|
var subscription = entry.subscription,
|
|
subscriber = entry.subscriber,
|
|
notified = entry.notified;
|
|
|
|
if (notifySubscriber(subscriber, subscription, state, lastState, filter, force || !notified)) {
|
|
entry.notified = true;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function createForm(config) {
|
|
if (!config) {
|
|
throw new Error('No config specified');
|
|
}
|
|
|
|
var debug = config.debug,
|
|
destroyOnUnregister = config.destroyOnUnregister,
|
|
keepDirtyOnReinitialize = config.keepDirtyOnReinitialize,
|
|
initialValues = config.initialValues,
|
|
mutators = config.mutators,
|
|
onSubmit = config.onSubmit,
|
|
validate = config.validate,
|
|
validateOnBlur = config.validateOnBlur;
|
|
|
|
if (!onSubmit) {
|
|
throw new Error('No onSubmit function specified');
|
|
}
|
|
|
|
var state = {
|
|
subscribers: {
|
|
index: 0,
|
|
entries: {}
|
|
},
|
|
fieldSubscribers: {},
|
|
fields: {},
|
|
formState: {
|
|
dirtySinceLastSubmit: false,
|
|
errors: {},
|
|
initialValues: initialValues && _extends({}, initialValues),
|
|
invalid: false,
|
|
pristine: true,
|
|
submitting: false,
|
|
submitFailed: false,
|
|
submitSucceeded: false,
|
|
valid: true,
|
|
validating: 0,
|
|
values: initialValues ? _extends({}, initialValues) : {}
|
|
},
|
|
lastFormState: undefined
|
|
};
|
|
var inBatch = 0;
|
|
var validationPaused = false;
|
|
var validationBlocked = false;
|
|
var nextAsyncValidationKey = 0;
|
|
var asyncValidationPromises = {};
|
|
|
|
var clearAsyncValidationPromise = function clearAsyncValidationPromise(key) {
|
|
return function (result) {
|
|
delete asyncValidationPromises[key];
|
|
return result;
|
|
};
|
|
};
|
|
|
|
var changeValue = function changeValue(state, name, mutate) {
|
|
var before = getIn(state.formState.values, name);
|
|
var after = mutate(before);
|
|
state.formState.values = setIn(state.formState.values, name, after) || {};
|
|
};
|
|
|
|
var renameField = function renameField(state, from, to) {
|
|
if (state.fields[from]) {
|
|
var _extends2, _extends3;
|
|
|
|
state.fields = _extends({}, state.fields, (_extends2 = {}, _extends2[to] = _extends({}, state.fields[from], {
|
|
name: to,
|
|
// rebind event handlers
|
|
blur: function blur() {
|
|
return api.blur(to);
|
|
},
|
|
change: function change(value) {
|
|
return api.change(to, value);
|
|
},
|
|
focus: function focus() {
|
|
return api.focus(to);
|
|
},
|
|
lastFieldState: undefined
|
|
}), _extends2));
|
|
delete state.fields[from];
|
|
state.fieldSubscribers = _extends({}, state.fieldSubscribers, (_extends3 = {}, _extends3[to] = state.fieldSubscribers[from], _extends3));
|
|
delete state.fieldSubscribers[from];
|
|
var value = getIn(state.formState.values, from);
|
|
state.formState.values = setIn(state.formState.values, from, undefined) || {};
|
|
state.formState.values = setIn(state.formState.values, to, value);
|
|
delete state.lastFormState;
|
|
}
|
|
}; // bind state to mutators
|
|
|
|
|
|
var getMutatorApi = function getMutatorApi(key) {
|
|
return function () {
|
|
// istanbul ignore next
|
|
if (mutators) {
|
|
// ^^ causes branch coverage warning, but needed to appease the Flow gods
|
|
var mutatableState = {
|
|
formState: state.formState,
|
|
fields: state.fields,
|
|
fieldSubscribers: state.fieldSubscribers,
|
|
lastFormState: state.lastFormState
|
|
};
|
|
|
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
args[_key] = arguments[_key];
|
|
}
|
|
|
|
var returnValue = mutators[key](args, mutatableState, {
|
|
changeValue: changeValue,
|
|
getIn: getIn,
|
|
renameField: renameField,
|
|
resetFieldState: api.resetFieldState,
|
|
setIn: setIn,
|
|
shallowEqual: shallowEqual
|
|
});
|
|
state.formState = mutatableState.formState;
|
|
state.fields = mutatableState.fields;
|
|
state.fieldSubscribers = mutatableState.fieldSubscribers;
|
|
state.lastFormState = mutatableState.lastFormState;
|
|
runValidation(undefined, function () {
|
|
notifyFieldListeners();
|
|
notifyFormListeners();
|
|
});
|
|
return returnValue;
|
|
}
|
|
};
|
|
};
|
|
|
|
var mutatorsApi = mutators ? Object.keys(mutators).reduce(function (result, key) {
|
|
result[key] = getMutatorApi(key);
|
|
return result;
|
|
}, {}) : {};
|
|
|
|
var runRecordLevelValidation = function runRecordLevelValidation(setErrors) {
|
|
var promises = [];
|
|
|
|
if (validate) {
|
|
var errorsOrPromise = validate(_extends({}, state.formState.values)); // clone to avoid writing
|
|
|
|
if (isPromise(errorsOrPromise)) {
|
|
promises.push(errorsOrPromise.then(setErrors));
|
|
} else {
|
|
setErrors(errorsOrPromise);
|
|
}
|
|
}
|
|
|
|
return promises;
|
|
};
|
|
|
|
var getValidators = function getValidators(field) {
|
|
return Object.keys(field.validators).reduce(function (result, index) {
|
|
var validator = field.validators[Number(index)]();
|
|
|
|
if (validator) {
|
|
result.push(validator);
|
|
}
|
|
|
|
return result;
|
|
}, []);
|
|
};
|
|
|
|
var runFieldLevelValidation = function runFieldLevelValidation(field, setError) {
|
|
var promises = [];
|
|
var validators = getValidators(field);
|
|
|
|
if (validators.length) {
|
|
var error;
|
|
validators.forEach(function (validator) {
|
|
var errorOrPromise = validator(getIn(state.formState.values, field.name), state.formState.values, validator.length === 3 ? publishFieldState(state.formState, state.fields[field.name]) : undefined);
|
|
|
|
if (errorOrPromise && isPromise(errorOrPromise)) {
|
|
field.validating = true;
|
|
var promise = errorOrPromise.then(function (error) {
|
|
field.validating = false;
|
|
setError(error);
|
|
}); // errors must be resolved, not rejected
|
|
|
|
promises.push(promise);
|
|
} else if (!error) {
|
|
// first registered validator wins
|
|
error = errorOrPromise;
|
|
}
|
|
});
|
|
setError(error);
|
|
}
|
|
|
|
return promises;
|
|
};
|
|
|
|
var runValidation = function runValidation(fieldChanged, callback) {
|
|
if (validationPaused) {
|
|
validationBlocked = true;
|
|
callback();
|
|
return;
|
|
}
|
|
|
|
var fields = state.fields,
|
|
formState = state.formState;
|
|
|
|
var safeFields = _extends({}, fields);
|
|
|
|
var fieldKeys = Object.keys(safeFields);
|
|
|
|
if (!validate && !fieldKeys.some(function (key) {
|
|
return getValidators(safeFields[key]).length;
|
|
})) {
|
|
callback();
|
|
return; // no validation rules
|
|
} // pare down field keys to actually validate
|
|
|
|
|
|
var limitedFieldLevelValidation = false;
|
|
|
|
if (fieldChanged) {
|
|
var changedField = safeFields[fieldChanged];
|
|
|
|
if (changedField) {
|
|
var validateFields = changedField.validateFields;
|
|
|
|
if (validateFields) {
|
|
limitedFieldLevelValidation = true;
|
|
fieldKeys = validateFields.length ? validateFields.concat(fieldChanged) : [fieldChanged];
|
|
}
|
|
}
|
|
}
|
|
|
|
var recordLevelErrors = {};
|
|
var fieldLevelErrors = {};
|
|
var promises = [].concat(runRecordLevelValidation(function (errors) {
|
|
recordLevelErrors = errors || {};
|
|
}), fieldKeys.reduce(function (result, name) {
|
|
return result.concat(runFieldLevelValidation(fields[name], function (error) {
|
|
fieldLevelErrors[name] = error;
|
|
}));
|
|
}, []));
|
|
var hasAsyncValidations = promises.length > 0;
|
|
var asyncValidationPromiseKey = ++nextAsyncValidationKey;
|
|
var promise = Promise.all(promises).then(clearAsyncValidationPromise(asyncValidationPromiseKey)); // backwards-compat: add promise to submit-blocking promises iff there are any promises to await
|
|
|
|
if (hasAsyncValidations) {
|
|
asyncValidationPromises[asyncValidationPromiseKey] = promise;
|
|
}
|
|
|
|
var processErrors = function processErrors() {
|
|
var merged = _extends({}, limitedFieldLevelValidation ? formState.errors : {}, recordLevelErrors);
|
|
|
|
var forEachError = function forEachError(fn) {
|
|
fieldKeys.forEach(function (name) {
|
|
if (fields[name]) {
|
|
// make sure field is still registered
|
|
// field-level errors take precedent over record-level errors
|
|
var recordLevelError = getIn(recordLevelErrors, name);
|
|
var errorFromParent = getIn(merged, name);
|
|
var hasFieldLevelValidation = getValidators(safeFields[name]).length;
|
|
var fieldLevelError = fieldLevelErrors[name];
|
|
fn(name, hasFieldLevelValidation && fieldLevelError || validate && recordLevelError || (!recordLevelError && !limitedFieldLevelValidation ? errorFromParent : undefined));
|
|
}
|
|
});
|
|
};
|
|
|
|
forEachError(function (name, error) {
|
|
merged = setIn(merged, name, error) || {};
|
|
});
|
|
forEachError(function (name, error) {
|
|
if (error && error[ARRAY_ERROR]) {
|
|
var existing = getIn(merged, name);
|
|
var copy = [].concat(existing);
|
|
copy[ARRAY_ERROR] = error[ARRAY_ERROR];
|
|
merged = setIn(merged, name, copy);
|
|
}
|
|
});
|
|
|
|
if (!shallowEqual(formState.errors, merged)) {
|
|
formState.errors = merged;
|
|
}
|
|
|
|
formState.error = recordLevelErrors[FORM_ERROR];
|
|
}; // process sync errors
|
|
|
|
|
|
processErrors(); // sync errors have been set. notify listeners while we wait for others
|
|
|
|
callback();
|
|
|
|
if (hasAsyncValidations) {
|
|
state.formState.validating++;
|
|
callback();
|
|
|
|
var afterPromise = function afterPromise() {
|
|
state.formState.validating--;
|
|
callback();
|
|
};
|
|
|
|
promise.then(function () {
|
|
if (nextAsyncValidationKey > asyncValidationPromiseKey) {
|
|
// if this async validator has been superseded by another, ignore its results
|
|
return;
|
|
}
|
|
|
|
processErrors();
|
|
}).then(afterPromise, afterPromise);
|
|
}
|
|
};
|
|
|
|
var notifyFieldListeners = function notifyFieldListeners(name) {
|
|
if (inBatch) {
|
|
return;
|
|
}
|
|
|
|
var fields = state.fields,
|
|
fieldSubscribers = state.fieldSubscribers,
|
|
formState = state.formState;
|
|
|
|
var safeFields = _extends({}, fields);
|
|
|
|
var notifyField = function notifyField(name) {
|
|
var field = safeFields[name];
|
|
var fieldState = publishFieldState(formState, field);
|
|
var lastFieldState = field.lastFieldState;
|
|
field.lastFieldState = fieldState;
|
|
var fieldSubscriber = fieldSubscribers[name];
|
|
|
|
if (fieldSubscriber) {
|
|
notify(fieldSubscriber, fieldState, lastFieldState, filterFieldState, lastFieldState === undefined);
|
|
}
|
|
};
|
|
|
|
if (name) {
|
|
notifyField(name);
|
|
} else {
|
|
Object.keys(safeFields).forEach(notifyField);
|
|
}
|
|
};
|
|
|
|
var markAllFieldsTouched = function markAllFieldsTouched() {
|
|
Object.keys(state.fields).forEach(function (key) {
|
|
state.fields[key].touched = true;
|
|
});
|
|
};
|
|
|
|
var hasSyncErrors = function hasSyncErrors() {
|
|
return !!(state.formState.error || hasAnyError(state.formState.errors));
|
|
};
|
|
|
|
var calculateNextFormState = function calculateNextFormState() {
|
|
var fields = state.fields,
|
|
formState = state.formState,
|
|
lastFormState = state.lastFormState;
|
|
|
|
var safeFields = _extends({}, fields);
|
|
|
|
var safeFieldKeys = Object.keys(safeFields); // calculate dirty/pristine
|
|
|
|
var foundDirty = false;
|
|
var dirtyFields = safeFieldKeys.reduce(function (result, key) {
|
|
var dirty = !safeFields[key].isEqual(getIn(formState.values, key), getIn(formState.initialValues || {}, key));
|
|
|
|
if (dirty) {
|
|
foundDirty = true;
|
|
result[key] = true;
|
|
}
|
|
|
|
return result;
|
|
}, {});
|
|
var dirtyFieldsSinceLastSubmit = safeFieldKeys.reduce(function (result, key) {
|
|
// istanbul ignore next
|
|
var nonNullLastSubmittedValues = formState.lastSubmittedValues || {}; // || {} is for flow, but causes branch coverage complaint
|
|
|
|
if (!safeFields[key].isEqual(getIn(formState.values, key), getIn(nonNullLastSubmittedValues, key))) {
|
|
result[key] = true;
|
|
}
|
|
|
|
return result;
|
|
}, {});
|
|
formState.pristine = !foundDirty;
|
|
formState.dirtySinceLastSubmit = !!(formState.lastSubmittedValues && Object.values(dirtyFieldsSinceLastSubmit).some(function (value) {
|
|
return value;
|
|
}));
|
|
formState.valid = !formState.error && !formState.submitError && !hasAnyError(formState.errors) && !(formState.submitErrors && hasAnyError(formState.submitErrors));
|
|
var nextFormState = convertToExternalFormState(formState);
|
|
|
|
var _safeFieldKeys$reduce = safeFieldKeys.reduce(function (result, key) {
|
|
result.modified[key] = safeFields[key].modified;
|
|
result.touched[key] = safeFields[key].touched;
|
|
result.visited[key] = safeFields[key].visited;
|
|
return result;
|
|
}, {
|
|
modified: {},
|
|
touched: {},
|
|
visited: {}
|
|
}),
|
|
modified = _safeFieldKeys$reduce.modified,
|
|
touched = _safeFieldKeys$reduce.touched,
|
|
visited = _safeFieldKeys$reduce.visited;
|
|
|
|
nextFormState.dirtyFields = lastFormState && shallowEqual(lastFormState.dirtyFields, dirtyFields) ? lastFormState.dirtyFields : dirtyFields;
|
|
nextFormState.dirtyFieldsSinceLastSubmit = lastFormState && shallowEqual(lastFormState.dirtyFieldsSinceLastSubmit, dirtyFieldsSinceLastSubmit) ? lastFormState.dirtyFieldsSinceLastSubmit : dirtyFieldsSinceLastSubmit;
|
|
nextFormState.modified = lastFormState && shallowEqual(lastFormState.modified, modified) ? lastFormState.modified : modified;
|
|
nextFormState.touched = lastFormState && shallowEqual(lastFormState.touched, touched) ? lastFormState.touched : touched;
|
|
nextFormState.visited = lastFormState && shallowEqual(lastFormState.visited, visited) ? lastFormState.visited : visited;
|
|
return lastFormState && shallowEqual(lastFormState, nextFormState) ? lastFormState : nextFormState;
|
|
};
|
|
|
|
var callDebug = function callDebug() {
|
|
return debug && "development" !== 'production' && debug(calculateNextFormState(), Object.keys(state.fields).reduce(function (result, key) {
|
|
result[key] = state.fields[key];
|
|
return result;
|
|
}, {}));
|
|
};
|
|
|
|
var notifying = false;
|
|
var scheduleNotification = false;
|
|
|
|
var notifyFormListeners = function notifyFormListeners() {
|
|
if (notifying) {
|
|
scheduleNotification = true;
|
|
} else {
|
|
notifying = true;
|
|
callDebug();
|
|
|
|
if (!inBatch && !validationPaused) {
|
|
var lastFormState = state.lastFormState;
|
|
var nextFormState = calculateNextFormState();
|
|
|
|
if (nextFormState !== lastFormState) {
|
|
state.lastFormState = nextFormState;
|
|
notify(state.subscribers, nextFormState, lastFormState, filterFormState);
|
|
}
|
|
}
|
|
|
|
notifying = false;
|
|
|
|
if (scheduleNotification) {
|
|
scheduleNotification = false;
|
|
notifyFormListeners();
|
|
}
|
|
}
|
|
};
|
|
|
|
var beforeSubmit = function beforeSubmit() {
|
|
return Object.keys(state.fields).some(function (name) {
|
|
return state.fields[name].beforeSubmit && state.fields[name].beforeSubmit() === false;
|
|
});
|
|
};
|
|
|
|
var afterSubmit = function afterSubmit() {
|
|
return Object.keys(state.fields).forEach(function (name) {
|
|
return state.fields[name].afterSubmit && state.fields[name].afterSubmit();
|
|
});
|
|
}; // generate initial errors
|
|
|
|
|
|
runValidation(undefined, function () {
|
|
notifyFormListeners();
|
|
});
|
|
var api = {
|
|
batch: function batch(fn) {
|
|
inBatch++;
|
|
fn();
|
|
inBatch--;
|
|
notifyFieldListeners();
|
|
notifyFormListeners();
|
|
},
|
|
blur: function blur(name) {
|
|
var fields = state.fields,
|
|
formState = state.formState;
|
|
var previous = fields[name];
|
|
|
|
if (previous) {
|
|
// can only blur registered fields
|
|
delete formState.active;
|
|
fields[name] = _extends({}, previous, {
|
|
active: false,
|
|
touched: true
|
|
});
|
|
|
|
if (validateOnBlur) {
|
|
runValidation(name, function () {
|
|
notifyFieldListeners();
|
|
notifyFormListeners();
|
|
});
|
|
} else {
|
|
notifyFieldListeners();
|
|
notifyFormListeners();
|
|
}
|
|
}
|
|
},
|
|
change: function change(name, value) {
|
|
var fields = state.fields,
|
|
formState = state.formState;
|
|
|
|
if (getIn(formState.values, name) !== value) {
|
|
changeValue(state, name, function () {
|
|
return value;
|
|
});
|
|
var previous = fields[name];
|
|
|
|
if (previous) {
|
|
// only track modified for registered fields
|
|
fields[name] = _extends({}, previous, {
|
|
modified: true
|
|
});
|
|
}
|
|
|
|
if (validateOnBlur) {
|
|
notifyFieldListeners();
|
|
notifyFormListeners();
|
|
} else {
|
|
runValidation(name, function () {
|
|
notifyFieldListeners();
|
|
notifyFormListeners();
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
get destroyOnUnregister() {
|
|
return !!destroyOnUnregister;
|
|
},
|
|
|
|
set destroyOnUnregister(value) {
|
|
destroyOnUnregister = value;
|
|
},
|
|
|
|
focus: function focus(name) {
|
|
var field = state.fields[name];
|
|
|
|
if (field && !field.active) {
|
|
state.formState.active = name;
|
|
field.active = true;
|
|
field.visited = true;
|
|
notifyFieldListeners();
|
|
notifyFormListeners();
|
|
}
|
|
},
|
|
mutators: mutatorsApi,
|
|
getFieldState: function getFieldState(name) {
|
|
var field = state.fields[name];
|
|
return field && field.lastFieldState;
|
|
},
|
|
getRegisteredFields: function getRegisteredFields() {
|
|
return Object.keys(state.fields);
|
|
},
|
|
getState: function getState() {
|
|
return calculateNextFormState();
|
|
},
|
|
initialize: function initialize(data) {
|
|
var fields = state.fields,
|
|
formState = state.formState;
|
|
|
|
var safeFields = _extends({}, fields);
|
|
|
|
var values = typeof data === 'function' ? data(formState.values) : data;
|
|
|
|
if (!keepDirtyOnReinitialize) {
|
|
formState.values = values;
|
|
}
|
|
|
|
Object.keys(safeFields).forEach(function (key) {
|
|
var field = safeFields[key];
|
|
field.modified = false;
|
|
field.touched = false;
|
|
field.visited = false;
|
|
|
|
if (keepDirtyOnReinitialize) {
|
|
var pristine = field.isEqual(getIn(formState.values, key), getIn(formState.initialValues || {}, key));
|
|
|
|
if (pristine) {
|
|
// only update pristine values
|
|
formState.values = setIn(formState.values, key, getIn(values, key));
|
|
}
|
|
}
|
|
});
|
|
formState.initialValues = values;
|
|
runValidation(undefined, function () {
|
|
notifyFieldListeners();
|
|
notifyFormListeners();
|
|
});
|
|
},
|
|
isValidationPaused: function isValidationPaused() {
|
|
return validationPaused;
|
|
},
|
|
pauseValidation: function pauseValidation() {
|
|
validationPaused = true;
|
|
},
|
|
registerField: function registerField(name, subscriber, subscription, fieldConfig) {
|
|
if (subscription === void 0) {
|
|
subscription = {};
|
|
}
|
|
|
|
if (!state.fieldSubscribers[name]) {
|
|
state.fieldSubscribers[name] = {
|
|
index: 0,
|
|
entries: {}
|
|
};
|
|
}
|
|
|
|
var index = state.fieldSubscribers[name].index++; // save field subscriber callback
|
|
|
|
state.fieldSubscribers[name].entries[index] = {
|
|
subscriber: memoize(subscriber),
|
|
subscription: subscription,
|
|
notified: false
|
|
};
|
|
|
|
if (!state.fields[name]) {
|
|
// create initial field state
|
|
state.fields[name] = {
|
|
active: false,
|
|
afterSubmit: fieldConfig && fieldConfig.afterSubmit,
|
|
beforeSubmit: fieldConfig && fieldConfig.beforeSubmit,
|
|
blur: function blur() {
|
|
return api.blur(name);
|
|
},
|
|
change: function change(value) {
|
|
return api.change(name, value);
|
|
},
|
|
data: fieldConfig && fieldConfig.data || {},
|
|
focus: function focus() {
|
|
return api.focus(name);
|
|
},
|
|
isEqual: fieldConfig && fieldConfig.isEqual || tripleEquals,
|
|
lastFieldState: undefined,
|
|
modified: false,
|
|
name: name,
|
|
touched: false,
|
|
valid: true,
|
|
validateFields: fieldConfig && fieldConfig.validateFields,
|
|
validators: {},
|
|
validating: false,
|
|
visited: false
|
|
};
|
|
}
|
|
|
|
var haveValidator = false;
|
|
|
|
if (fieldConfig) {
|
|
haveValidator = !!(fieldConfig.getValidator && fieldConfig.getValidator());
|
|
|
|
if (fieldConfig.getValidator) {
|
|
state.fields[name].validators[index] = fieldConfig.getValidator;
|
|
}
|
|
|
|
if (fieldConfig.initialValue !== undefined && getIn(state.formState.values, name) === undefined // only initialize if we don't yet have any value for this field
|
|
) {
|
|
state.formState.initialValues = setIn(state.formState.initialValues || {}, name, fieldConfig.initialValue);
|
|
state.formState.values = setIn(state.formState.values, name, fieldConfig.initialValue);
|
|
}
|
|
|
|
if (fieldConfig.defaultValue !== undefined && fieldConfig.initialValue === undefined && getIn(state.formState.initialValues, name) === undefined) {
|
|
state.formState.values = setIn(state.formState.values, name, fieldConfig.defaultValue);
|
|
}
|
|
}
|
|
|
|
if (haveValidator) {
|
|
runValidation(undefined, function () {
|
|
notifyFormListeners();
|
|
notifyFieldListeners();
|
|
});
|
|
} else {
|
|
notifyFormListeners();
|
|
notifyFieldListeners(name);
|
|
}
|
|
|
|
return function () {
|
|
var validatorRemoved = false; // istanbul ignore next
|
|
|
|
if (state.fields[name]) {
|
|
// state.fields[name] may have been removed by a mutator
|
|
validatorRemoved = !!(state.fields[name].validators[index] && state.fields[name].validators[index]());
|
|
delete state.fields[name].validators[index];
|
|
}
|
|
|
|
delete state.fieldSubscribers[name].entries[index];
|
|
var lastOne = !Object.keys(state.fieldSubscribers[name].entries).length;
|
|
|
|
if (lastOne) {
|
|
delete state.fieldSubscribers[name];
|
|
delete state.fields[name];
|
|
|
|
if (validatorRemoved) {
|
|
state.formState.errors = setIn(state.formState.errors, name, undefined) || {};
|
|
}
|
|
|
|
if (destroyOnUnregister) {
|
|
state.formState.values = setIn(state.formState.values, name, undefined, true) || {};
|
|
}
|
|
}
|
|
|
|
if (validatorRemoved) {
|
|
runValidation(undefined, function () {
|
|
notifyFormListeners();
|
|
notifyFieldListeners();
|
|
});
|
|
} else if (lastOne) {
|
|
// values or errors may have changed
|
|
notifyFormListeners();
|
|
}
|
|
};
|
|
},
|
|
reset: function reset(initialValues) {
|
|
if (initialValues === void 0) {
|
|
initialValues = state.formState.initialValues;
|
|
}
|
|
|
|
if (state.formState.submitting) {
|
|
throw Error('Cannot reset() in onSubmit(), use setTimeout(form.reset)');
|
|
}
|
|
|
|
state.formState.submitFailed = false;
|
|
state.formState.submitSucceeded = false;
|
|
delete state.formState.submitError;
|
|
delete state.formState.submitErrors;
|
|
delete state.formState.lastSubmittedValues;
|
|
api.initialize(initialValues || {});
|
|
},
|
|
|
|
/**
|
|
* Resets all field flags (e.g. touched, visited, etc.) to their initial state
|
|
*/
|
|
resetFieldState: function resetFieldState(name) {
|
|
state.fields[name] = _extends({}, state.fields[name], {
|
|
active: false,
|
|
lastFieldState: undefined,
|
|
modified: false,
|
|
touched: false,
|
|
valid: true,
|
|
validating: false,
|
|
visited: false
|
|
});
|
|
runValidation(undefined, function () {
|
|
notifyFieldListeners();
|
|
notifyFormListeners();
|
|
});
|
|
},
|
|
resumeValidation: function resumeValidation() {
|
|
validationPaused = false;
|
|
|
|
if (validationBlocked) {
|
|
// validation was attempted while it was paused, so run it now
|
|
runValidation(undefined, function () {
|
|
notifyFieldListeners();
|
|
notifyFormListeners();
|
|
});
|
|
}
|
|
|
|
validationBlocked = false;
|
|
},
|
|
setConfig: function setConfig(name, value) {
|
|
switch (name) {
|
|
case 'debug':
|
|
debug = value;
|
|
break;
|
|
|
|
case 'destroyOnUnregister':
|
|
destroyOnUnregister = value;
|
|
break;
|
|
|
|
case 'initialValues':
|
|
api.initialize(value);
|
|
break;
|
|
|
|
case 'keepDirtyOnReinitialize':
|
|
keepDirtyOnReinitialize = value;
|
|
break;
|
|
|
|
case 'mutators':
|
|
mutators = value;
|
|
|
|
if (value) {
|
|
Object.keys(mutatorsApi).forEach(function (key) {
|
|
if (!(key in value)) {
|
|
delete mutatorsApi[key];
|
|
}
|
|
});
|
|
Object.keys(value).forEach(function (key) {
|
|
mutatorsApi[key] = getMutatorApi(key);
|
|
});
|
|
} else {
|
|
Object.keys(mutatorsApi).forEach(function (key) {
|
|
delete mutatorsApi[key];
|
|
});
|
|
}
|
|
|
|
break;
|
|
|
|
case 'onSubmit':
|
|
onSubmit = value;
|
|
break;
|
|
|
|
case 'validate':
|
|
validate = value;
|
|
runValidation(undefined, function () {
|
|
notifyFieldListeners();
|
|
notifyFormListeners();
|
|
});
|
|
break;
|
|
|
|
case 'validateOnBlur':
|
|
validateOnBlur = value;
|
|
break;
|
|
|
|
default:
|
|
throw new Error('Unrecognised option ' + name);
|
|
}
|
|
},
|
|
submit: function submit() {
|
|
var formState = state.formState;
|
|
|
|
if (formState.submitting) {
|
|
return;
|
|
}
|
|
|
|
if (hasSyncErrors()) {
|
|
markAllFieldsTouched();
|
|
state.formState.submitFailed = true;
|
|
notifyFormListeners();
|
|
notifyFieldListeners();
|
|
return; // no submit for you!!
|
|
}
|
|
|
|
var asyncValidationPromisesKeys = Object.keys(asyncValidationPromises);
|
|
|
|
if (asyncValidationPromisesKeys.length) {
|
|
// still waiting on async validation to complete...
|
|
Promise.all(asyncValidationPromisesKeys.map(function (key) {
|
|
return asyncValidationPromises[Number(key)];
|
|
})).then(api.submit, api.submit);
|
|
return;
|
|
}
|
|
|
|
var submitIsBlocked = beforeSubmit();
|
|
|
|
if (submitIsBlocked) {
|
|
return;
|
|
}
|
|
|
|
var resolvePromise;
|
|
var completeCalled = false;
|
|
|
|
var complete = function complete(errors) {
|
|
formState.submitting = false;
|
|
|
|
if (errors && hasAnyError(errors)) {
|
|
formState.submitFailed = true;
|
|
formState.submitSucceeded = false;
|
|
formState.submitErrors = errors;
|
|
formState.submitError = errors[FORM_ERROR];
|
|
markAllFieldsTouched();
|
|
} else {
|
|
formState.submitFailed = false;
|
|
formState.submitSucceeded = true;
|
|
afterSubmit();
|
|
}
|
|
|
|
notifyFormListeners();
|
|
notifyFieldListeners();
|
|
completeCalled = true;
|
|
|
|
if (resolvePromise) {
|
|
resolvePromise(errors);
|
|
}
|
|
|
|
return errors;
|
|
};
|
|
|
|
delete formState.submitErrors;
|
|
delete formState.submitError;
|
|
formState.submitting = true;
|
|
formState.submitFailed = false;
|
|
formState.submitSucceeded = false;
|
|
formState.lastSubmittedValues = _extends({}, formState.values); // onSubmit is either sync, callback or async with a Promise
|
|
|
|
var result = onSubmit(formState.values, api, complete);
|
|
|
|
if (!completeCalled) {
|
|
if (result && isPromise(result)) {
|
|
// onSubmit is async with a Promise
|
|
notifyFormListeners(); // let everyone know we are submitting
|
|
|
|
notifyFieldListeners(); // notify fields also
|
|
|
|
return result.then(complete, function (error) {
|
|
complete();
|
|
throw error;
|
|
});
|
|
} else if (onSubmit.length >= 3) {
|
|
// must be async, so we should return a Promise
|
|
notifyFormListeners(); // let everyone know we are submitting
|
|
|
|
notifyFieldListeners(); // notify fields also
|
|
|
|
return new Promise(function (resolve) {
|
|
resolvePromise = resolve;
|
|
});
|
|
} else {
|
|
// onSubmit is sync
|
|
complete(result);
|
|
}
|
|
}
|
|
},
|
|
subscribe: function subscribe(subscriber, subscription) {
|
|
if (!subscriber) {
|
|
throw new Error('No callback given.');
|
|
}
|
|
|
|
if (!subscription) {
|
|
throw new Error('No subscription provided. What values do you want to listen to?');
|
|
}
|
|
|
|
var memoized = memoize(subscriber);
|
|
var subscribers = state.subscribers;
|
|
var index = subscribers.index++;
|
|
subscribers.entries[index] = {
|
|
subscriber: memoized,
|
|
subscription: subscription,
|
|
notified: false
|
|
};
|
|
var nextFormState = calculateNextFormState();
|
|
notifySubscriber(memoized, subscription, nextFormState, nextFormState, filterFormState, true);
|
|
return function () {
|
|
delete subscribers.entries[index];
|
|
};
|
|
}
|
|
};
|
|
return api;
|
|
}
|
|
|
|
//
|
|
|
|
export { ARRAY_ERROR, FORM_ERROR, configOptions, createForm, fieldSubscriptionItems, formSubscriptionItems, getIn, setIn, version };
|